Compare commits

...

28 Commits

Author SHA1 Message Date
Jeffrey Griffin
9c5d46e72a add std feature for no-std compatibility 2020-01-17 14:57:06 -08:00
Dan Burkert
9ec3614580 release 0.6.1 2020-01-16 09:29:57 -08:00
Dan Burkert
04091d3e74 apply recursion limit when skipping fields
Fixes #267
2020-01-16 09:29:05 -08:00
koushiro
221ebbf5e6 Update outdated dependencies
Signed-off-by: koushiro <koushiro.cqx@gmail.com>
2020-01-16 08:38:03 -08:00
Dan Burkert
a8ed170657 publish script bugfixes 2020-01-12 17:13:58 -08:00
Dan Burkert
4b1914fe68 release 0.6.0 2020-01-12 16:20:26 -08:00
Dan Burkert
6cc470208e Add prepare-release.sh, publish-release.sh scripts
Automates preparing and publishing a new release of the prost crates.
2020-01-12 16:19:49 -08:00
Dan Burkert
420e032fe7 Reintroduce unsafe in encoding::string::merge
3ab36fda9a1788e0 introduced a ~3% regression on some decoding
benchmarks. My best guess is that swapping the value temporarily with a
new string is not being optimized well. This commit reverts to using
String::as_mut_vec, but carefully clears the string on decode failure.
It also fixes up the associated regression test, which wasn't properly
covering the malformed UTF-8 case.

    // Before #194
    dataset/google_message3_2/decode
                            time:   [219.45 ms 219.48 ms 219.51 ms]
                            thrpt:  [458.64 MiB/s 458.72 MiB/s 458.78 MiB/s]

    // #194
    dataset/google_message3_2/decode
                            time:   [226.08 ms 226.11 ms 226.18 ms]
                            thrpt:  [445.12 MiB/s 445.25 MiB/s 445.32 MiB/s]

    // This commit
    dataset/google_message3_2/decode
                            time:   [219.05 ms 219.76 ms 221.78 ms]
                            thrpt:  [453.94 MiB/s 458.12 MiB/s 459.61 MiB/s]
2020-01-12 12:11:47 -08:00
Dan Burkert
51ed67d9c7 remove deprecated error description methods 2020-01-11 23:24:27 -08:00
Dan Burkert
59c11cab04
Bump packaged Protobuf to 3.11.2 (#263)
The new conformance test cases caught some edge case bugs related to
decoding messages with repeated instances of scalar strings, bytes, and
oneofs.
2020-01-11 22:23:14 -08:00
Dan Burkert
7e0070f140 More fallible Try to TryFrom conversions
This is a breaking change to `prost-types`.
2020-01-11 16:57:01 -08:00
Nicolas Polomack
20684a721a Move from failure to anyhow in prost-derive 2020-01-11 14:07:05 -08:00
Lucio Franco
ea788ba8de Remove bytes depdency requirement from the readme 2020-01-11 13:10:15 -08:00
Dan Burkert
8c0c18031f fail gracefully on missing package specifier 2020-01-11 12:22:11 -08:00
koushiro
91f4ff6f8e Use std::convert::TryFrom
Signed-off-by: koushiro <koushiro.cqx@gmail.com>
2020-01-11 11:52:46 -08:00
Tim Hutt
8c6474ea48 Don't write prost-build output if its content is unchanged
This means things like cargo-watch work better. Fixes #231.
2020-01-11 11:47:16 -08:00
Stephan Wolski
2f7602b092 fix compilation error for types named "Option" with a repeated field 2020-01-11 11:25:07 -08:00
Dan Burkert
ce5f6e5d67 rustfmt 2020-01-11 11:25:07 -08:00
Erich Gubler
5e2bc154fa Update to syn/quote/proc-macro2 1.0 2020-01-11 10:44:56 -08:00
Lucio Franco
067e259518 Update derive to not require bytes 2020-01-11 10:06:43 -08:00
Joel Gallant
8df3a14e8c Add a more detailed error message for missing package specifier 2019-12-14 11:15:39 +13:00
Dan Burkert
dfe51e94e9
bump deps (except syn/quote/proc-macro2) (#252)
* bump deps (except syn/quote/proc-macro2)

* fix throughput casts in varint benchmark
2019-12-14 11:15:15 +13:00
Jay Oster
cb79d3fc30 Add ServiceGenerator::finalize_package
Update finalize_package to provide the package name, and do not call on packages without services

Add tests for ServiceGenerator::finalize_package()
2019-12-13 19:42:58 +13:00
Lucio Franco
ece64fa021 Update to bytes 0.5 2019-12-13 18:09:52 +13:00
M@ Dunlap
b709729c89 add docstrings for generated getters and setters
improve getter/setter method docs
2019-12-13 18:02:13 +13:00
Dan Burkert
1efcabfe7b prost-derive: remove unnecessary dummy consts
They were inhibiting rustdoc from generating code for nested impl
blocks, and are no longer necessary since the new 2018 edition fully
qualified imports.
2019-12-13 13:59:31 +13:00
Dan Burkert
5c2cfe1685 bump MSRV to 1.34
miniz_oxide, a transitive dependency, forces this bump.
2019-12-13 11:14:07 +13:00
Dan Burkert
fa14e72d18 Rework benchmarks 2019-12-13 11:14:07 +13:00
55 changed files with 1335 additions and 865 deletions

View File

@ -1,13 +1,6 @@
language: rust
dist: xenial
# Even though this build script doesn't use sudo, we enable it in order to get
# more memory. Previously on the container infrastructure the OOM killer would
# come in and kill either the GCC processes compiling protobuf, or rustc. See
# https://github.com/travis-ci/travis-ci/issues/7427 for the source of this
# workaround.
sudo: required
cache: cargo
os:
@ -15,7 +8,7 @@ os:
- osx
rust:
- 1.32.0
- 1.39.0
- nightly
script:
@ -24,3 +17,13 @@ script:
- if [[ $TRAVIS_RUST_VERSION = nightly* ]]; then
cargo bench --verbose --no-run;
fi
addons:
homebrew:
packages:
- cmake
- ninja
apt:
packages:
- cmake
- ninja-build

View File

@ -1,7 +1,6 @@
[package]
name = "prost"
# NB: When modifying, also modify html_root_url in lib.rs
version = "0.5.0"
version = "0.6.1"
authors = ["Dan Burkert <dan@danburkert.com>"]
license = "Apache-2.0"
repository = "https://github.com/danburkert/prost"
@ -32,25 +31,29 @@ exclude = [
"fuzz",
]
[lib]
# https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
bench = false
[features]
default = ["prost-derive"]
default = ["prost-derive", "std"]
no-recursion-limit = []
std = []
[dependencies]
bytes = "0.4.7"
prost-derive = { version = "0.5.0", path = "prost-derive", optional = true }
bytes = { version = "0.5", default-features = false }
prost-derive = { version = "0.6.1", path = "prost-derive", optional = true }
[dev-dependencies]
criterion = "0.2"
env_logger = { version = "0.6", default-features = false }
failure = "0.1"
criterion = "0.3"
env_logger = { version = "0.7", default-features = false }
log = "0.4"
protobuf = { version = "0", path = "protobuf" }
quickcheck = "0.8"
quickcheck = "0.9"
rand = "0.7"
[profile.bench]
debug = true
[[bench]]
name = "varint"
[[bench]]
name = "benchmark"
harness = false

View File

@ -2,6 +2,7 @@
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/24rpba3x2vqe8lje/branch/master?svg=true)](https://ci.appveyor.com/project/danburkert/prost/branch/master)
[![Documentation](https://docs.rs/prost/badge.svg)](https://docs.rs/prost/)
[![Crate](https://img.shields.io/crates/v/prost.svg)](https://crates.io/crates/prost)
[![Dependency Status](https://deps.rs/repo/github/danburkert/prost/status.svg)](https://deps.rs/repo/github/danburkert/prost)
# *PROST!*
@ -18,22 +19,20 @@ Compared to other Protocol Buffers implementations, `prost`
and deserialized by adding attributes.
* Uses the [`bytes::{Buf, BufMut}`](https://github.com/carllerche/bytes)
abstractions for serialization instead of `std::io::{Read, Write}`.
* Respects the Protobuf `package` declaration when organizing generated code
* Respects the Protobuf `package` specifier when organizing generated code
into Rust modules.
* Preserves unknown enum values during deserialization.
* Does not include support for runtime reflection or message descriptors.
## Using `prost` in a Cargo Project
First, add `prost` and its public dependencies to your `Cargo.toml` (see
[crates.io](https://crates.io/crates/prost) for the current versions):
First, add `prost` and its public dependencies to your `Cargo.toml`:
```
[dependencies]
prost = <prost-version>
bytes = <bytes-version>
prost = "0.6"
# Only necessary if using Protobuf well-known types:
prost-types = <prost-version>
prost-types = "0.6"
```
The recommended way to add `.proto` compilation to a Cargo project is to use the
@ -48,9 +47,11 @@ possible.
### Packages
Currently, all `.proto` files used with `prost` must contain a `package`
declaration. `prost` will translate the Protobuf package into a Rust module.
For example, given the `package` declaration:
All `.proto` files used with `prost` must contain a
[`package` specifier][package]. `prost` will translate the Protobuf package into
a Rust module. For example, given the `package` specifier:
[package]: https://developers.google.com/protocol-buffers/docs/proto#packages
```proto
package foo.bar;

View File

@ -1,108 +0,0 @@
use std::fs::File;
use std::io::Read;
use std::result;
use criterion::{Benchmark, Criterion, Throughput};
use failure::bail;
use prost::encoding::DecodeContext;
use prost::Message;
use protobuf::benchmarks::{google_message3, google_message4, proto2, proto3, BenchmarkDataset};
type Result = result::Result<(), failure::Error>;
fn benchmark_dataset<M>(criterion: &mut Criterion, dataset: BenchmarkDataset) -> Result
where
M: prost::Message + Default + 'static,
{
let payload_len = dataset.payload.iter().map(Vec::len).sum::<usize>();
let messages = dataset
.payload
.iter()
.map(|buf| M::decode(buf))
.collect::<result::Result<Vec<_>, _>>()?;
let encoded_len = messages
.iter()
.map(|message| message.encoded_len())
.sum::<usize>();
let mut buf = Vec::with_capacity(encoded_len);
let encode = Benchmark::new("encode", move |b| {
b.iter(|| {
buf.clear();
for message in &messages {
message.encode(&mut buf).unwrap();
}
criterion::black_box(&buf);
})
})
.throughput(Throughput::Bytes(encoded_len as u32));
let payload = dataset.payload.clone();
let decode = Benchmark::new("decode", move |b| {
b.iter(|| {
for buf in &payload {
criterion::black_box(M::decode(buf).unwrap());
}
})
})
.throughput(Throughput::Bytes(payload_len as u32));
let payload = dataset.payload.clone();
let merge = Benchmark::new("merge", move |b| {
let mut message = M::default();
b.iter(|| {
for buf in &payload {
message.clear();
message.merge(buf).unwrap();
criterion::black_box(&message);
}
})
})
.throughput(Throughput::Bytes(payload_len as u32));
criterion
.bench(&dataset.name, encode)
.bench(&dataset.name, decode)
.bench(&dataset.name, merge);
Ok(())
}
fn main() -> Result {
let mut criterion = Criterion::default()
.configure_from_args()
// TODO(bheisler/criterion.rs#295): switch to a time-based limit.
.sample_size(10);
for dataset in protobuf::benchmarks::datasets() {
let dataset = {
let mut f = File::open(dataset)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
protobuf::benchmarks::BenchmarkDataset::decode(buf)?
};
match dataset.message_name.as_str() {
"benchmarks.proto2.GoogleMessage1" => {
benchmark_dataset::<proto2::GoogleMessage1>(&mut criterion, dataset)?
}
"benchmarks.proto3.GoogleMessage1" => {
benchmark_dataset::<proto3::GoogleMessage1>(&mut criterion, dataset)?
}
"benchmarks.proto2.GoogleMessage2" => {
benchmark_dataset::<proto2::GoogleMessage2>(&mut criterion, dataset)?
}
"benchmarks.google_message3.GoogleMessage3" => {
benchmark_dataset::<google_message3::GoogleMessage3>(&mut criterion, dataset)?
}
"benchmarks.google_message4.GoogleMessage4" => {
benchmark_dataset::<google_message4::GoogleMessage4>(&mut criterion, dataset)?
}
_ => bail!("unknown dataset message type: {}", dataset.message_name),
}
}
criterion.final_summary();
Ok(())
}

View File

@ -1,108 +1,95 @@
#![feature(test)]
use std::mem;
extern crate test;
use bytes::IntoBuf;
use bytes::Buf;
use criterion::{Benchmark, Criterion, Throughput};
use prost::encoding::{decode_varint, encode_varint, encoded_len_varint};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
macro_rules! varint_bench {
($encode_name:ident, $decode_name:ident, $encoded_len_name: ident, $encode:expr) => {
#[bench]
fn $encode_name(b: &mut test::Bencher) {
let mut buf = Vec::with_capacity(100 * 10);
b.iter(|| {
buf.clear();
$encode(&mut buf);
test::black_box(&buf[..]);
});
b.bytes = 100 * 8;
}
#[bench]
fn $decode_name(b: &mut test::Bencher) {
let mut buf = Vec::with_capacity(100 * 10);
$encode(&mut buf);
let buf = &buf[..];
fn benchmark_varint(criterion: &mut Criterion, name: &str, mut values: Vec<u64>) {
// Shuffle the values in a stable order.
values.shuffle(&mut StdRng::seed_from_u64(0));
let mut values = [0u64; 100];
let encoded_len = values
.iter()
.cloned()
.map(encoded_len_varint)
.sum::<usize>() as u64;
let decoded_len = (values.len() * mem::size_of::<u64>()) as u64;
b.iter(|| {
let mut buf = buf.into_buf();
for i in 0..100 {
values[i] = decode_varint(&mut buf).unwrap();
}
test::black_box(&values[..]);
});
b.bytes = 100 * 8;
}
#[bench]
fn $encoded_len_name(b: &mut test::Bencher) {
let mut values = [0u64; 100];
{
let mut buf = Vec::with_capacity(100 * 10);
$encode(&mut buf);
let mut buf = (&buf[..]).into_buf();
for i in 0..100 {
values[i] = decode_varint(&mut buf).unwrap();
}
let encode_values = values.clone();
let encode = Benchmark::new("encode", move |b| {
let mut buf = Vec::<u8>::with_capacity(encode_values.len() * 10);
b.iter(|| {
buf.clear();
for &value in &encode_values {
encode_varint(value, &mut buf);
}
criterion::black_box(&buf);
})
})
.throughput(Throughput::Bytes(encoded_len));
b.iter(|| {
let mut sum = 0;
for &value in values.iter() {
sum += encoded_len_varint(value);
}
test::black_box(sum);
});
b.bytes = 100 * 8;
let mut decode_values = values.clone();
let decode = Benchmark::new("decode", move |b| {
let mut buf = Vec::with_capacity(decode_values.len() * 10);
for &value in &decode_values {
encode_varint(value, &mut buf);
}
};
b.iter(|| {
decode_values.clear();
let mut buf = &mut buf.as_slice();
while buf.has_remaining() {
let value = decode_varint(&mut buf).unwrap();
decode_values.push(value);
}
criterion::black_box(&decode_values);
})
})
.throughput(Throughput::Bytes(decoded_len));
let encoded_len_values = values.clone();
let encoded_len = Benchmark::new("encoded_len", move |b| {
b.iter(|| {
let mut sum = 0;
for &value in &encoded_len_values {
sum += encoded_len_varint(value);
}
criterion::black_box(sum);
})
})
.throughput(Throughput::Bytes(decoded_len));
let name = format!("varint/{}", name);
criterion
.bench(&name, encode)
.bench(&name, decode)
.bench(&name, encoded_len);
}
// Benchmark encoding and decoding 100 varints of mixed width (average 5.5 bytes).
varint_bench!(
encode_varint_mixed,
decode_varint_mixed,
encoded_len_varint_mixed,
|ref mut buf| for width in 0..10 {
let exponent = width * 7;
for offset in 0..10 {
encode_varint(offset + (1 << exponent), buf);
}
}
);
fn main() {
let mut criterion = Criterion::default().configure_from_args();
// Benchmark encoding and decoding 100 small (1 byte) varints.
varint_bench!(
encode_varint_small,
decode_varint_small,
encoded_len_varint_small,
|ref mut buf| for value in 0..100 {
encode_varint(value, buf);
}
);
// Benchmark encoding and decoding 100 small (1 byte) varints.
benchmark_varint(&mut criterion, "small", (0..100).collect());
// Benchmark encoding and decoding 100 medium (5 byte) varints.
varint_bench!(
encode_varint_medium,
decode_varint_medium,
encoded_len_varint_medium,
|ref mut buf| {
let start = 1 << 28;
for value in start..start + 100 {
encode_varint(value, buf);
}
}
);
// Benchmark encoding and decoding 100 medium (5 byte) varints.
benchmark_varint(&mut criterion, "medium", (1 << 28..).take(100).collect());
// Benchmark encoding and decoding 100 large (10 byte) varints.
varint_bench!(
encode_varint_large,
decode_varint_large,
encoded_len_varint_large,
|ref mut buf| {
let start = 1 << 63;
for value in start..start + 100 {
encode_varint(value, buf);
}
}
);
// Benchmark encoding and decoding 100 large (10 byte) varints.
benchmark_varint(&mut criterion, "large", (1 << 63..).take(100).collect());
// Benchmark encoding and decoding 100 varints of mixed width (average 5.5 bytes).
benchmark_varint(
&mut criterion,
"mixed",
(0..10)
.flat_map(move |width| {
let exponent = width * 7;
(0..10).map(move |offset| offset + (1 << exponent))
})
.collect(),
);
criterion.final_summary();
}

View File

@ -6,9 +6,9 @@ publish = false
edition = "2018"
[dependencies]
bytes = "0.4.7"
env_logger = { version = "0.6", default-features = false }
log = "0.3"
bytes = "0.5"
env_logger = { version = "0.7", default-features = false }
log = "0.4"
prost = { path = ".." }
protobuf = { path = "../protobuf" }
tests = { path = "../tests" }

View File

@ -1,6 +1,6 @@
use std::io::{self, Read, Write};
use bytes::{ByteOrder, LittleEndian};
use bytes::{Buf, BufMut};
use prost::Message;
use protobuf::conformance::{
@ -10,24 +10,24 @@ use protobuf::test_messages::proto2::TestAllTypesProto2;
use protobuf::test_messages::proto3::TestAllTypesProto3;
use tests::{roundtrip, RoundtripResult};
fn main() {
fn main() -> io::Result<()> {
env_logger::init();
let mut bytes = Vec::new();
loop {
bytes.resize(4, 0);
if io::stdin().read_exact(&mut bytes[..]).is_err() {
if io::stdin().read_exact(&mut *bytes).is_err() {
// No more test cases.
break;
return Ok(());
}
let len = LittleEndian::read_u32(&bytes[..]) as usize;
let len = bytes.as_slice().get_u32_le() as usize;
bytes.resize(len, 0);
io::stdin().read_exact(&mut bytes[..]).unwrap();
io::stdin().read_exact(&mut *bytes)?;
let result = match ConformanceRequest::decode(&bytes) {
let result = match ConformanceRequest::decode(&*bytes) {
Ok(request) => handle_request(request),
Err(error) => conformance_response::Result::ParseError(format!("{:?}", error)),
};
@ -36,15 +36,14 @@ fn main() {
response.result = Some(result);
let len = response.encoded_len();
bytes.resize(4, 0);
LittleEndian::write_u32(&mut bytes[..4], len as u32);
response.encode(&mut bytes).unwrap();
bytes.clear();
bytes.put_u32_le(len as u32);
response.encode(&mut bytes)?;
assert_eq!(len + 4, bytes.len());
let mut stdout = io::stdout();
stdout.lock().write_all(&bytes).unwrap();
stdout.flush().unwrap();
stdout.lock().write_all(&bytes)?;
stdout.flush()?;
}
}

47
prepare-release.sh Executable file
View File

@ -0,0 +1,47 @@
#!/bin/bash
# Script which automates modifying source version fields, and creating a release
# commit and tag. The commit and tag are not automatically pushed, nor are the
# crates published (see publish-release.sh).
set -ex
if [ "$#" -ne 1 ]
then
echo "Usage: $0 <version>"
exit 1
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
VERSION="$1"
MINOR="$( echo ${VERSION} | cut -d\. -f1-2 )"
VERSION_MATCHER="([a-z0-9\\.-]+)"
PROST_CRATE_MATCHER="(prost|prost-[a-z]+)"
# Update the README.md.
sed -i -E "s/${PROST_CRATE_MATCHER} = \"${VERSION_MATCHER}\"/\1 = \"${MINOR}\"/" "$DIR/README.md"
# Update html_root_url attributes.
sed -i -E "s~html_root_url = \"https://docs\.rs/${PROST_CRATE_MATCHER}/$VERSION_MATCHER\"~html_root_url = \"https://docs.rs/\1/${VERSION}\"~" \
"$DIR/src/lib.rs" \
"$DIR/prost-derive/src/lib.rs" \
"$DIR/prost-build/src/lib.rs" \
"$DIR/prost-types/src/lib.rs"
# Update Cargo.toml version fields.
sed -i -E "s/^version = \"${VERSION_MATCHER}\"$/version = \"${VERSION}\"/" \
"$DIR/Cargo.toml" \
"$DIR/prost-derive/Cargo.toml" \
"$DIR/prost-build/Cargo.toml" \
"$DIR/prost-types/Cargo.toml"
# Update Cargo.toml dependency versions.
sed -i -E "s/^${PROST_CRATE_MATCHER} = \{ version = \"${VERSION_MATCHER}\"/\1 = { version = \"${VERSION}\"/" \
"$DIR/Cargo.toml" \
"$DIR/prost-derive/Cargo.toml" \
"$DIR/prost-build/Cargo.toml" \
"$DIR/prost-types/Cargo.toml"
git commit -a -m "release ${VERSION}"
git tag -a "v${VERSION}" -m "release ${VERSION}"

View File

@ -1,7 +1,6 @@
[package]
name = "prost-build"
# NB: When modifying, also modify html_root_url in lib.rs
version = "0.5.0"
version = "0.6.1"
authors = ["Dan Burkert <dan@danburkert.com>"]
license = "Apache-2.0"
repository = "https://github.com/danburkert/prost"
@ -11,18 +10,18 @@ description = "A Protocol Buffers implementation for the Rust Language."
edition = "2018"
[dependencies]
bytes = "0.4.7"
bytes = "0.5"
heck = "0.3"
itertools = "0.8"
log = "0.4"
multimap = { version = "0.4", default-features = false }
petgraph = { version = "0.4", default-features = false }
prost = { version = "0.5.0", path = ".." }
prost-types = { version = "0.5.0", path = "../prost-types" }
multimap = { version = "0.8", default-features = false }
petgraph = { version = "0.5", default-features = false }
prost = { version = "0.6.1", path = ".." }
prost-types = { version = "0.6.1", path = "../prost-types" }
tempfile = "3"
[build-dependencies]
which = "2"
which = { version = "3", default-features = false }
[dev-dependencies]
env_logger = { version = "0.6", default-features = false }
env_logger = { version = "0.7", default-features = false }

View File

@ -0,0 +1,9 @@
syntax = "proto3";
import "types.proto";
package helloworld;
service Farewell {
rpc Goodbye (Message) returns (Response) {}
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
import "types.proto";
package helloworld;
service Greeting {
rpc Hello (Message) returns (Response) {}
}

View File

@ -1,4 +1,4 @@
#![doc(html_root_url = "https://docs.rs/prost-build/0.5.0")]
#![doc(html_root_url = "https://docs.rs/prost-build/0.6.1")]
//! `prost-build` compiles `.proto` files into Rust.
//!
@ -114,7 +114,7 @@ use std::collections::HashMap;
use std::default;
use std::env;
use std::fs;
use std::io::{Error, ErrorKind, Read, Result, Write};
use std::io::{Error, ErrorKind, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
@ -163,6 +163,16 @@ pub trait ServiceGenerator {
///
/// The default implementation is empty and does nothing.
fn finalize(&mut self, _buf: &mut String) {}
/// Finalizes the generation process for an entire protobuf package.
///
/// This differs from [`finalize`](#method.finalize) by where (and how often) it is called
/// during the service generator life cycle. This method is called once per protobuf package,
/// making it ideal for grouping services within a single package spread across multiple
/// `.proto` files.
///
/// The default implementation is empty and does nothing.
fn finalize_package(&mut self, _package: &str, _buf: &mut String) {}
}
/// Configuration options for Protobuf code generation.
@ -536,18 +546,27 @@ impl Config {
));
}
let mut buf = Vec::new();
fs::File::open(descriptor_set)?.read_to_end(&mut buf)?;
let descriptor_set = FileDescriptorSet::decode(&buf)?;
let buf = fs::read(descriptor_set)?;
let descriptor_set = FileDescriptorSet::decode(&*buf)?;
let modules = self.generate(descriptor_set.file)?;
for (module, content) in modules {
let mut filename = module.join(".");
filename.push_str(".rs");
trace!("writing: {:?}", filename);
let mut file = fs::File::create(target.join(filename))?;
file.write_all(content.as_bytes())?;
file.flush()?;
let output_path = target.join(&filename);
let previous_content = fs::read(&output_path);
if previous_content
.map(|previous_content| previous_content == content.as_bytes())
.unwrap_or(false)
{
trace!("unchanged: {:?}", filename);
} else {
trace!("writing: {:?}", filename);
fs::write(output_path, content)?;
}
}
Ok(())
@ -555,16 +574,32 @@ impl Config {
fn generate(&mut self, files: Vec<FileDescriptorProto>) -> Result<HashMap<Module, String>> {
let mut modules = HashMap::new();
let mut packages = HashMap::new();
let message_graph = MessageGraph::new(&files);
let message_graph = MessageGraph::new(&files)
.map_err(|error| Error::new(ErrorKind::InvalidInput, error))?;
let extern_paths = ExternPaths::new(&self.extern_paths, self.prost_types)
.map_err(|error| Error::new(ErrorKind::InvalidInput, error))?;
for file in files {
let module = self.module(&file);
// Only record packages that have services
if !file.service.is_empty() {
packages.insert(module.clone(), file.package().to_string());
}
let mut buf = modules.entry(module).or_insert_with(String::new);
CodeGenerator::generate(self, &message_graph, &extern_paths, file, &mut buf);
}
if let Some(ref mut service_generator) = self.service_generator {
for (module, package) in packages {
let buf = modules.get_mut(&module).unwrap();
service_generator.finalize_package(&package, buf);
}
}
Ok(modules)
}
@ -615,6 +650,7 @@ impl default::Default for Config {
/// - Failure to locate or download `protoc`.
/// - Failure to parse the `.proto`s.
/// - Failure to locate an imported `.proto`.
/// - Failure to compile a `.proto` without a [package specifier][4].
///
/// It's expected that this function call be `unwrap`ed in a `build.rs`; there is typically no
/// reason to gracefully recover from errors during a build.
@ -631,6 +667,7 @@ impl default::Default for Config {
/// [1]: https://doc.rust-lang.org/std/macro.include.html
/// [2]: http://doc.crates.io/build-script.html#case-study-code-generation
/// [3]: https://developers.google.com/protocol-buffers/docs/proto3#importing-definitions
/// [4]: https://developers.google.com/protocol-buffers/docs/proto#packages
pub fn compile_protos<P>(protos: &[P], includes: &[P]) -> Result<()>
where
P: AsRef<Path>,
@ -652,6 +689,8 @@ pub fn protoc_include() -> &'static Path {
mod tests {
use super::*;
use env_logger;
use std::cell::RefCell;
use std::rc::Rc;
/// An example service generator that generates a trait with methods corresponding to the
/// service methods.
@ -680,12 +719,66 @@ mod tests {
}
}
/// Implements `ServiceGenerator` and provides some state for assertions.
struct MockServiceGenerator {
state: Rc<RefCell<MockState>>,
}
/// Holds state for `MockServiceGenerator`
#[derive(Default)]
struct MockState {
service_names: Vec<String>,
package_names: Vec<String>,
finalized: u32,
}
impl MockServiceGenerator {
fn new(state: Rc<RefCell<MockState>>) -> Self {
Self { state }
}
}
impl ServiceGenerator for MockServiceGenerator {
fn generate(&mut self, service: Service, _buf: &mut String) {
let mut state = self.state.borrow_mut();
state.service_names.push(service.name.clone());
}
fn finalize(&mut self, _buf: &mut String) {
let mut state = self.state.borrow_mut();
state.finalized += 1;
}
fn finalize_package(&mut self, package: &str, _buf: &mut String) {
let mut state = self.state.borrow_mut();
state.package_names.push(package.to_string());
}
}
#[test]
fn smoke_test() {
let _ = env_logger::init();
let _ = env_logger::try_init();
Config::new()
.service_generator(Box::new(ServiceTraitGenerator))
.compile_protos(&["src/smoke_test.proto"], &["src"])
.unwrap();
}
#[test]
fn finalize_package() {
let _ = env_logger::try_init();
let state = Rc::new(RefCell::new(MockState::default()));
let gen = MockServiceGenerator::new(Rc::clone(&state));
Config::new()
.service_generator(Box::new(gen))
.compile_protos(&["src/hello.proto", "src/goodbye.proto"], &["src"])
.unwrap();
let state = state.borrow();
assert_eq!(&state.service_names, &["Greeting", "Farewell"]);
assert_eq!(&state.package_names, &["helloworld"]);
assert_eq!(state.finalized, 3);
}
}

View File

@ -15,20 +15,30 @@ pub struct MessageGraph {
}
impl MessageGraph {
pub fn new(files: &[FileDescriptorProto]) -> MessageGraph {
pub fn new(files: &[FileDescriptorProto]) -> Result<MessageGraph, String> {
let mut msg_graph = MessageGraph {
index: HashMap::new(),
graph: Graph::new(),
};
for file in files {
let package = format!(".{}", file.package.as_ref().expect("expected a package"));
let package = format!(
".{}",
file.package.as_ref().ok_or_else(|| {
format!(
"prost requires a package specifier in all .proto files \
(https://developers.google.com/protocol-buffers/docs/proto#packages); \
file with missing package specifier: {}",
file.name.as_ref().map_or("(unknown)", String::as_ref),
)
})?,
);
for msg in &file.message_type {
msg_graph.add_message(&package, msg);
}
}
msg_graph
Ok(msg_graph)
}
fn get_or_insert_index(&mut self, msg_name: String) -> NodeIndex {

View File

@ -0,0 +1,11 @@
syntax = "proto3";
package helloworld;
message Message {
string say = 1;
}
message Response {
string say = 1;
}

View File

@ -1,11 +0,0 @@
`LICENSE` was taken from https://github.com/google/protobuf/archive/v3.7.1.zip.
The `protoc-*` and `protoc-*.exe` binaries here were taken from:
* https://github.com/google/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-aarch_64.zip
* https://github.com/google/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_32.zip
* https://github.com/google/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_64.zip
* https://github.com/google/protobuf/releases/download/v3.7.1/protoc-3.7.1-osx-x86_64.zip
* https://github.com/google/protobuf/releases/download/v3.7.1/protoc-3.7.1-win32.zip
The `include` directory's contents were taken from the linux-x86_64 package.

View File

@ -45,6 +45,7 @@
// flag "--${NAME}_out" is passed to protoc.
syntax = "proto2";
package google.protobuf.compiler;
option java_package = "com.google.protobuf.compiler";
option java_outer_classname = "PluginProtos";

View File

@ -40,6 +40,7 @@
syntax = "proto2";
package google.protobuf;
option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor";
option java_package = "com.google.protobuf";
option java_outer_classname = "DescriptorProtos";
@ -59,8 +60,8 @@ message FileDescriptorSet {
// Describes a complete .proto file.
message FileDescriptorProto {
optional string name = 1; // file name, relative to root of source tree
optional string package = 2; // e.g. "foo", "foo.bar", etc.
optional string name = 1; // file name, relative to root of source tree
optional string package = 2; // e.g. "foo", "foo.bar", etc.
// Names of files imported by this file.
repeated string dependency = 3;
@ -100,8 +101,8 @@ message DescriptorProto {
repeated EnumDescriptorProto enum_type = 4;
message ExtensionRange {
optional int32 start = 1;
optional int32 end = 2;
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Exclusive.
optional ExtensionRangeOptions options = 3;
}
@ -115,8 +116,8 @@ message DescriptorProto {
// fields or extension ranges in the same message. Reserved ranges may
// not overlap.
message ReservedRange {
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Exclusive.
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Exclusive.
}
repeated ReservedRange reserved_range = 9;
// Reserved field names, which may not be used by fields in the same message.
@ -137,42 +138,42 @@ message FieldDescriptorProto {
enum Type {
// 0 is reserved for errors.
// Order is weird for historical reasons.
TYPE_DOUBLE = 1;
TYPE_FLOAT = 2;
TYPE_DOUBLE = 1;
TYPE_FLOAT = 2;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
// negative values are likely.
TYPE_INT64 = 3;
TYPE_UINT64 = 4;
TYPE_INT64 = 3;
TYPE_UINT64 = 4;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
// negative values are likely.
TYPE_INT32 = 5;
TYPE_FIXED64 = 6;
TYPE_FIXED32 = 7;
TYPE_BOOL = 8;
TYPE_STRING = 9;
TYPE_INT32 = 5;
TYPE_FIXED64 = 6;
TYPE_FIXED32 = 7;
TYPE_BOOL = 8;
TYPE_STRING = 9;
// Tag-delimited aggregate.
// Group type is deprecated and not supported in proto3. However, Proto3
// implementations should still be able to parse the group wire format and
// treat group fields as unknown fields.
TYPE_GROUP = 10;
TYPE_MESSAGE = 11; // Length-delimited aggregate.
TYPE_GROUP = 10;
TYPE_MESSAGE = 11; // Length-delimited aggregate.
// New in version 2.
TYPE_BYTES = 12;
TYPE_UINT32 = 13;
TYPE_ENUM = 14;
TYPE_SFIXED32 = 15;
TYPE_SFIXED64 = 16;
TYPE_SINT32 = 17; // Uses ZigZag encoding.
TYPE_SINT64 = 18; // Uses ZigZag encoding.
};
TYPE_BYTES = 12;
TYPE_UINT32 = 13;
TYPE_ENUM = 14;
TYPE_SFIXED32 = 15;
TYPE_SFIXED64 = 16;
TYPE_SINT32 = 17; // Uses ZigZag encoding.
TYPE_SINT64 = 18; // Uses ZigZag encoding.
}
enum Label {
// 0 is reserved for errors
LABEL_OPTIONAL = 1;
LABEL_REQUIRED = 2;
LABEL_REPEATED = 3;
};
LABEL_OPTIONAL = 1;
LABEL_REQUIRED = 2;
LABEL_REPEATED = 3;
}
optional string name = 1;
optional int32 number = 3;
@ -234,8 +235,8 @@ message EnumDescriptorProto {
// is inclusive such that it can appropriately represent the entire int32
// domain.
message EnumReservedRange {
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Inclusive.
optional int32 start = 1; // Inclusive.
optional int32 end = 2; // Inclusive.
}
// Range of reserved numeric values. Reserved numeric values may not be used
@ -276,9 +277,9 @@ message MethodDescriptorProto {
optional MethodOptions options = 4;
// Identifies if client streams multiple client messages
optional bool client_streaming = 5 [default=false];
optional bool client_streaming = 5 [default = false];
// Identifies if server streams multiple server messages
optional bool server_streaming = 6 [default=false];
optional bool server_streaming = 6 [default = false];
}
@ -314,7 +315,6 @@ message MethodDescriptorProto {
// If this turns out to be popular, a web service will be set up
// to automatically assign option numbers.
message FileOptions {
// Sets the Java package where classes generated from this .proto will be
@ -337,7 +337,7 @@ message FileOptions {
// named by java_outer_classname. However, the outer class will still be
// generated to contain the file's getDescriptor() method as well as any
// top-level extensions defined in the file.
optional bool java_multiple_files = 10 [default=false];
optional bool java_multiple_files = 10 [default = false];
// This option does nothing.
optional bool java_generate_equals_and_hash = 20 [deprecated=true];
@ -348,17 +348,17 @@ message FileOptions {
// Message reflection will do the same.
// However, an extension field still accepts non-UTF-8 byte sequences.
// This option has no effect on when used with the lite runtime.
optional bool java_string_check_utf8 = 27 [default=false];
optional bool java_string_check_utf8 = 27 [default = false];
// Generated classes can be optimized for speed or code size.
enum OptimizeMode {
SPEED = 1; // Generate complete code for parsing, serialization,
// etc.
CODE_SIZE = 2; // Use ReflectionOps to implement these methods.
LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
SPEED = 1; // Generate complete code for parsing, serialization,
// etc.
CODE_SIZE = 2; // Use ReflectionOps to implement these methods.
LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
}
optional OptimizeMode optimize_for = 9 [default=SPEED];
optional OptimizeMode optimize_for = 9 [default = SPEED];
// Sets the Go package where structs generated from this .proto will be
// placed. If omitted, the Go package will be derived from the following:
@ -369,6 +369,7 @@ message FileOptions {
// Should generic services be generated in each language? "Generic" services
// are not specific to any particular RPC system. They are generated by the
// main code generators in each language (without additional plugins).
@ -379,20 +380,20 @@ message FileOptions {
// that generate code specific to your particular RPC system. Therefore,
// these default to false. Old code which depends on generic services should
// explicitly set them to true.
optional bool cc_generic_services = 16 [default=false];
optional bool java_generic_services = 17 [default=false];
optional bool py_generic_services = 18 [default=false];
optional bool php_generic_services = 42 [default=false];
optional bool cc_generic_services = 16 [default = false];
optional bool java_generic_services = 17 [default = false];
optional bool py_generic_services = 18 [default = false];
optional bool php_generic_services = 42 [default = false];
// Is this file deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for everything in the file, or it will be completely ignored; in the very
// least, this is a formalization for deprecating files.
optional bool deprecated = 23 [default=false];
optional bool deprecated = 23 [default = false];
// Enables the use of arenas for the proto messages in this file. This applies
// only to generated classes for C++.
optional bool cc_enable_arenas = 31 [default=false];
optional bool cc_enable_arenas = 31 [default = false];
// Sets the objective c class prefix which is prepended to all objective c
@ -417,10 +418,9 @@ message FileOptions {
// determining the namespace.
optional string php_namespace = 41;
// Use this option to change the namespace of php generated metadata classes.
// Default is empty. When this option is empty, the proto file name will be used
// for determining the namespace.
// Default is empty. When this option is empty, the proto file name will be
// used for determining the namespace.
optional string php_metadata_namespace = 44;
// Use this option to change the package of ruby generated classes. Default
@ -428,6 +428,7 @@ message FileOptions {
// determining the ruby package.
optional string ruby_package = 45;
// The parser stores options it doesn't recognize here.
// See the documentation for the "Options" section above.
repeated UninterpretedOption uninterpreted_option = 999;
@ -458,18 +459,18 @@ message MessageOptions {
//
// Because this is an option, the above two restrictions are not enforced by
// the protocol compiler.
optional bool message_set_wire_format = 1 [default=false];
optional bool message_set_wire_format = 1 [default = false];
// Disables the generation of the standard "descriptor()" accessor, which can
// conflict with a field of the same name. This is meant to make migration
// from proto1 easier; new code should avoid fields named "descriptor".
optional bool no_standard_descriptor_accessor = 2 [default=false];
optional bool no_standard_descriptor_accessor = 2 [default = false];
// Is this message deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for the message, or it will be completely ignored; in the very least,
// this is a formalization for deprecating messages.
optional bool deprecated = 3 [default=false];
optional bool deprecated = 3 [default = false];
// Whether the message is an automatically generated map entry type for the
// maps field.
@ -486,7 +487,7 @@ message MessageOptions {
//
// Implementations may choose not to generate the map_entry=true message, but
// use a native map in the target language to hold the keys and values.
// The reflection APIs in such implementions still need to work as
// The reflection APIs in such implementations still need to work as
// if the field is a repeated message field.
//
// NOTE: Do not set the option in .proto files. Always use the maps syntax
@ -497,6 +498,7 @@ message MessageOptions {
reserved 8; // javalite_serializable
reserved 9; // javanano_as_lite
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
@ -576,16 +578,16 @@ message FieldOptions {
// implementation must either *always* check its required fields, or *never*
// check its required fields, regardless of whether or not the message has
// been parsed.
optional bool lazy = 5 [default=false];
optional bool lazy = 5 [default = false];
// Is this field deprecated?
// Depending on the target platform, this can emit Deprecated annotations
// for accessors, or it will be completely ignored; in the very least, this
// is a formalization for deprecating fields.
optional bool deprecated = 3 [default=false];
optional bool deprecated = 3 [default = false];
// For Google-internal migration only. Do not use.
optional bool weak = 10 [default=false];
optional bool weak = 10 [default = false];
// The parser stores options it doesn't recognize here. See above.
@ -615,7 +617,7 @@ message EnumOptions {
// Depending on the target platform, this can emit Deprecated annotations
// for the enum, or it will be completely ignored; in the very least, this
// is a formalization for deprecating enums.
optional bool deprecated = 3 [default=false];
optional bool deprecated = 3 [default = false];
reserved 5; // javanano_as_lite
@ -631,7 +633,7 @@ message EnumValueOptions {
// Depending on the target platform, this can emit Deprecated annotations
// for the enum value, or it will be completely ignored; in the very least,
// this is a formalization for deprecating enum values.
optional bool deprecated = 1 [default=false];
optional bool deprecated = 1 [default = false];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
@ -651,7 +653,7 @@ message ServiceOptions {
// Depending on the target platform, this can emit Deprecated annotations
// for the service, or it will be completely ignored; in the very least,
// this is a formalization for deprecating services.
optional bool deprecated = 33 [default=false];
optional bool deprecated = 33 [default = false];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
@ -671,18 +673,18 @@ message MethodOptions {
// Depending on the target platform, this can emit Deprecated annotations
// for the method, or it will be completely ignored; in the very least,
// this is a formalization for deprecating methods.
optional bool deprecated = 33 [default=false];
optional bool deprecated = 33 [default = false];
// Is this method side-effect-free (or safe in HTTP parlance), or idempotent,
// or neither? HTTP based RPC implementation may choose GET verb for safe
// methods, and PUT verb for idempotent methods instead of the default POST.
enum IdempotencyLevel {
IDEMPOTENCY_UNKNOWN = 0;
NO_SIDE_EFFECTS = 1; // implies idempotent
IDEMPOTENT = 2; // idempotent, but may have side effects
NO_SIDE_EFFECTS = 1; // implies idempotent
IDEMPOTENT = 2; // idempotent, but may have side effects
}
optional IdempotencyLevel idempotency_level =
34 [default=IDEMPOTENCY_UNKNOWN];
optional IdempotencyLevel idempotency_level = 34
[default = IDEMPOTENCY_UNKNOWN];
// The parser stores options it doesn't recognize here. See above.
repeated UninterpretedOption uninterpreted_option = 999;
@ -763,7 +765,7 @@ message SourceCodeInfo {
// beginning of the "extend" block and is shared by all extensions within
// the block.
// - Just because a location's span is a subset of some other location's span
// does not mean that it is a descendent. For example, a "group" defines
// does not mean that it is a descendant. For example, a "group" defines
// both a type and a field in a single declaration. Thus, the locations
// corresponding to the type and field and their components will overlap.
// - Code which tries to interpret locations should probably be designed to
@ -794,14 +796,14 @@ message SourceCodeInfo {
// [ 4, 3, 2, 7 ]
// this path refers to the whole field declaration (from the beginning
// of the label to the terminating semicolon).
repeated int32 path = 1 [packed=true];
repeated int32 path = 1 [packed = true];
// Always has exactly three or four elements: start line, start column,
// end line (optional, otherwise assumed same as start line), end column.
// These are packed into a single field for efficiency. Note that line
// and column numbers are zero-based -- typically you will want to add
// 1 to each before displaying to a user.
repeated int32 span = 2 [packed=true];
repeated int32 span = 2 [packed = true];
// If this SourceCodeInfo represents a complete declaration, these are any
// comments appearing before and after the declaration which appear to be
@ -866,7 +868,7 @@ message GeneratedCodeInfo {
message Annotation {
// Identifies the element in the original source .proto file. This field
// is formatted the same as SourceCodeInfo.Location.path.
repeated int32 path = 1 [packed=true];
repeated int32 path = 1 [packed = true];
// Identifies the filesystem path to the original source .proto.
optional string source_file = 2;

View File

@ -61,7 +61,7 @@ option objc_class_prefix = "GPB";
// if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1;
// duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) {
// } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1;
// duration.nanos += 1000000000;
// }
@ -101,7 +101,6 @@ option objc_class_prefix = "GPB";
//
//
message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years

View File

@ -238,7 +238,7 @@ option cc_enable_arenas = true;
//
// The implementation of any API method which has a FieldMask type field in the
// request should verify the included field paths, and return an
// `INVALID_ARGUMENT` error if any path is duplicated or unmappable.
// `INVALID_ARGUMENT` error if any path is unmappable.
message FieldMask {
// The set of field mask paths.
repeated string paths = 1;

View File

@ -40,7 +40,6 @@ option java_outer_classname = "StructProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// `Struct` represents a structured data value, consisting of fields
// which map to dynamically typed values. In some languages, `Struct`
// might be supported by a native representation. For example, in

View File

@ -113,17 +113,18 @@ option objc_class_prefix = "GPB";
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
// to this format using
// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format.
//
//
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.

View File

@ -64,44 +64,44 @@ message Field {
// Basic field types.
enum Kind {
// Field type unknown.
TYPE_UNKNOWN = 0;
TYPE_UNKNOWN = 0;
// Field type double.
TYPE_DOUBLE = 1;
TYPE_DOUBLE = 1;
// Field type float.
TYPE_FLOAT = 2;
TYPE_FLOAT = 2;
// Field type int64.
TYPE_INT64 = 3;
TYPE_INT64 = 3;
// Field type uint64.
TYPE_UINT64 = 4;
TYPE_UINT64 = 4;
// Field type int32.
TYPE_INT32 = 5;
TYPE_INT32 = 5;
// Field type fixed64.
TYPE_FIXED64 = 6;
TYPE_FIXED64 = 6;
// Field type fixed32.
TYPE_FIXED32 = 7;
TYPE_FIXED32 = 7;
// Field type bool.
TYPE_BOOL = 8;
TYPE_BOOL = 8;
// Field type string.
TYPE_STRING = 9;
TYPE_STRING = 9;
// Field type group. Proto2 syntax only, and deprecated.
TYPE_GROUP = 10;
TYPE_GROUP = 10;
// Field type message.
TYPE_MESSAGE = 11;
TYPE_MESSAGE = 11;
// Field type bytes.
TYPE_BYTES = 12;
TYPE_BYTES = 12;
// Field type uint32.
TYPE_UINT32 = 13;
TYPE_UINT32 = 13;
// Field type enum.
TYPE_ENUM = 14;
TYPE_ENUM = 14;
// Field type sfixed32.
TYPE_SFIXED32 = 15;
TYPE_SFIXED32 = 15;
// Field type sfixed64.
TYPE_SFIXED64 = 16;
TYPE_SFIXED64 = 16;
// Field type sint32.
TYPE_SINT32 = 17;
TYPE_SINT32 = 17;
// Field type sint64.
TYPE_SINT64 = 18;
};
TYPE_SINT64 = 18;
}
// Whether a field is optional, required, or repeated.
enum Cardinality {

Binary file not shown.

View File

@ -0,0 +1,51 @@
#!/bin/bash
set -ex
if [ "$#" -ne 1 ]
then
echo "Usage: $0 <protobuf-version>"
exit 1
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
VERSION="$1"
TEMPDIR=$(mktemp -d "protobuf-$VERSION-XXX")
ARCHS=( \
"linux-aarch_64" \
"linux-x86_32" \
"linux-x86_64" \
"osx-x86_64" \
"win32" \
)
for ARCH in "${ARCHS[@]}"; do
mkdir "$TEMPDIR/$ARCH"
curl --proto '=https' --tlsv1.2 -sSfL \
"https://github.com/protocolbuffers/protobuf/releases/download/v$VERSION/protoc-$VERSION-$ARCH.zip" \
-o "$TEMPDIR/$ARCH/protoc.zip"
EXTENSION=""
if [[ "$ARCH" == *"win"* ]]; then
EXTENSION=".exe"
fi
unzip "$TEMPDIR/$ARCH/protoc.zip" -d "$TEMPDIR/$ARCH"
mv "$TEMPDIR/$ARCH/bin/protoc$EXTENSION" "$DIR/protobuf/protoc-$ARCH$EXTENSION"
done
# Update the include directory
rm -rf "$DIR/protobuf/include"
mv "$TEMPDIR/linux-x86_64/include" "$DIR/protobuf/"
# Update the Protocol Buffers license.
mkdir "$TEMPDIR/src"
curl --proto '=https' --tlsv1.2 -sSfL \
"https://github.com/protocolbuffers/protobuf/archive/v$VERSION.zip" \
https://github.com/protocolbuffers/protobuf/archive/v3.11.2.zip
-o "$TEMPDIR/src/protobuf.zip"
unzip "$TEMPDIR/src/protobuf.zip" -d "$TEMPDIR/src"
mv "$TEMPDIR/src/license.txt" "$DIR/protobuf/license"
rm -rf $TEMPDIR

View File

@ -1,7 +1,6 @@
[package]
name = "prost-derive"
# NB: When modifying, also modify html_root_url in lib.rs
version = "0.5.0"
version = "0.6.1"
authors = ["Dan Burkert <dan@danburkert.com>"]
license = "Apache-2.0"
repository = "https://github.com/danburkert/prost"
@ -14,8 +13,8 @@ edition = "2018"
proc_macro = true
[dependencies]
failure = { version = "0.1", default-features = false, features = ["std"] }
anyhow = "1"
itertools = "0.8"
proc-macro2 = "0.4.4"
quote = "0.6.3"
syn = { version = "0.15", features = [ "extra-traits" ] }
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = [ "extra-traits" ] }

View File

@ -1,6 +1,6 @@
use failure::{bail, Error};
use anyhow::{bail, Error};
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use syn::Meta;
use crate::field::{set_bool, set_option, tag_attr, word_attr, Label};
@ -58,7 +58,10 @@ impl Field {
pub fn new_oneof(attrs: &[Meta]) -> Result<Option<Field>, Error> {
if let Some(mut field) = Field::new(attrs, None)? {
if let Some(attr) = attrs.iter().find(|attr| Label::from_attr(attr).is_some()) {
bail!("invalid atribute for oneof field: {}", attr.name());
bail!(
"invalid attribute for oneof field: {}",
attr.path().into_token_stream()
);
}
field.label = Label::Required;
Ok(Some(field))
@ -72,15 +75,15 @@ impl Field {
match self.label {
Label::Optional => quote! {
if let Some(ref msg) = #ident {
_prost::encoding::group::encode(#tag, msg, buf);
::prost::encoding::group::encode(#tag, msg, buf);
}
},
Label::Required => quote! {
_prost::encoding::group::encode(#tag, &#ident, buf);
::prost::encoding::group::encode(#tag, &#ident, buf);
},
Label::Repeated => quote! {
for msg in &#ident {
_prost::encoding::group::encode(#tag, msg, buf);
::prost::encoding::group::encode(#tag, msg, buf);
}
},
}
@ -89,16 +92,19 @@ impl Field {
pub fn merge(&self, ident: TokenStream) -> TokenStream {
match self.label {
Label::Optional => quote! {
_prost::encoding::group::merge(tag, wire_type,
#ident.get_or_insert_with(Default::default),
buf,
ctx)
::prost::encoding::group::merge(
tag,
wire_type,
#ident.get_or_insert_with(Default::default),
buf,
ctx,
)
},
Label::Required => quote! {
_prost::encoding::group::merge(tag, wire_type, &mut #ident, buf, ctx)
::prost::encoding::group::merge(tag, wire_type, #ident, buf, ctx)
},
Label::Repeated => quote! {
_prost::encoding::group::merge_repeated(tag, wire_type, &mut #ident, buf, ctx)
::prost::encoding::group::merge_repeated(tag, wire_type, #ident, buf, ctx)
},
}
}
@ -107,13 +113,13 @@ impl Field {
let tag = self.tag;
match self.label {
Label::Optional => quote! {
#ident.as_ref().map_or(0, |msg| _prost::encoding::group::encoded_len(#tag, msg))
#ident.as_ref().map_or(0, |msg| ::prost::encoding::group::encoded_len(#tag, msg))
},
Label::Required => quote! {
_prost::encoding::group::encoded_len(#tag, &#ident)
::prost::encoding::group::encoded_len(#tag, &#ident)
},
Label::Repeated => quote! {
_prost::encoding::group::encoded_len_repeated(#tag, &#ident)
::prost::encoding::group::encoded_len_repeated(#tag, &#ident)
},
}
}

View File

@ -1,4 +1,4 @@
use failure::{bail, Error};
use anyhow::{bail, Error};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Ident, Lit, Meta, MetaNameValue, NestedMeta};
@ -53,7 +53,11 @@ impl Field {
for attr in attrs {
if let Some(t) = tag_attr(attr)? {
set_option(&mut tag, t, "duplicate tag attributes")?;
} else if let Some(map_ty) = MapTy::from_str(&attr.name().to_string()) {
} else if let Some(map_ty) = attr
.path()
.get_ident()
.and_then(|i| MapTy::from_str(&i.to_string()))
{
let (k, v): (String, String) = match *attr {
Meta::NameValue(MetaNameValue {
lit: Lit::Str(ref lit),
@ -77,11 +81,15 @@ impl Field {
bail!("invalid map attribute: must contain key and value types");
}
let k = match &meta_list.nested[0] {
&NestedMeta::Meta(Meta::Word(ref k)) => k.to_string(),
&NestedMeta::Meta(Meta::Path(ref k)) if k.get_ident().is_some() => {
k.get_ident().unwrap().to_string()
}
_ => bail!("invalid map attribute: key must be an identifier"),
};
let v = match &meta_list.nested[1] {
&NestedMeta::Meta(Meta::Word(ref v)) => v.to_string(),
&NestedMeta::Meta(Meta::Path(ref v)) if v.get_ident().is_some() => {
v.get_ident().unwrap().to_string()
}
_ => bail!("invalid map attribute: value must be an identifier"),
};
(k, v)
@ -117,37 +125,52 @@ impl Field {
pub fn encode(&self, ident: TokenStream) -> TokenStream {
let tag = self.tag;
let key_mod = self.key_ty.module();
let ke = quote!(_prost::encoding::#key_mod::encode);
let kl = quote!(_prost::encoding::#key_mod::encoded_len);
let ke = quote!(::prost::encoding::#key_mod::encode);
let kl = quote!(::prost::encoding::#key_mod::encoded_len);
let module = self.map_ty.module();
match self.value_ty {
ValueTy::Scalar(scalar::Ty::Enumeration(ref ty)) => {
let default = quote!(#ty::default() as i32);
quote! {
_prost::encoding::#module::encode_with_default(#ke, #kl,
_prost::encoding::int32::encode,
_prost::encoding::int32::encoded_len,
&(#default),
#tag, &#ident, buf);
::prost::encoding::#module::encode_with_default(
#ke,
#kl,
::prost::encoding::int32::encode,
::prost::encoding::int32::encoded_len,
&(#default),
#tag,
&#ident,
buf,
);
}
}
ValueTy::Scalar(ref value_ty) => {
let val_mod = value_ty.module();
let ve = quote!(_prost::encoding::#val_mod::encode);
let vl = quote!(_prost::encoding::#val_mod::encoded_len);
let ve = quote!(::prost::encoding::#val_mod::encode);
let vl = quote!(::prost::encoding::#val_mod::encoded_len);
quote! {
_prost::encoding::#module::encode(#ke, #kl, #ve, #vl,
#tag, &#ident, buf);
}
}
ValueTy::Message => {
quote! {
_prost::encoding::#module::encode(#ke, #kl,
_prost::encoding::message::encode,
_prost::encoding::message::encoded_len,
#tag, &#ident, buf);
::prost::encoding::#module::encode(
#ke,
#kl,
#ve,
#vl,
#tag,
&#ident,
buf,
);
}
}
ValueTy::Message => quote! {
::prost::encoding::#module::encode(
#ke,
#kl,
::prost::encoding::message::encode,
::prost::encoding::message::encoded_len,
#tag,
&#ident,
buf,
);
},
}
}
@ -155,25 +178,36 @@ impl Field {
/// into the map.
pub fn merge(&self, ident: TokenStream) -> TokenStream {
let key_mod = self.key_ty.module();
let km = quote!(_prost::encoding::#key_mod::merge);
let km = quote!(::prost::encoding::#key_mod::merge);
let module = self.map_ty.module();
match self.value_ty {
ValueTy::Scalar(scalar::Ty::Enumeration(ref ty)) => {
let default = quote!(#ty::default() as i32);
quote! {
_prost::encoding::#module::merge_with_default(#km, _prost::encoding::int32::merge,
#default, &mut #ident, buf, ctx)
::prost::encoding::#module::merge_with_default(
#km,
::prost::encoding::int32::merge,
#default,
&mut #ident,
buf,
ctx,
)
}
}
ValueTy::Scalar(ref value_ty) => {
let val_mod = value_ty.module();
let vm = quote!(_prost::encoding::#val_mod::merge);
quote!(_prost::encoding::#module::merge(#km, #vm, &mut #ident, buf, ctx))
}
ValueTy::Message => {
quote!(_prost::encoding::#module::merge(#km, _prost::encoding::message::merge,
&mut #ident, buf, ctx))
let vm = quote!(::prost::encoding::#val_mod::merge);
quote!(::prost::encoding::#module::merge(#km, #vm, &mut #ident, buf, ctx))
}
ValueTy::Message => quote! {
::prost::encoding::#module::merge(
#km,
::prost::encoding::message::merge,
&mut #ident,
buf,
ctx,
)
},
}
}
@ -181,26 +215,34 @@ impl Field {
pub fn encoded_len(&self, ident: TokenStream) -> TokenStream {
let tag = self.tag;
let key_mod = self.key_ty.module();
let kl = quote!(_prost::encoding::#key_mod::encoded_len);
let kl = quote!(::prost::encoding::#key_mod::encoded_len);
let module = self.map_ty.module();
match self.value_ty {
ValueTy::Scalar(scalar::Ty::Enumeration(ref ty)) => {
let default = quote!(#ty::default() as i32);
quote! {
_prost::encoding::#module::encoded_len_with_default(
#kl, _prost::encoding::int32::encoded_len,
&(#default), #tag, &#ident)
::prost::encoding::#module::encoded_len_with_default(
#kl,
::prost::encoding::int32::encoded_len,
&(#default),
#tag,
&#ident,
)
}
}
ValueTy::Scalar(ref value_ty) => {
let val_mod = value_ty.module();
let vl = quote!(_prost::encoding::#val_mod::encoded_len);
quote!(_prost::encoding::#module::encoded_len(#kl, #vl, #tag, &#ident))
}
ValueTy::Message => {
quote!(_prost::encoding::#module::encoded_len(#kl, _prost::encoding::message::encoded_len,
#tag, &#ident))
let vl = quote!(::prost::encoding::#val_mod::encoded_len);
quote!(::prost::encoding::#module::encoded_len(#kl, #vl, #tag, &#ident))
}
ValueTy::Message => quote! {
::prost::encoding::#module::encoded_len(
#kl,
::prost::encoding::message::encoded_len,
#tag,
&#ident,
)
},
}
}
@ -222,10 +264,18 @@ impl Field {
quote!()
};
let get_doc = format!(
"Returns the enum value for the corresponding key in `{}`, \
or `None` if the entry does not exist or it is not a valid enum value.",
ident,
);
let insert_doc = format!("Inserts a key value pair into `{}`.", ident);
Some(quote! {
#[doc=#get_doc]
pub fn #get(&self, key: #key_ref_ty) -> ::std::option::Option<#ty> {
self.#ident.get(#take_ref key).cloned().and_then(#ty::from_i32)
}
#[doc=#insert_doc]
pub fn #insert(&mut self, key: #key_ty, value: #ty) -> ::std::option::Option<#ty> {
self.#ident.insert(key, value as i32).and_then(#ty::from_i32)
}

View File

@ -1,6 +1,6 @@
use failure::{bail, Error};
use anyhow::{bail, Error};
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use syn::Meta;
use crate::field::{set_bool, set_option, tag_attr, word_attr, Label};
@ -61,7 +61,10 @@ impl Field {
pub fn new_oneof(attrs: &[Meta]) -> Result<Option<Field>, Error> {
if let Some(mut field) = Field::new(attrs, None)? {
if let Some(attr) = attrs.iter().find(|attr| Label::from_attr(attr).is_some()) {
bail!("invalid atribute for oneof field: {}", attr.name());
bail!(
"invalid attribute for oneof field: {}",
attr.path().into_token_stream()
);
}
field.label = Label::Required;
Ok(Some(field))
@ -75,15 +78,15 @@ impl Field {
match self.label {
Label::Optional => quote! {
if let Some(ref msg) = #ident {
_prost::encoding::message::encode(#tag, msg, buf);
::prost::encoding::message::encode(#tag, msg, buf);
}
},
Label::Required => quote! {
_prost::encoding::message::encode(#tag, &#ident, buf);
::prost::encoding::message::encode(#tag, &#ident, buf);
},
Label::Repeated => quote! {
for msg in &#ident {
_prost::encoding::message::encode(#tag, msg, buf);
::prost::encoding::message::encode(#tag, msg, buf);
}
},
}
@ -92,16 +95,16 @@ impl Field {
pub fn merge(&self, ident: TokenStream) -> TokenStream {
match self.label {
Label::Optional => quote! {
_prost::encoding::message::merge(wire_type,
::prost::encoding::message::merge(wire_type,
#ident.get_or_insert_with(Default::default),
buf,
ctx)
},
Label::Required => quote! {
_prost::encoding::message::merge(wire_type, &mut #ident, buf, ctx)
::prost::encoding::message::merge(wire_type, #ident, buf, ctx)
},
Label::Repeated => quote! {
_prost::encoding::message::merge_repeated(wire_type, &mut #ident, buf, ctx)
::prost::encoding::message::merge_repeated(wire_type, #ident, buf, ctx)
},
}
}
@ -110,13 +113,13 @@ impl Field {
let tag = self.tag;
match self.label {
Label::Optional => quote! {
#ident.as_ref().map_or(0, |msg| _prost::encoding::message::encoded_len(#tag, msg))
#ident.as_ref().map_or(0, |msg| ::prost::encoding::message::encoded_len(#tag, msg))
},
Label::Required => quote! {
_prost::encoding::message::encoded_len(#tag, &#ident)
::prost::encoding::message::encoded_len(#tag, &#ident)
},
Label::Repeated => quote! {
_prost::encoding::message::encoded_len_repeated(#tag, &#ident)
::prost::encoding::message::encoded_len_repeated(#tag, &#ident)
},
}
}

View File

@ -7,7 +7,7 @@ mod scalar;
use std::fmt;
use std::slice;
use failure::{bail, Error};
use anyhow::{bail, Error};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, Ident, Lit, LitBool, Meta, MetaList, MetaNameValue, NestedMeta};
@ -200,9 +200,9 @@ impl Label {
/// Parses a string into a field label.
/// If the string doesn't match a field label, `None` is returned.
fn from_attr(attr: &Meta) -> Option<Label> {
if let Meta::Word(ref ident) = *attr {
if let Meta::Path(ref path) = *attr {
for &label in Label::variants() {
if ident == label.as_str() {
if path.is_ident(label.as_str()) {
return Some(label);
}
}
@ -227,10 +227,10 @@ impl fmt::Display for Label {
pub(super) fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
Ok(attrs
.iter()
.flat_map(Attribute::interpret_meta)
.flat_map(Attribute::parse_meta)
.flat_map(|meta| match meta {
Meta::List(MetaList { ident, nested, .. }) => {
if ident == "prost" {
Meta::List(MetaList { path, nested, .. }) => {
if path.is_ident("prost") {
nested.into_iter().collect()
} else {
Vec::new()
@ -241,7 +241,7 @@ pub(super) fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
.flat_map(|attr| -> Result<_, _> {
match attr {
NestedMeta::Meta(attr) => Ok(attr),
NestedMeta::Literal(lit) => bail!("invalid prost attribute: {:?}", lit),
NestedMeta::Lit(lit) => bail!("invalid prost attribute: {:?}", lit),
}
})
.collect())
@ -270,15 +270,15 @@ pub fn set_bool(b: &mut bool, message: &str) -> Result<(), Error> {
/// Unpacks an attribute into a (key, boolean) pair, returning the boolean value.
/// If the key doesn't match the attribute, `None` is returned.
fn bool_attr(key: &str, attr: &Meta) -> Result<Option<bool>, Error> {
if attr.name() != key {
if !attr.path().is_ident(key) {
return Ok(None);
}
match *attr {
Meta::Word(..) => Ok(Some(true)),
Meta::Path(..) => Ok(Some(true)),
Meta::List(ref meta_list) => {
// TODO(rustlang/rust#23121): slice pattern matching would make this much nicer.
if meta_list.nested.len() == 1 {
if let NestedMeta::Literal(Lit::Bool(LitBool { value, .. })) = meta_list.nested[0] {
if let NestedMeta::Lit(Lit::Bool(LitBool { value, .. })) = meta_list.nested[0] {
return Ok(Some(value));
}
}
@ -302,23 +302,23 @@ fn bool_attr(key: &str, attr: &Meta) -> Result<Option<bool>, Error> {
/// Checks if an attribute matches a word.
fn word_attr(key: &str, attr: &Meta) -> bool {
if let Meta::Word(ref ident) = *attr {
ident == key
if let Meta::Path(ref path) = *attr {
path.is_ident(key)
} else {
false
}
}
pub(super) fn tag_attr(attr: &Meta) -> Result<Option<u32>, Error> {
if attr.name() != "tag" {
if !attr.path().is_ident("tag") {
return Ok(None);
}
match *attr {
Meta::List(ref meta_list) => {
// TODO(rustlang/rust#23121): slice pattern matching would make this much nicer.
if meta_list.nested.len() == 1 {
if let NestedMeta::Literal(Lit::Int(ref lit)) = meta_list.nested[0] {
return Ok(Some(lit.value() as u32));
if let NestedMeta::Lit(Lit::Int(ref lit)) = meta_list.nested[0] {
return Ok(Some(lit.base10_parse()?));
}
}
bail!("invalid tag attribute: {:?}", attr);
@ -329,7 +329,7 @@ pub(super) fn tag_attr(attr: &Meta) -> Result<Option<u32>, Error> {
.parse::<u32>()
.map_err(Error::from)
.map(Option::Some),
Lit::Int(ref lit) => Ok(Some(lit.value() as u32)),
Lit::Int(ref lit) => Ok(Some(lit.base10_parse()?)),
_ => bail!("invalid tag attribute: {:?}", attr),
},
_ => bail!("invalid tag attribute: {:?}", attr),
@ -337,15 +337,15 @@ pub(super) fn tag_attr(attr: &Meta) -> Result<Option<u32>, Error> {
}
fn tags_attr(attr: &Meta) -> Result<Option<Vec<u32>>, Error> {
if attr.name() != "tags" {
if !attr.path().is_ident("tags") {
return Ok(None);
}
match *attr {
Meta::List(ref meta_list) => {
let mut tags = Vec::with_capacity(meta_list.nested.len());
for item in &meta_list.nested {
if let NestedMeta::Literal(Lit::Int(ref lit)) = *item {
tags.push(lit.value() as u32);
if let NestedMeta::Lit(Lit::Int(ref lit)) = *item {
tags.push(lit.base10_parse()?);
} else {
bail!("invalid tag attribute: {:?}", attr);
}

View File

@ -1,4 +1,4 @@
use failure::{bail, Error};
use anyhow::{bail, Error};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_str, Lit, Meta, MetaNameValue, NestedMeta, Path};
@ -18,7 +18,7 @@ impl Field {
let mut unknown_attrs = Vec::new();
for attr in attrs {
if attr.name() == "oneof" {
if attr.path().is_ident("oneof") {
let t = match *attr {
Meta::NameValue(MetaNameValue {
lit: Lit::Str(ref lit),
@ -26,8 +26,12 @@ impl Field {
}) => parse_str::<Path>(&lit.value())?,
Meta::List(ref list) if list.nested.len() == 1 => {
// TODO(rustlang/rust#23121): slice pattern matching would make this much nicer.
if let NestedMeta::Meta(Meta::Word(ref ident)) = list.nested[0] {
Path::from(ident.clone())
if let NestedMeta::Meta(Meta::Path(ref path)) = list.nested[0] {
if let Some(ident) = path.get_ident() {
Path::from(ident.clone())
} else {
bail!("invalid oneof attribute: item must be an identifier");
}
} else {
bail!("invalid oneof attribute: item must be an identifier");
}
@ -77,7 +81,7 @@ impl Field {
pub fn merge(&self, ident: TokenStream) -> TokenStream {
let ty = &self.ty;
quote! {
#ty::merge(&mut #ident, tag, wire_type, buf, ctx)
#ty::merge(#ident, tag, wire_type, buf, ctx)
}
}

View File

@ -1,11 +1,11 @@
use std::convert::TryFrom;
use std::fmt;
use failure::{bail, format_err, Error};
use anyhow::{anyhow, bail, Error};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
self, parse_str, FloatSuffix, Ident, IntSuffix, Lit, LitByteStr, Meta, MetaList, MetaNameValue,
NestedMeta, Path,
self, parse_str, Ident, Lit, LitByteStr, Meta, MetaList, MetaNameValue, NestedMeta, Path,
};
use crate::field::{bool_attr, set_option, tag_attr, Label};
@ -118,7 +118,7 @@ impl Field {
Kind::Repeated => quote!(encode_repeated),
Kind::Packed => quote!(encode_packed),
};
let encode_fn = quote!(_prost::encoding::#module::#encode_fn);
let encode_fn = quote!(::prost::encoding::#module::#encode_fn);
let tag = self.tag;
match self.kind {
@ -149,11 +149,11 @@ impl Field {
Kind::Plain(..) | Kind::Optional(..) | Kind::Required(..) => quote!(merge),
Kind::Repeated | Kind::Packed => quote!(merge_repeated),
};
let merge_fn = quote!(_prost::encoding::#module::#merge_fn);
let merge_fn = quote!(::prost::encoding::#module::#merge_fn);
match self.kind {
Kind::Plain(..) | Kind::Required(..) | Kind::Repeated | Kind::Packed => quote! {
#merge_fn(wire_type, &mut #ident, buf, ctx)
#merge_fn(wire_type, #ident, buf, ctx)
},
Kind::Optional(..) => quote! {
#merge_fn(wire_type,
@ -172,7 +172,7 @@ impl Field {
Kind::Repeated => quote!(encoded_len_repeated),
Kind::Packed => quote!(encoded_len_packed),
};
let encoded_len_fn = quote!(_prost::encoding::#module::#encoded_len_fn);
let encoded_len_fn = quote!(::prost::encoding::#module::#encoded_len_fn);
let tag = self.tag;
match self.kind {
@ -278,38 +278,63 @@ impl Field {
if ident_str.starts_with("r#") {
ident_str = ident_str[2..].to_owned();
}
let set = Ident::new(&format!("set_{}", ident_str), Span::call_site());
let push = Ident::new(&format!("push_{}", ident_str), Span::call_site());
if let Ty::Enumeration(ref ty) = self.ty {
let set = Ident::new(&format!("set_{}", ident_str), Span::call_site());
let set_doc = format!("Sets `{}` to the provided enum value.", ident_str);
Some(match self.kind {
Kind::Plain(ref default) | Kind::Required(ref default) => {
let get_doc = format!(
"Returns the enum value of `{}`, \
or the default if the field is set to an invalid enum value.",
ident_str,
);
quote! {
#[doc=#get_doc]
pub fn #ident(&self) -> #ty {
#ty::from_i32(self.#ident).unwrap_or(#default)
}
#[doc=#set_doc]
pub fn #set(&mut self, value: #ty) {
self.#ident = value as i32;
}
}
}
Kind::Optional(ref default) => {
let get_doc = format!(
"Returns the enum value of `{}`, \
or the default if the field is unset or set to an invalid enum value.",
ident_str,
);
quote! {
#[doc=#get_doc]
pub fn #ident(&self) -> #ty {
self.#ident.and_then(#ty::from_i32).unwrap_or(#default)
}
#[doc=#set_doc]
pub fn #set(&mut self, value: #ty) {
self.#ident = ::std::option::Option::Some(value as i32);
}
}
}
Kind::Repeated | Kind::Packed => {
let iter_doc = format!(
"Returns an iterator which yields the valid enum values contained in `{}`.",
ident_str,
);
let push = Ident::new(&format!("push_{}", ident_str), Span::call_site());
let push_doc = format!("Appends the provided enum value to `{}`.", ident_str);
quote! {
pub fn #ident(&self) -> ::std::iter::FilterMap<::std::iter::Cloned<::std::slice::Iter<i32>>,
fn(i32) -> Option<#ty>> {
#[doc=#iter_doc]
pub fn #ident(&self) -> ::std::iter::FilterMap<
::std::iter::Cloned<::std::slice::Iter<i32>>,
fn(i32) -> ::std::option::Option<#ty>,
> {
self.#ident.iter().cloned().filter_map(#ty::from_i32)
}
#[doc=#push_doc]
pub fn #push(&mut self, value: #ty) {
self.#ident.push(value as i32);
}
@ -325,7 +350,13 @@ impl Field {
quote!(::std::option::Option::Some(ref val) => &val[..],)
};
let get_doc = format!(
"Returns the value of `{0}`, or the default value if `{0}` is unset.",
ident_str,
);
Some(quote! {
#[doc=#get_doc]
pub fn #ident(&self) -> #ty {
match self.#ident {
#match_some
@ -363,35 +394,35 @@ pub enum Ty {
impl Ty {
pub fn from_attr(attr: &Meta) -> Result<Option<Ty>, Error> {
let ty = match *attr {
Meta::Word(ref name) if name == "float" => Ty::Float,
Meta::Word(ref name) if name == "double" => Ty::Double,
Meta::Word(ref name) if name == "int32" => Ty::Int32,
Meta::Word(ref name) if name == "int64" => Ty::Int64,
Meta::Word(ref name) if name == "uint32" => Ty::Uint32,
Meta::Word(ref name) if name == "uint64" => Ty::Uint64,
Meta::Word(ref name) if name == "sint32" => Ty::Sint32,
Meta::Word(ref name) if name == "sint64" => Ty::Sint64,
Meta::Word(ref name) if name == "fixed32" => Ty::Fixed32,
Meta::Word(ref name) if name == "fixed64" => Ty::Fixed64,
Meta::Word(ref name) if name == "sfixed32" => Ty::Sfixed32,
Meta::Word(ref name) if name == "sfixed64" => Ty::Sfixed64,
Meta::Word(ref name) if name == "bool" => Ty::Bool,
Meta::Word(ref name) if name == "string" => Ty::String,
Meta::Word(ref name) if name == "bytes" => Ty::Bytes,
Meta::Path(ref name) if name.is_ident("float") => Ty::Float,
Meta::Path(ref name) if name.is_ident("double") => Ty::Double,
Meta::Path(ref name) if name.is_ident("int32") => Ty::Int32,
Meta::Path(ref name) if name.is_ident("int64") => Ty::Int64,
Meta::Path(ref name) if name.is_ident("uint32") => Ty::Uint32,
Meta::Path(ref name) if name.is_ident("uint64") => Ty::Uint64,
Meta::Path(ref name) if name.is_ident("sint32") => Ty::Sint32,
Meta::Path(ref name) if name.is_ident("sint64") => Ty::Sint64,
Meta::Path(ref name) if name.is_ident("fixed32") => Ty::Fixed32,
Meta::Path(ref name) if name.is_ident("fixed64") => Ty::Fixed64,
Meta::Path(ref name) if name.is_ident("sfixed32") => Ty::Sfixed32,
Meta::Path(ref name) if name.is_ident("sfixed64") => Ty::Sfixed64,
Meta::Path(ref name) if name.is_ident("bool") => Ty::Bool,
Meta::Path(ref name) if name.is_ident("string") => Ty::String,
Meta::Path(ref name) if name.is_ident("bytes") => Ty::Bytes,
Meta::NameValue(MetaNameValue {
ref ident,
ref path,
lit: Lit::Str(ref l),
..
}) if ident == "enumeration" => Ty::Enumeration(parse_str::<Path>(&l.value())?),
}) if path.is_ident("enumeration") => Ty::Enumeration(parse_str::<Path>(&l.value())?),
Meta::List(MetaList {
ref ident,
ref path,
ref nested,
..
}) if ident == "enumeration" => {
}) if path.is_ident("enumeration") => {
// TODO(rustlang/rust#23121): slice pattern matching would make this much nicer.
if nested.len() == 1 {
if let NestedMeta::Meta(Meta::Word(ref ident)) = nested[0] {
Ty::Enumeration(Path::from(ident.clone()))
if let NestedMeta::Meta(Meta::Path(ref path)) = nested[0] {
Ty::Enumeration(path.clone())
} else {
bail!("invalid enumeration attribute: item must be an identifier");
}
@ -406,7 +437,7 @@ impl Ty {
pub fn from_str(s: &str) -> Result<Ty, Error> {
let enumeration_len = "enumeration".len();
let error = Err(format_err!("invalid type: {}", s));
let error = Err(anyhow!("invalid type: {}", s));
let ty = match s.trim() {
"float" => Ty::Float,
"double" => Ty::Double,
@ -552,7 +583,7 @@ pub enum DefaultValue {
impl DefaultValue {
pub fn from_attr(attr: &Meta) -> Result<Option<Lit>, Error> {
if attr.name() != "default" {
if !attr.path().is_ident("default") {
return Ok(None);
} else if let Meta::NameValue(ref name_value) = *attr {
Ok(Some(name_value.lit.clone()))
@ -568,51 +599,35 @@ impl DefaultValue {
let is_u32 = *ty == Ty::Uint32 || *ty == Ty::Fixed32;
let is_u64 = *ty == Ty::Uint64 || *ty == Ty::Fixed64;
let empty_or_is = |expected, actual: &str| expected == actual || actual.is_empty();
let default = match lit {
Lit::Int(ref lit)
if is_i32
&& (lit.suffix() == IntSuffix::I32 || lit.suffix() == IntSuffix::None) =>
{
DefaultValue::I32(lit.value() as _)
Lit::Int(ref lit) if is_i32 && empty_or_is("i32", lit.suffix()) => {
DefaultValue::I32(lit.base10_parse()?)
}
Lit::Int(ref lit)
if is_i64
&& (lit.suffix() == IntSuffix::I64 || lit.suffix() == IntSuffix::None) =>
{
DefaultValue::I64(lit.value() as _)
Lit::Int(ref lit) if is_i64 && empty_or_is("i64", lit.suffix()) => {
DefaultValue::I64(lit.base10_parse()?)
}
Lit::Int(ref lit)
if is_u32
&& (lit.suffix() == IntSuffix::U32 || lit.suffix() == IntSuffix::None) =>
{
DefaultValue::U32(lit.value() as _)
Lit::Int(ref lit) if is_u32 && empty_or_is("u32", lit.suffix()) => {
DefaultValue::U32(lit.base10_parse()?)
}
Lit::Int(ref lit)
if is_u64
&& (lit.suffix() == IntSuffix::U64 || lit.suffix() == IntSuffix::None) =>
{
DefaultValue::U64(lit.value())
Lit::Int(ref lit) if is_u64 && empty_or_is("u64", lit.suffix()) => {
DefaultValue::U64(lit.base10_parse()?)
}
Lit::Float(ref lit)
if *ty == Ty::Float
&& (lit.suffix() == FloatSuffix::F32 || lit.suffix() == FloatSuffix::None) =>
{
DefaultValue::F32(lit.value() as _)
Lit::Float(ref lit) if *ty == Ty::Float && empty_or_is("f32", lit.suffix()) => {
DefaultValue::F32(lit.base10_parse()?)
}
Lit::Int(ref lit) if *ty == Ty::Float => DefaultValue::F32(lit.value() as _),
Lit::Int(ref lit) if *ty == Ty::Float => DefaultValue::F32(lit.base10_parse()?),
Lit::Float(ref lit)
if *ty == Ty::Double
&& (lit.suffix() == FloatSuffix::F64 || lit.suffix() == FloatSuffix::None) =>
{
DefaultValue::F64(lit.value())
Lit::Float(ref lit) if *ty == Ty::Double && empty_or_is("f64", lit.suffix()) => {
DefaultValue::F64(lit.base10_parse()?)
}
Lit::Int(ref lit) if *ty == Ty::Double => DefaultValue::F64(lit.value() as _),
Lit::Int(ref lit) if *ty == Ty::Double => DefaultValue::F64(lit.base10_parse()?),
Lit::Bool(ref lit) if *ty == Ty::Bool => DefaultValue::Bool(lit.value),
Lit::Str(ref lit) if *ty == Ty::String => DefaultValue::String(lit.value().clone()),
Lit::ByteStr(ref lit) if *ty == Ty::Bytes => DefaultValue::Bytes(lit.value().clone()),
Lit::Str(ref lit) if *ty == Ty::String => DefaultValue::String(lit.value()),
Lit::ByteStr(ref lit) if *ty == Ty::Bytes => DefaultValue::Bytes(lit.value()),
Lit::Str(ref lit) => {
let value = lit.value();
@ -665,48 +680,36 @@ impl DefaultValue {
if value.chars().next() == Some('-') {
if let Ok(lit) = syn::parse_str::<Lit>(&value[1..]) {
match lit {
Lit::Int(ref lit)
if is_i32
&& (lit.suffix() == IntSuffix::I32
|| lit.suffix() == IntSuffix::None) =>
{
return Ok(DefaultValue::I32((!lit.value() + 1) as i32));
Lit::Int(ref lit) if is_i32 && empty_or_is("i32", lit.suffix()) => {
// Initially parse into an i64, so that i32::MIN does not overflow.
let value: i64 = -lit.base10_parse()?;
return Ok(i32::try_from(value).map(DefaultValue::I32)?);
}
Lit::Int(ref lit)
if is_i64
&& (lit.suffix() == IntSuffix::I64
|| lit.suffix() == IntSuffix::None) =>
{
return Ok(DefaultValue::I64((!lit.value() + 1) as i64));
Lit::Int(ref lit) if is_i64 && empty_or_is("i64", lit.suffix()) => {
// Initially parse into an i128, so that i64::MIN does not overflow.
let value: i128 = -lit.base10_parse()?;
return Ok(i64::try_from(value).map(DefaultValue::I64)?);
}
Lit::Float(ref lit)
if *ty == Ty::Float
&& (lit.suffix() == FloatSuffix::F32
|| lit.suffix() == FloatSuffix::None) =>
if *ty == Ty::Float && empty_or_is("f32", lit.suffix()) =>
{
return Ok(DefaultValue::F32(-lit.value() as f32));
return Ok(DefaultValue::F32(-lit.base10_parse()?));
}
Lit::Float(ref lit)
if *ty == Ty::Double
&& (lit.suffix() == FloatSuffix::F64
|| lit.suffix() == FloatSuffix::None) =>
if *ty == Ty::Double && empty_or_is("f64", lit.suffix()) =>
{
return Ok(DefaultValue::F64(-lit.value()));
return Ok(DefaultValue::F64(-lit.base10_parse()?));
}
Lit::Int(ref lit)
if *ty == Ty::Float && lit.suffix() == IntSuffix::None =>
{
return Ok(DefaultValue::F32(-(lit.value() as f32)));
Lit::Int(ref lit) if *ty == Ty::Float && lit.suffix().is_empty() => {
return Ok(DefaultValue::F32(-lit.base10_parse()?));
}
Lit::Int(ref lit)
if *ty == Ty::Double && lit.suffix() == IntSuffix::None =>
{
return Ok(DefaultValue::F64(-(lit.value() as f64)));
Lit::Int(ref lit) if *ty == Ty::Double && lit.suffix().is_empty() => {
return Ok(DefaultValue::F64(-lit.base10_parse()?));
}
_ => (),

View File

@ -1,13 +1,13 @@
#![doc(html_root_url = "https://docs.rs/prost-derive/0.5.0")]
#![doc(html_root_url = "https://docs.rs/prost-derive/0.6.1")]
// The `quote!` macro requires deep recursion.
#![recursion_limit = "4096"]
extern crate proc_macro;
use failure::bail;
use anyhow::bail;
use quote::quote;
use failure::Error;
use anyhow::Error;
use itertools::Itertools;
use proc_macro::TokenStream;
use proc_macro2::Span;
@ -71,7 +71,7 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
)),
}
})
.collect::<Result<Vec<(Ident, Field)>, failure::Context<String>>>()?;
.collect::<Result<Vec<_>, _>>()?;
// We want Debug to be in declaration order
let unsorted_fields = fields.clone();
@ -94,9 +94,6 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
bail!("message {} has fields with duplicate tags", ident);
}
// Put impls in a const, so that 'extern crate' can be used.
let dummy_const = Ident::new(&format!("{}_MESSAGE", ident), Span::call_site());
let encoded_len = fields
.iter()
.map(|&(ref field_ident, ref field)| field.encoded_len(quote!(self.#field_ident)));
@ -106,16 +103,21 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
.map(|&(ref field_ident, ref field)| field.encode(quote!(self.#field_ident)));
let merge = fields.iter().map(|&(ref field_ident, ref field)| {
let merge = field.merge(quote!(self.#field_ident));
let merge = field.merge(quote!(value));
let tags = field
.tags()
.into_iter()
.map(|tag| quote!(#tag))
.intersperse(quote!(|));
quote!(#(#tags)* => #merge.map_err(|mut error| {
error.push(STRUCT_NAME, stringify!(#field_ident));
error
}),)
quote! {
#(#tags)* => {
let mut value = &mut self.#field_ident;
#merge.map_err(|mut error| {
error.push(STRUCT_NAME, stringify!(#field_ident));
error
})
},
}
});
let struct_name = if fields.is_empty() {
@ -174,60 +176,55 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> {
};
let expanded = quote! {
#[allow(non_snake_case, unused_attributes)]
const #dummy_const: () = {
extern crate prost as _prost;
extern crate bytes as _bytes;
impl ::prost::Message for #ident {
#[allow(unused_variables)]
fn encode_raw<B>(&self, buf: &mut B) where B: ::prost::bytes::BufMut {
#(#encode)*
}
impl _prost::Message for #ident {
#[allow(unused_variables)]
fn encode_raw<B>(&self, buf: &mut B) where B: _bytes::BufMut {
#(#encode)*
}
#[allow(unused_variables)]
fn merge_field<B>(&mut self,
tag: u32,
wire_type: _prost::encoding::WireType,
buf: &mut B,
ctx: _prost::encoding::DecodeContext,
) -> ::std::result::Result<(), _prost::DecodeError>
where B: _bytes::Buf {
#struct_name
match tag {
#(#merge)*
_ => _prost::encoding::skip_field(wire_type, tag, buf),
}
}
#[inline]
fn encoded_len(&self) -> usize {
0 #(+ #encoded_len)*
}
fn clear(&mut self) {
#(#clear;)*
#[allow(unused_variables)]
fn merge_field<B>(
&mut self,
tag: u32,
wire_type: ::prost::encoding::WireType,
buf: &mut B,
ctx: ::prost::encoding::DecodeContext,
) -> ::std::result::Result<(), ::prost::DecodeError>
where B: ::prost::bytes::Buf {
#struct_name
match tag {
#(#merge)*
_ => ::prost::encoding::skip_field(wire_type, tag, buf, ctx),
}
}
impl Default for #ident {
fn default() -> #ident {
#ident {
#(#default)*
}
}
#[inline]
fn encoded_len(&self) -> usize {
0 #(+ #encoded_len)*
}
impl ::std::fmt::Debug for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
let mut builder = #debug_builder;
#(#debugs;)*
builder.finish()
fn clear(&mut self) {
#(#clear;)*
}
}
impl Default for #ident {
fn default() -> #ident {
#ident {
#(#default)*
}
}
}
#methods
};
impl ::std::fmt::Debug for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
let mut builder = #debug_builder;
#(#debugs;)*
builder.finish()
}
}
#methods
};
Ok(expanded.into())
@ -280,8 +277,6 @@ fn try_enumeration(input: TokenStream) -> Result<TokenStream, Error> {
let default = variants[0].0.clone();
// Put impls in a const, so that 'extern crate' can be used.
let dummy_const = Ident::new(&format!("{}_ENUMERATION", ident), Span::call_site());
let is_valid = variants
.iter()
.map(|&(_, ref value)| quote!(#value => true));
@ -296,39 +291,35 @@ fn try_enumeration(input: TokenStream) -> Result<TokenStream, Error> {
);
let expanded = quote! {
#[allow(non_snake_case, unused_attributes)]
const #dummy_const: () = {
impl #ident {
#[doc=#is_valid_doc]
pub fn is_valid(value: i32) -> bool {
match value {
#(#is_valid,)*
_ => false,
}
}
#[doc=#from_i32_doc]
pub fn from_i32(value: i32) -> ::std::option::Option<#ident> {
match value {
#(#from,)*
_ => ::std::option::Option::None,
}
impl #ident {
#[doc=#is_valid_doc]
pub fn is_valid(value: i32) -> bool {
match value {
#(#is_valid,)*
_ => false,
}
}
impl ::std::default::Default for #ident {
fn default() -> #ident {
#ident::#default
#[doc=#from_i32_doc]
pub fn from_i32(value: i32) -> ::std::option::Option<#ident> {
match value {
#(#from,)*
_ => ::std::option::Option::None,
}
}
}
impl ::std::convert::From<#ident> for i32 {
fn from(value: #ident) -> i32 {
value as i32
}
impl ::std::default::Default for #ident {
fn default() -> #ident {
#ident::#default
}
};
}
impl ::std::convert::From<#ident> for i32 {
fn from(value: #ident) -> i32 {
value as i32
}
}
};
Ok(expanded.into())
@ -398,9 +389,6 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
panic!("invalid oneof {}: variants have duplicate tags", ident);
}
// Put impls in a const, so that 'extern crate' can be used.
let dummy_const = Ident::new(&format!("{}_ONEOF", ident), Span::call_site());
let encode = fields.iter().map(|&(ref variant_ident, ref field)| {
let encode = field.encode(quote!(*value));
quote!(#ident::#variant_ident(ref value) => { #encode })
@ -411,8 +399,16 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
let merge = field.merge(quote!(value));
quote! {
#tag => {
let mut value = ::std::default::Default::default();
#merge.map(|_| *field = ::std::option::Option::Some(#ident::#variant_ident(value)))
match field {
::std::option::Option::Some(#ident::#variant_ident(ref mut value)) => {
#merge
},
_ => {
let mut owned_value = ::std::default::Default::default();
let value = &mut owned_value;
#merge.map(|_| *field = ::std::option::Option::Some(#ident::#variant_ident(owned_value)))
},
}
}
}
});
@ -433,47 +429,42 @@ fn try_oneof(input: TokenStream) -> Result<TokenStream, Error> {
});
let expanded = quote! {
#[allow(non_snake_case, unused_attributes)]
const #dummy_const: () = {
extern crate bytes as _bytes;
extern crate prost as _prost;
impl #ident {
pub fn encode<B>(&self, buf: &mut B) where B: _bytes::BufMut {
match *self {
#(#encode,)*
}
}
pub fn merge<B>(field: &mut ::std::option::Option<#ident>,
tag: u32,
wire_type: _prost::encoding::WireType,
buf: &mut B,
ctx: _prost::encoding::DecodeContext,
) -> ::std::result::Result<(), _prost::DecodeError>
where B: _bytes::Buf {
match tag {
#(#merge,)*
_ => unreachable!(concat!("invalid ", stringify!(#ident), " tag: {}"), tag),
}
}
#[inline]
pub fn encoded_len(&self) -> usize {
match *self {
#(#encoded_len,)*
}
impl #ident {
pub fn encode<B>(&self, buf: &mut B) where B: ::prost::bytes::BufMut {
match *self {
#(#encode,)*
}
}
impl ::std::fmt::Debug for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
#(#debug,)*
}
pub fn merge<B>(
field: &mut ::std::option::Option<#ident>,
tag: u32,
wire_type: ::prost::encoding::WireType,
buf: &mut B,
ctx: ::prost::encoding::DecodeContext,
) -> ::std::result::Result<(), ::prost::DecodeError>
where B: ::prost::bytes::Buf {
match tag {
#(#merge,)*
_ => unreachable!(concat!("invalid ", stringify!(#ident), " tag: {}"), tag),
}
}
};
#[inline]
pub fn encoded_len(&self) -> usize {
match *self {
#(#encoded_len,)*
}
}
}
impl ::std::fmt::Debug for #ident {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
#(#debug,)*
}
}
}
};
Ok(expanded.into())

View File

@ -1,7 +1,6 @@
[package]
name = "prost-types"
# NB: When modifying, also modify html_root_url in lib.rs
version = "0.5.0"
version = "0.6.1"
authors = ["Dan Burkert <dan@danburkert.com>"]
license = "Apache-2.0"
repository = "https://github.com/danburkert/prost"
@ -15,5 +14,5 @@ doctest = false
test = false
[dependencies]
bytes = "0.4.7"
prost = { version = "0.5.0", path = ".." }
bytes = { version = "0.5", default-features = false }
prost = { version = "0.6.1", path = "..", default-features = false }

View File

@ -1,4 +1,4 @@
#![doc(html_root_url = "https://docs.rs/prost-codegen/0.5.0")]
#![doc(html_root_url = "https://docs.rs/prost-types/0.6.1")]
//! Protocol Buffers well-known types.
//!
@ -9,6 +9,7 @@
//!
//! [1]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
use std::convert::TryFrom;
use std::i32;
use std::i64;
use std::time;
@ -71,11 +72,12 @@ impl From<time::Duration> for Duration {
}
}
/// Converts a `Duration` to a result containing a positive (`Ok`) or negative (`Err`)
/// `std::time::Duration`.
// TODO: convert this to TryFrom when it's stable.
impl From<Duration> for Result<time::Duration, time::Duration> {
fn from(mut duration: Duration) -> Result<time::Duration, time::Duration> {
impl TryFrom<Duration> for time::Duration {
type Error = time::Duration;
/// Converts a `Duration` to a result containing a positive (`Ok`) or negative (`Err`)
/// `std::time::Duration`.
fn try_from(mut duration: Duration) -> Result<time::Duration, time::Duration> {
duration.normalize();
if duration.seconds >= 0 {
Ok(time::Duration::new(
@ -126,11 +128,12 @@ impl From<time::SystemTime> for Timestamp {
}
}
/// Converts a `Timestamp` to a `SystemTime`, or if the timestamp falls before the Unix epoch, a
/// duration containing the difference.
// TODO: convert this to TryFrom when it's stable.
impl From<Timestamp> for Result<time::SystemTime, time::Duration> {
fn from(mut timestamp: Timestamp) -> Result<time::SystemTime, time::Duration> {
impl TryFrom<Timestamp> for time::SystemTime {
type Error = time::Duration;
/// Converts a `Timestamp` to a `SystemTime`, or if the timestamp falls before the Unix epoch,
/// a duration containing the difference.
fn try_from(mut timestamp: Timestamp) -> Result<time::SystemTime, time::Duration> {
timestamp.normalize();
if timestamp.seconds >= 0 {
Ok(time::UNIX_EPOCH

View File

@ -75,8 +75,10 @@ pub struct DescriptorProto {
pub mod descriptor_proto {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExtensionRange {
/// Inclusive.
#[prost(int32, optional, tag="1")]
pub start: ::std::option::Option<i32>,
/// Exclusive.
#[prost(int32, optional, tag="2")]
pub end: ::std::option::Option<i32>,
#[prost(message, optional, tag="3")]
@ -401,8 +403,8 @@ pub struct FileOptions {
#[prost(string, optional, tag="41")]
pub php_namespace: ::std::option::Option<std::string::String>,
/// Use this option to change the namespace of php generated metadata classes.
/// Default is empty. When this option is empty, the proto file name will be used
/// for determining the namespace.
/// Default is empty. When this option is empty, the proto file name will be
/// used for determining the namespace.
#[prost(string, optional, tag="44")]
pub php_metadata_namespace: ::std::option::Option<std::string::String>,
/// Use this option to change the package of ruby generated classes. Default
@ -478,7 +480,7 @@ pub struct MessageOptions {
///
/// Implementations may choose not to generate the map_entry=true message, but
/// use a native map in the target language to hold the keys and values.
/// The reflection APIs in such implementions still need to work as
/// The reflection APIs in such implementations still need to work as
/// if the field is a repeated message field.
///
/// NOTE: Do not set the option in .proto files. Always use the maps syntax
@ -748,7 +750,7 @@ pub struct SourceCodeInfo {
/// beginning of the "extend" block and is shared by all extensions within
/// the block.
/// - Just because a location's span is a subset of some other location's span
/// does not mean that it is a descendent. For example, a "group" defines
/// does not mean that it is a descendant. For example, a "group" defines
/// both a type and a field in a single declaration. Thus, the locations
/// corresponding to the type and field and their components will overlap.
/// - Code which tries to interpret locations should probably be designed to
@ -1366,7 +1368,7 @@ pub struct Mixin {
/// if (duration.seconds < 0 && duration.nanos > 0) {
/// duration.seconds += 1;
/// duration.nanos -= 1000000000;
/// } else if (durations.seconds > 0 && duration.nanos < 0) {
/// } else if (duration.seconds > 0 && duration.nanos < 0) {
/// duration.seconds -= 1;
/// duration.nanos += 1000000000;
/// }
@ -1619,7 +1621,7 @@ pub struct Duration {
///
/// The implementation of any API method which has a FieldMask type field in the
/// request should verify the included field paths, and return an
/// `INVALID_ARGUMENT` error if any path is duplicated or unmappable.
/// `INVALID_ARGUMENT` error if any path is unmappable.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FieldMask {
/// The set of field mask paths.
@ -1768,11 +1770,13 @@ pub enum NullValue {
/// 01:30 UTC on January 15, 2017.
///
/// In JavaScript, one can convert a Date object to this format using the
/// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
/// standard
/// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
/// method. In Python, a standard `datetime.datetime` object can be converted
/// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
/// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
/// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
/// to this format using
/// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
/// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
/// the Joda Time's [`ISODateTimeFormat.dateTime()`](
/// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
/// ) to obtain a formatter capable of generating timestamps in this format.
///

View File

@ -6,7 +6,7 @@ publish = false
edition = "2018"
[dependencies]
bytes = "0.4.7"
bytes = "0.5"
prost = { path = ".." }
prost-types = { path = "../prost-types" }
@ -17,3 +17,14 @@ flate2 = "1.0"
prost-build = { path = "../prost-build" }
tar = "0.4"
tempfile = "3"
[dev-dependencies]
criterion = "0.3"
[lib]
# https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
bench = false
[[bench]]
name = "dataset"
harness = false

109
protobuf/benches/dataset.rs Normal file
View File

@ -0,0 +1,109 @@
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use criterion::{criterion_group, criterion_main, Criterion};
use prost::Message;
use protobuf::benchmarks::{
dataset, google_message3::GoogleMessage3, google_message4::GoogleMessage4, proto2, proto3,
BenchmarkDataset,
};
fn load_dataset(dataset: &Path) -> Result<BenchmarkDataset, Box<dyn Error>> {
let mut f = File::open(dataset)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
Ok(BenchmarkDataset::decode(&*buf)?)
}
fn benchmark_dataset<M>(criterion: &mut Criterion, name: &str, dataset: &'static Path)
where
M: prost::Message + Default + 'static,
{
criterion.bench_function(&format!("dataset/{}/merge", name), move |b| {
let dataset = load_dataset(dataset).unwrap();
let mut message = M::default();
b.iter(|| {
for buf in &dataset.payload {
message.clear();
message.merge(buf.as_slice()).unwrap();
criterion::black_box(&message);
}
});
});
criterion.bench_function(&format!("dataset/{}/encode", name), move |b| {
let messages = load_dataset(dataset)
.unwrap()
.payload
.iter()
.map(Vec::as_slice)
.map(M::decode)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut buf = Vec::with_capacity(messages.iter().map(M::encoded_len).sum::<usize>());
b.iter(|| {
buf.clear();
for message in &messages {
message.encode(&mut buf).unwrap();
}
criterion::black_box(&buf);
});
});
criterion.bench_function(&format!("dataset/{}/encoded_len", name), move |b| {
let messages = load_dataset(dataset)
.unwrap()
.payload
.iter()
.map(Vec::as_slice)
.map(M::decode)
.collect::<Result<Vec<_>, _>>()
.unwrap();
b.iter(|| {
let encoded_len = messages.iter().map(M::encoded_len).sum::<usize>();
criterion::black_box(encoded_len)
});
});
}
macro_rules! dataset {
($name: ident, $ty: ty) => {
fn $name(criterion: &mut Criterion) {
benchmark_dataset::<$ty>(criterion, stringify!($name), dataset::$name());
}
};
}
dataset!(google_message1_proto2, proto2::GoogleMessage1);
dataset!(google_message1_proto3, proto3::GoogleMessage1);
dataset!(google_message2, proto2::GoogleMessage2);
dataset!(google_message3_1, GoogleMessage3);
dataset!(google_message3_2, GoogleMessage3);
dataset!(google_message3_3, GoogleMessage3);
dataset!(google_message3_4, GoogleMessage3);
dataset!(google_message3_5, GoogleMessage3);
dataset!(google_message4, GoogleMessage4);
criterion_group!(
dataset,
google_message1_proto2,
google_message1_proto3,
google_message2,
);
criterion_group! {
name = slow;
config = Criterion::default().sample_size(10);
targets = google_message3_2, google_message3_4, google_message3_3
}
criterion_group! {
name = extra_slow;
config = Criterion::default().sample_size(3);
targets = google_message3_1, google_message3_5, google_message4
}
criterion_main!(dataset, slow, extra_slow);

View File

@ -12,7 +12,7 @@ use curl::easy::Easy;
use flate2::bufread::GzDecoder;
use tar::Archive;
const VERSION: &'static str = "3.7.1";
const VERSION: &'static str = "3.11.2";
static TEST_PROTOS: &[&str] = &[
"test_messages_proto2.proto",
@ -107,6 +107,7 @@ fn download_tarball(url: &str, out_dir: &Path) {
let mut data = Vec::new();
let mut handle = Easy::new();
// Download the tarball.
handle.url(url).expect("failed to configure tarball URL");
handle
.follow_location(true)
@ -122,6 +123,7 @@ fn download_tarball(url: &str, out_dir: &Path) {
transfer.perform().expect("failed to download tarball");
}
// Unpack the tarball.
Archive::new(GzDecoder::new(Cursor::new(data)))
.unpack(out_dir)
.expect("failed to unpack tarball");
@ -136,50 +138,58 @@ fn download_protobuf(out_dir: &Path) -> PathBuf {
),
out_dir,
);
out_dir.join(format!("protobuf-{}", VERSION))
let src_dir = out_dir.join(format!("protobuf-{}", VERSION));
// Apply patches.
let mut patch_src = env::current_dir().expect("failed to get current working directory");
patch_src.push("src");
patch_src.push("fix-conformance_test_runner-cmake-build.patch");
let rc = Command::new("patch")
.arg("-p1")
.arg("-i")
.arg(patch_src)
.current_dir(&src_dir)
.status()
.expect("failed to apply patch");
assert!(rc.success(), "protobuf patch failed");
src_dir
}
fn install_conformance_test_runner(src_dir: &Path, prefix_dir: &Path) {
#[cfg(not(windows))]
{
// Build and install protoc, the protobuf libraries, and the conformance test runner.
let rc = Command::new("./autogen.sh")
let rc = Command::new("cmake")
.arg("-GNinja")
.arg("cmake/")
.arg("-DCMAKE_BUILD_TYPE=DEBUG")
.arg(&format!("-DCMAKE_INSTALL_PREFIX={}", prefix_dir.display()))
.arg("-Dprotobuf_BUILD_CONFORMANCE=ON")
.arg("-Dprotobuf_BUILD_TESTS=OFF")
.current_dir(&src_dir)
.status()
.expect("failed to execute autogen.sh");
assert!(rc.success(), "protobuf autogen.sh failed");
.expect("failed to execute CMake");
assert!(rc.success(), "protobuf CMake failed");
let num_jobs = env::var("NUM_JOBS").expect("NUM_JOBS environment variable not set");
let rc = Command::new("./configure")
.arg("--disable-shared")
.arg("--prefix")
.arg(&prefix_dir)
.current_dir(&src_dir)
.status()
.expect("failed to execute configure");
assert!(rc.success(), "failed to configure protobuf");
let rc = Command::new("make")
let rc = Command::new("ninja")
.arg("-j")
.arg(&num_jobs)
.arg("install")
.current_dir(&src_dir)
.status()
.expect("failed to execute make protobuf");
.expect("failed to execute ninja protobuf");
assert!(rc.success(), "failed to make protobuf");
// Workaround for protocolbuffers/protobuf#6210.
fs::create_dir(src_dir.join("conformance").join("google-protobuf")).unwrap();
let rc = Command::new("make")
.arg("-j")
.arg(&num_jobs)
.arg("install")
.current_dir(src_dir.join("conformance"))
.status()
.expect("failed to execute make conformance");
assert!(rc.success(), "failed to make conformance");
// Install the conformance-test-runner binary, since it isn't done automatically.
fs::rename(
src_dir.join("conformance_test_runner"),
prefix_dir.join("bin").join("conformance-test-runner"),
)
.expect("failed to move conformance-test-runner");
}
}

View File

@ -0,0 +1,41 @@
From 104a483fbf3b87d42b0c3381049c72ab45d0f630 Mon Sep 17 00:00:00 2001
From: Dan Burkert <dan@danburkert.com>
Date: Sat, 11 Jan 2020 13:44:02 -0800
Subject: [PATCH] Fix conformance_test_runner CMake build
Makes the conformance_test_runner declared sources in
cmake/conformance.cmake match the corresponding sources in
conformance/Makefile.am, which allows the test runner to build
successfully with CMake.
---
cmake/conformance.cmake | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/cmake/conformance.cmake b/cmake/conformance.cmake
index 82b4cf580..7be713d33 100644
--- a/cmake/conformance.cmake
+++ b/cmake/conformance.cmake
@@ -19,14 +19,17 @@ add_custom_command(
)
add_executable(conformance_test_runner
- ${protobuf_source_dir}/conformance/conformance.pb.cc
+ ${protobuf_source_dir}/conformance/conformance_test.h
${protobuf_source_dir}/conformance/conformance_test.cc
- ${protobuf_source_dir}/conformance/binary_json_conformance_main.cc
- ${protobuf_source_dir}/conformance/binary_json_conformance_suite.cc
+ ${protobuf_source_dir}/conformance/conformance_test_main.cc
${protobuf_source_dir}/conformance/binary_json_conformance_suite.h
+ ${protobuf_source_dir}/conformance/binary_json_conformance_suite.cc
+ ${protobuf_source_dir}/conformance/text_format_conformance_suite.h
+ ${protobuf_source_dir}/conformance/text_format_conformance_suite.cc
${protobuf_source_dir}/conformance/conformance_test_runner.cc
${protobuf_source_dir}/conformance/third_party/jsoncpp/json.h
${protobuf_source_dir}/conformance/third_party/jsoncpp/jsoncpp.cpp
+ ${protobuf_source_dir}/conformance/conformance.pb.cc
${protobuf_source_dir}/src/google/protobuf/test_messages_proto3.pb.cc
${protobuf_source_dir}/src/google/protobuf/test_messages_proto2.pb.cc
)
--
2.21.1

View File

@ -1,47 +1,71 @@
pub mod benchmarks {
include!(concat!(env!("OUT_DIR"), "/benchmarks.rs"));
use std::path::Path;
pub mod dataset {
use std::path::Path;
pub fn datasets() -> Vec<&'static Path> {
vec![
pub fn google_message1_proto2() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message1_proto2.pb"
)),
))
}
pub fn google_message1_proto3() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message1_proto3.pb"
)),
))
}
pub fn google_message2() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message2.pb"
)),
))
}
pub fn google_message3_1() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message3_1.pb"
)),
))
}
pub fn google_message3_2() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message3_2.pb"
)),
))
}
pub fn google_message3_3() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message3_3.pb"
)),
))
}
pub fn google_message3_4() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message3_4.pb"
)),
))
}
pub fn google_message3_5() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message3_5.pb"
)),
))
}
pub fn google_message4() -> &'static Path {
Path::new(concat!(
env!("PROTOBUF"),
"/share/dataset.google_message4.pb"
)),
]
))
}
}
pub mod google_message3 {

26
publish-release.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
# Script which automates publishing a crates.io release of the prost crates.
set -ex
if [ "$#" -ne 0 ]
then
echo "Usage: $0"
exit 1
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
CRATES=( \
"prost-derive" \
"." \
"prost-types" \
"prost-build" \
)
for CRATE in "${CRATES[@]}"; do
pushd "$DIR/$CRATE"
cargo publish
popd
done

View File

@ -2,12 +2,17 @@
//!
//! Meant to be used only from `Message` implementations.
use std::cmp::min;
use std::mem;
use std::u32;
use std::usize;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::cmp::min;
use core::mem;
use core::str;
use core::u32;
use core::usize;
use ::bytes::{Buf, BufMut};
use ::bytes::{buf::ext::BufExt, Buf, BufMut};
use crate::DecodeError;
use crate::Message;
@ -21,9 +26,6 @@ where
{
// Safety notes:
//
// - bytes_mut is unsafe because it may return an uninitialized slice.
// The use here is safe because the slice is only written to, never read from.
//
// - advance_mut is unsafe because it could cause uninitialized memory to be
// advanced over. The use here is safe since each byte which is advanced over
// has been written to in the previous loop iteration.
@ -31,13 +33,13 @@ where
'outer: loop {
i = 0;
for byte in unsafe { buf.bytes_mut() } {
for byte in buf.bytes_mut() {
i += 1;
if value < 0x80 {
*byte = value as u8;
*byte = mem::MaybeUninit::new(value as u8);
break 'outer;
} else {
*byte = ((value & 0x7F) | 0x80) as u8;
*byte = mem::MaybeUninit::new(((value & 0x7F) | 0x80) as u8);
value >>= 7;
}
}
@ -280,11 +282,12 @@ pub enum WireType {
pub const MIN_TAG: u32 = 1;
pub const MAX_TAG: u32 = (1 << 29) - 1;
impl WireType {
// TODO: impl TryFrom<u64> when stable.
#[inline(always)]
pub fn try_from(val: u64) -> Result<WireType, DecodeError> {
match val {
impl TryFrom<u64> for WireType {
type Error = DecodeError;
#[inline]
fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
0 => Ok(WireType::Varint),
1 => Ok(WireType::SixtyFourBit),
2 => Ok(WireType::LengthDelimited),
@ -293,7 +296,7 @@ impl WireType {
5 => Ok(WireType::ThirtyTwoBit),
_ => Err(DecodeError::new(format!(
"invalid wire type value: {}",
val
value
))),
}
}
@ -381,10 +384,11 @@ where
Ok(())
}
pub fn skip_field<B>(wire_type: WireType, tag: u32, buf: &mut B) -> Result<(), DecodeError>
pub fn skip_field<B>(wire_type: WireType, tag: u32, buf: &mut B, ctx: DecodeContext) -> Result<(), DecodeError>
where
B: Buf,
{
ctx.limit_reached()?;
let len = match wire_type {
WireType::Varint => decode_varint(buf).map(|_| 0)?,
WireType::ThirtyTwoBit => 4,
@ -399,7 +403,7 @@ where
}
break 0;
}
_ => skip_field(inner_wire_type, inner_tag, buf)?,
_ => skip_field(inner_wire_type, inner_tag, buf, ctx.enter_recursion())?,
}
},
WireType::EndGroup => return Err(DecodeError::new("unexpected end group tag")),
@ -820,11 +824,41 @@ pub mod string {
where
B: Buf,
{
let mut value_bytes = mem::replace(value, String::new()).into_bytes();
bytes::merge(wire_type, &mut value_bytes, buf, ctx)?;
*value = String::from_utf8(value_bytes)
.map_err(|_| DecodeError::new("invalid string value: data is not UTF-8 encoded"))?;
Ok(())
// ## Unsafety
//
// `string::merge` reuses `bytes::merge`, with an additional check of utf-8
// well-formedness. If the utf-8 is not well-formed, or if any other error occurs, then the
// string is cleared, so as to avoid leaking a string field with invalid data.
//
// This implementation uses the unsafe `String::as_mut_vec` method instead of the safe
// alternative of temporarily swapping an empty `String` into the field, because it results
// in up to 10% better performance on the protobuf message decoding benchmarks.
//
// It's required when using `String::as_mut_vec` that invalid utf-8 data not be leaked into
// the backing `String`. To enforce this, even in the event of a panic in `bytes::merge` or
// in the buf implementation, a drop guard is used.
unsafe {
struct DropGuard<'a>(&'a mut Vec<u8>);
impl<'a> Drop for DropGuard<'a> {
#[inline]
fn drop(&mut self) {
self.0.clear();
}
}
let drop_guard = DropGuard(value.as_mut_vec());
bytes::merge(wire_type, drop_guard.0, buf, ctx)?;
match str::from_utf8(drop_guard.0) {
Ok(_) => {
// Success; do not clear the bytes.
mem::forget(drop_guard);
Ok(())
}
Err(_) => Err(DecodeError::new(
"invalid string value: data is not UTF-8 encoded",
)),
}
}
}
length_delimited!(String);
@ -857,6 +891,16 @@ pub mod bytes {
return Err(DecodeError::new("buffer underflow"));
}
let len = len as usize;
// Clear the existing value. This follows from the following rule in the encoding guide[1]:
//
// > Normally, an encoded message would never have more than one instance of a non-repeated
// > field. However, parsers are expected to handle the case in which they do. For numeric
// > types and strings, if the same field appears multiple times, the parser accepts the
// > last value it sees.
//
// [1]: https://developers.google.com/protocol-buffers/docs/encoding#optional
value.clear();
value.reserve(len);
value.put(buf.take(len));
Ok(())
@ -1040,8 +1084,7 @@ pub mod group {
/// generic over `HashMap` and `BTreeMap`.
macro_rules! map {
($map_ty:ident) => {
use std::collections::$map_ty;
use std::hash::Hash;
use core::hash::Hash;
use crate::encoding::*;
@ -1179,7 +1222,7 @@ macro_rules! map {
match tag {
1 => key_merge(wire_type, key, buf, ctx),
2 => val_merge(wire_type, val, buf, ctx),
_ => skip_field(wire_type, tag, buf),
_ => skip_field(wire_type, tag, buf, ctx),
}
},
)?;
@ -1225,11 +1268,14 @@ macro_rules! map {
};
}
#[cfg(feature = "std")]
pub mod hash_map {
use std::collections::HashMap;
map!(HashMap);
}
pub mod btree_map {
use alloc::collections::BTreeMap;
map!(BTreeMap);
}
@ -1240,7 +1286,7 @@ mod test {
use std::io::Cursor;
use std::u64;
use ::bytes::{Bytes, BytesMut, IntoBuf};
use ::bytes::{Bytes, BytesMut};
use quickcheck::TestResult;
use crate::encoding::*;
@ -1250,7 +1296,7 @@ mod test {
tag: u32,
wire_type: WireType,
encode: fn(u32, &B, &mut BytesMut),
merge: fn(WireType, &mut T, &mut Cursor<Bytes>, DecodeContext) -> Result<(), DecodeError>,
merge: fn(WireType, &mut T, &mut Bytes, DecodeContext) -> Result<(), DecodeError>,
encoded_len: fn(u32, &B) -> usize,
) -> TestResult
where
@ -1266,7 +1312,7 @@ mod test {
let mut buf = BytesMut::with_capacity(expected_len);
encode(tag, value.borrow(), &mut buf);
let mut buf = buf.freeze().into_buf();
let mut buf = buf.freeze();
if buf.remaining() != expected_len {
return TestResult::error(format!(
@ -1354,7 +1400,7 @@ mod test {
T: Debug + Default + PartialEq + Borrow<B>,
B: ?Sized,
E: FnOnce(u32, &B, &mut BytesMut),
M: FnMut(WireType, &mut T, &mut Cursor<Bytes>, DecodeContext) -> Result<(), DecodeError>,
M: FnMut(WireType, &mut T, &mut Bytes, DecodeContext) -> Result<(), DecodeError>,
L: FnOnce(u32, &B) -> usize,
{
if tag > MAX_TAG || tag < MIN_TAG {
@ -1366,7 +1412,7 @@ mod test {
let mut buf = BytesMut::with_capacity(expected_len);
encode(tag, value.borrow(), &mut buf);
let mut buf = buf.freeze().into_buf();
let mut buf = buf.freeze();
if buf.remaining() != expected_len {
return TestResult::error(format!(
@ -1415,9 +1461,10 @@ mod test {
}
#[test]
fn string_merge_failure() {
fn string_merge_invalid_utf8() {
let mut s = String::new();
let mut buf = Cursor::new(b"\x80\x80");
let mut buf = Cursor::new(b"\x02\x80\x80");
let r = string::merge(
WireType::LengthDelimited,
&mut s,
@ -1430,7 +1477,7 @@ mod test {
#[test]
fn varint() {
fn check(value: u64, encoded: &[u8]) {
fn check(value: u64, mut encoded: &[u8]) {
// Small buffer.
let mut buf = Vec::with_capacity(1);
encode_varint(value, &mut buf);
@ -1443,11 +1490,11 @@ mod test {
assert_eq!(encoded_len_varint(value), encoded.len());
let roundtrip_value = decode_varint(&mut encoded.into_buf()).expect("decoding failed");
let roundtrip_value = decode_varint(&mut encoded.clone()).expect("decoding failed");
assert_eq!(value, roundtrip_value);
let roundtrip_value =
decode_varint_slow(&mut encoded.into_buf()).expect("slow decoding failed");
println!("encoding {:?}", encoded);
let roundtrip_value = decode_varint_slow(&mut encoded).expect("slow decoding failed");
assert_eq!(value, roundtrip_value);
}

View File

@ -1,8 +1,11 @@
//! Protobuf encoding and decoding errors.
use std::borrow::Cow;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::fmt;
#[cfg(feature = "std")]
use std::error;
use std::fmt;
#[cfg(feature = "std")]
use std::io;
/// A Protobuf message decoding error.
@ -54,12 +57,10 @@ impl fmt::Display for DecodeError {
}
}
impl error::Error for DecodeError {
fn description(&self) -> &str {
&self.description
}
}
#[cfg(feature = "std")]
impl error::Error for DecodeError {}
#[cfg(feature = "std")]
impl From<DecodeError> for io::Error {
fn from(error: DecodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, error)
@ -99,21 +100,18 @@ impl EncodeError {
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(error::Error::description(self))?;
write!(
f,
" (required: {}, remaining: {})",
"failed to encode Protobuf messsage; insufficient buffer capacity (required: {}, remaining: {})",
self.required, self.remaining
)
}
}
impl error::Error for EncodeError {
fn description(&self) -> &str {
"failed to encode Protobuf message: insufficient buffer capacity"
}
}
#[cfg(feature = "std")]
impl error::Error for EncodeError {}
#[cfg(feature = "std")]
impl From<EncodeError> for io::Error {
fn from(error: EncodeError) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, error)

View File

@ -1,4 +1,13 @@
#![doc(html_root_url = "https://docs.rs/prost/0.5.0")]
#![doc(html_root_url = "https://docs.rs/prost/0.6.1")]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(feature = "std")]
extern crate core;
mod error;
mod message;
@ -10,7 +19,7 @@ pub mod encoding;
pub use crate::error::{DecodeError, EncodeError};
pub use crate::message::Message;
use bytes::{BufMut, IntoBuf};
use bytes::{Buf, BufMut};
use crate::encoding::{decode_varint, encode_varint, encoded_len_varint};
@ -58,11 +67,10 @@ pub fn length_delimiter_len(length: usize) -> usize {
/// input is required to decode the full delimiter.
/// * If the supplied buffer contains more than 10 bytes, then the buffer contains an invalid
/// delimiter, and typically the buffer should be considered corrupt.
pub fn decode_length_delimiter<B>(buf: B) -> Result<usize, DecodeError>
pub fn decode_length_delimiter<B>(mut buf: B) -> Result<usize, DecodeError>
where
B: IntoBuf,
B: Buf,
{
let mut buf = buf.into_buf();
let length = decode_varint(&mut buf)?;
if length > usize::max_value() as u64 {
return Err(DecodeError::new(
@ -82,4 +90,7 @@ where
extern crate prost_derive;
#[cfg(feature = "prost-derive")]
#[doc(hidden)]
pub use bytes;
#[cfg(feature = "prost-derive")]
#[doc(hidden)]
pub use prost_derive::*;

View File

@ -1,9 +1,12 @@
use std::fmt::Debug;
use std::usize;
use alloc::boxed::Box;
use core::fmt::Debug;
use core::usize;
use ::bytes::{Buf, BufMut, IntoBuf};
use bytes::{Buf, BufMut};
use crate::encoding::*;
use crate::encoding::{
decode_key, encode_varint, encoded_len_varint, message, DecodeContext, WireType,
};
use crate::DecodeError;
use crate::EncodeError;
@ -78,19 +81,19 @@ pub trait Message: Debug + Send + Sync {
/// Decodes an instance of the message from a buffer.
///
/// The entire buffer will be consumed.
fn decode<B>(buf: B) -> Result<Self, DecodeError>
fn decode<B>(mut buf: B) -> Result<Self, DecodeError>
where
B: IntoBuf,
B: Buf,
Self: Default,
{
let mut message = Self::default();
Self::merge(&mut message, &mut buf.into_buf()).map(|_| message)
Self::merge(&mut message, &mut buf).map(|_| message)
}
/// Decodes a length-delimited instance of the message from the buffer.
fn decode_length_delimited<B>(buf: B) -> Result<Self, DecodeError>
where
B: IntoBuf,
B: Buf,
Self: Default,
{
let mut message = Self::default();
@ -101,12 +104,11 @@ pub trait Message: Debug + Send + Sync {
/// Decodes an instance of the message from a buffer, and merges it into `self`.
///
/// The entire buffer will be consumed.
fn merge<B>(&mut self, buf: B) -> Result<(), DecodeError>
fn merge<B>(&mut self, mut buf: B) -> Result<(), DecodeError>
where
B: IntoBuf,
B: Buf,
Self: Sized,
{
let mut buf = buf.into_buf();
let ctx = DecodeContext::default();
while buf.has_remaining() {
let (tag, wire_type) = decode_key(&mut buf)?;
@ -117,15 +119,15 @@ pub trait Message: Debug + Send + Sync {
/// Decodes a length-delimited instance of the message from buffer, and
/// merges it into `self`.
fn merge_length_delimited<B>(&mut self, buf: B) -> Result<(), DecodeError>
fn merge_length_delimited<B>(&mut self, mut buf: B) -> Result<(), DecodeError>
where
B: IntoBuf,
B: Buf,
Self: Sized,
{
message::merge(
WireType::LengthDelimited,
self,
&mut buf.into_buf(),
&mut buf,
DecodeContext::default(),
)
}

View File

@ -5,11 +5,18 @@
//! the `prost-types` crate in order to avoid a cyclic dependency between `prost` and
//! `prost-build`.
use alloc::string::String;
use alloc::vec::Vec;
use ::bytes::{Buf, BufMut};
use crate::encoding::*;
use crate::DecodeError;
use crate::Message;
use crate::{
encoding::{
bool, bytes, double, float, int32, int64, skip_field, string, uint32, uint64,
DecodeContext, WireType,
},
DecodeError, Message,
};
/// `google.protobuf.BoolValue`
impl Message for bool {
@ -34,7 +41,7 @@ impl Message for bool {
if tag == 1 {
bool::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -72,7 +79,7 @@ impl Message for u32 {
if tag == 1 {
uint32::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -110,7 +117,7 @@ impl Message for u64 {
if tag == 1 {
uint64::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -148,7 +155,7 @@ impl Message for i32 {
if tag == 1 {
int32::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -186,7 +193,7 @@ impl Message for i64 {
if tag == 1 {
int64::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -224,7 +231,7 @@ impl Message for f32 {
if tag == 1 {
float::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -262,7 +269,7 @@ impl Message for f64 {
if tag == 1 {
double::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -300,7 +307,7 @@ impl Message for String {
if tag == 1 {
string::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -338,7 +345,7 @@ impl Message for Vec<u8> {
if tag == 1 {
bytes::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
@ -365,12 +372,12 @@ impl Message for () {
tag: u32,
wire_type: WireType,
buf: &mut B,
_ctx: DecodeContext,
ctx: DecodeContext,
) -> Result<(), DecodeError>
where
B: Buf,
{
skip_field(wire_type, tag, buf)
skip_field(wire_type, tag, buf, ctx)
}
fn encoded_len(&self) -> usize {
0

View File

@ -16,7 +16,7 @@ default = ["edition-2015"]
edition-2015 = []
[dependencies]
bytes = "0.4.7"
bytes = "0.5"
cfg-if = "0.1"
prost = { path = ".." }
prost-types = { path = "../prost-types" }
@ -29,6 +29,6 @@ tempfile = "3"
[build-dependencies]
cfg-if = "0.1"
env_logger = { version = "0.6", default-features = false }
env_logger = { version = "0.7", default-features = false }
prost-build = { path = "../prost-build" }
protobuf = { path = "../protobuf" }

View File

@ -11,7 +11,7 @@ build = "src/build.rs"
doctest = false
[dependencies]
bytes = "0.4.7"
bytes = "0.5"
cfg-if = "0.1"
prost = { path = ".." }
prost-types = { path = "../prost-types" }
@ -24,6 +24,6 @@ tempfile = "3"
[build-dependencies]
cfg-if = "0.1"
env_logger = { version = "0.6", default-features = false }
env_logger = { version = "0.7", default-features = false }
prost-build = { path = "../prost-build" }
protobuf = { path = "../protobuf" }

View File

@ -88,6 +88,12 @@ fn main() {
)
.unwrap();
// Check that attempting to compile a .proto without a package declaration results in an error.
config
.compile_protos(&[src.join("no_package.proto")], includes)
.err()
.unwrap();
let out_dir =
&PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set"))
.join("extern_paths");

View File

@ -76,7 +76,7 @@ pub mod groups {
use std::error::Error;
use bytes::{Buf, IntoBuf};
use bytes::Buf;
use prost::Message;
@ -145,7 +145,7 @@ where
);
}
let roundtrip = match M::decode(&buf1) {
let roundtrip = match M::decode(&*buf1) {
Ok(roundtrip) => roundtrip,
Err(error) => return RoundtripResult::Error(error.into()),
};
@ -180,7 +180,7 @@ where
msg.encode(&mut buf).unwrap();
assert_eq!(expected_len, buf.len());
let mut buf = buf.into_buf();
let mut buf = &*buf;
let roundtrip = M::decode(&mut buf).unwrap();
if buf.has_remaining() {
@ -355,7 +355,7 @@ mod tests {
let mut buf = Vec::new();
a.encode(&mut buf).unwrap();
A::decode(buf).map(|_| ())
A::decode(&*buf).map(|_| ())
}
assert!(build_and_roundtrip(100).is_ok());
@ -378,7 +378,7 @@ mod tests {
let mut buf = Vec::new();
a.encode(&mut buf).unwrap();
A::decode(buf).map(|_| ())
A::decode(&*buf).map(|_| ())
}
assert!(build_and_roundtrip(99).is_ok());
@ -401,7 +401,7 @@ mod tests {
let mut buf = Vec::new();
a.encode(&mut buf).unwrap();
NestedGroup2::decode(buf).map(|_| ())
NestedGroup2::decode(&*buf).map(|_| ())
}
assert!(build_and_roundtrip(50).is_ok());
@ -422,7 +422,7 @@ mod tests {
let mut buf = Vec::new();
c.encode(&mut buf).unwrap();
C::decode(buf).map(|_| ())
C::decode(&*buf).map(|_| ())
}
assert!(build_and_roundtrip(100).is_ok());
@ -443,7 +443,7 @@ mod tests {
let mut buf = Vec::new();
d.encode(&mut buf).unwrap();
D::decode(buf).map(|_| ())
D::decode(&*buf).map(|_| ())
}
assert!(build_and_roundtrip(50).is_ok());
@ -462,6 +462,16 @@ mod tests {
};
}
#[test]
fn test_267_regression() {
// Checks that skip_field will error appropriately when given a big stack of StartGroup
// tags.
//
// https://github.com/danburkert/prost/issues/267
let buf = vec![b'C'; 1 << 20];
<() as Message>::decode(&buf[..]).err().unwrap();
}
#[test]
fn test_default_enum() {
let msg = default_enum_value::Test::default();

View File

@ -0,0 +1 @@
syntax = "proto3";