From a1cccbcee343e2c444e1cd2738c7fba2599fc391 Mon Sep 17 00:00:00 2001 From: Dan Burkert Date: Sat, 18 Jul 2020 14:46:17 -0700 Subject: [PATCH] Optional support for bytes::Bytes type backing bytes field (#341) * Optional support for Bytes type backing bytes field. * Use actual Bytes type during build. * Run rustfmt. * Address clippy lints. * Clean up code generation logic. * Address fmt. * Update types. * Reinstate missing reserve(). * Rework trait to be private and clarify current behaviour. * Use trait bounds where possible. * Add link to related issue from bytes crate. * Add tests for bytes. * minor comment cleanup * fix default bytes values * clippy * fmt Co-authored-by: Rolf Timmermans --- prost-build/src/code_generator.rs | 50 +++++++-- prost-build/src/lib.rs | 59 +++++++++++ prost-derive/src/field/scalar.rs | 72 +++++++++---- prost-types/src/protobuf.rs | 4 +- src/encoding.rs | 169 +++++++++++++++++++++++++----- src/types.rs | 40 ++++++- tests/src/message_encoding.rs | 39 +++++-- 7 files changed, 366 insertions(+), 67 deletions(-) diff --git a/prost-build/src/code_generator.rs b/prost-build/src/code_generator.rs index 7323eb613..2754a8e77 100644 --- a/prost-build/src/code_generator.rs +++ b/prost-build/src/code_generator.rs @@ -26,6 +26,21 @@ enum Syntax { Proto3, } +#[derive(PartialEq)] +enum BytesTy { + Vec, + Bytes, +} + +impl BytesTy { + fn as_str(&self) -> &'static str { + match self { + BytesTy::Vec => "\"vec\"", + BytesTy::Bytes => "\"bytes\"", + } + } +} + pub struct CodeGenerator<'a> { config: &'a mut Config, package: String, @@ -277,7 +292,7 @@ impl<'a> CodeGenerator<'a> { let repeated = field.label == Some(Label::Repeated as i32); let deprecated = self.deprecated(&field); let optional = self.optional(&field); - let ty = self.resolve_type(&field); + let ty = self.resolve_type(&field, msg_name); let boxed = !repeated && (type_ == Type::Message || type_ == Type::Group) @@ -302,6 +317,12 @@ impl<'a> CodeGenerator<'a> { let type_tag = self.field_type_tag(&field); self.buf.push_str(&type_tag); + if type_ == Type::Bytes { + self.buf.push_str("="); + self.buf + .push_str(self.bytes_backing_type(&field, msg_name).as_str()); + } + match field.label() { Label::Optional => { if optional { @@ -394,8 +415,8 @@ impl<'a> CodeGenerator<'a> { key: &FieldDescriptorProto, value: &FieldDescriptorProto, ) { - let key_ty = self.resolve_type(key); - let value_ty = self.resolve_type(value); + let key_ty = self.resolve_type(key, msg_name); + let value_ty = self.resolve_type(value, msg_name); debug!( " map field: {:?}, key type: {:?}, value type: {:?}", @@ -420,6 +441,7 @@ impl<'a> CodeGenerator<'a> { let key_tag = self.field_type_tag(key); let value_tag = self.map_value_type_tag(value); + self.buf.push_str(&format!( "#[prost({}=\"{}, {}\", tag=\"{}\")]\n", annotation_ty, @@ -512,7 +534,7 @@ impl<'a> CodeGenerator<'a> { self.append_field_attributes(&oneof_name, field.name()); self.push_indent(); - let ty = self.resolve_type(&field); + let ty = self.resolve_type(&field, msg_name); let boxed = (type_ == Type::Message || type_ == Type::Group) && self.message_graph.is_nested(field.type_name(), msg_name); @@ -715,7 +737,7 @@ impl<'a> CodeGenerator<'a> { self.buf.push_str("}\n"); } - fn resolve_type(&self, field: &FieldDescriptorProto) -> String { + fn resolve_type(&self, field: &FieldDescriptorProto, msg_name: &str) -> String { match field.r#type() { Type::Float => String::from("f32"), Type::Double => String::from("f64"), @@ -725,7 +747,10 @@ impl<'a> CodeGenerator<'a> { Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"), Type::Bool => String::from("bool"), Type::String => String::from("::prost::alloc::string::String"), - Type::Bytes => String::from("::prost::alloc::vec::Vec"), + Type::Bytes => match self.bytes_backing_type(field, msg_name) { + BytesTy::Bytes => String::from("::prost::bytes::Bytes"), + BytesTy::Vec => String::from("::prost::alloc::vec::Vec"), + }, Type::Group | Type::Message => self.resolve_ident(field.type_name()), } } @@ -804,6 +829,19 @@ impl<'a> CodeGenerator<'a> { } } + fn bytes_backing_type(&self, field: &FieldDescriptorProto, msg_name: &str) -> BytesTy { + let bytes = self + .config + .bytes + .iter() + .any(|matcher| match_ident(matcher, msg_name, Some(field.name()))); + if bytes { + BytesTy::Bytes + } else { + BytesTy::Vec + } + } + /// Returns `true` if the field options includes the `deprecated` option. fn deprecated(&self, field: &FieldDescriptorProto) -> bool { field diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 72c66f5f4..e9042e904 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -184,6 +184,7 @@ pub trait ServiceGenerator { pub struct Config { service_generator: Option>, btree_map: Vec, + bytes: Vec, type_attributes: Vec<(String, String)>, field_attributes: Vec<(String, String)>, prost_types: bool, @@ -255,6 +256,63 @@ impl Config { self } + /// Configure the code generator to generate Rust [`bytes::Bytes`][1] fields for Protobuf + /// [`bytes`][2] type fields. + /// + /// # Arguments + /// + /// **`paths`** - paths to specific fields, messages, or packages which should use a Rust + /// `Bytes` for Protobuf `bytes` fields. Paths are specified in terms of the Protobuf type + /// name (not the generated Rust type name). Paths with a leading `.` are treated as fully + /// qualified names. Paths without a leading `.` are treated as relative, and are suffix + /// matched on the fully qualified field name. If a Protobuf map field matches any of the + /// paths, a Rust `Bytes` field is generated instead of the default [`Vec`][3]. + /// + /// The matching is done on the Protobuf names, before converting to Rust-friendly casing + /// standards. + /// + /// # Examples + /// + /// ```rust + /// # let mut config = prost_build::Config::new(); + /// // Match a specific field in a message type. + /// config.bytes(&[".my_messages.MyMessageType.my_bytes_field"]); + /// + /// // Match all bytes fields in a message type. + /// config.bytes(&[".my_messages.MyMessageType"]); + /// + /// // Match all bytes fields in a package. + /// config.bytes(&[".my_messages"]); + /// + /// // Match all bytes fields. Expecially useful in `no_std` contexts. + /// config.bytes(&["."]); + /// + /// // Match all bytes fields in a nested message. + /// config.bytes(&[".my_messages.MyMessageType.MyNestedMessageType"]); + /// + /// // Match all fields named 'my_bytes_field'. + /// config.bytes(&["my_bytes_field"]); + /// + /// // Match all fields named 'my_bytes_field' in messages named 'MyMessageType', regardless of + /// // package or nesting. + /// config.bytes(&["MyMessageType.my_bytes_field"]); + /// + /// // Match all fields named 'my_bytes_field', and all fields in the 'foo.bar' package. + /// config.bytes(&["my_bytes_field", ".foo.bar"]); + /// ``` + /// + /// [1]: https://docs.rs/bytes/latest/bytes/struct.Bytes.html + /// [2]: https://developers.google.com/protocol-buffers/docs/proto3#scalar + /// [3]: https://doc.rust-lang.org/std/vec/struct.Vec.html + pub fn bytes(&mut self, paths: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + self.bytes = paths.into_iter().map(|s| s.as_ref().to_string()).collect(); + self + } + /// Add additional attribute to matched fields. /// /// # Arguments @@ -626,6 +684,7 @@ impl default::Default for Config { Config { service_generator: None, btree_map: Vec::new(), + bytes: Vec::new(), type_attributes: Vec::new(), field_attributes: Vec::new(), prost_types: true, diff --git a/prost-derive/src/field/scalar.rs b/prost-derive/src/field/scalar.rs index de6f51e3a..e374e4fbf 100644 --- a/prost-derive/src/field/scalar.rs +++ b/prost-derive/src/field/scalar.rs @@ -3,7 +3,7 @@ use std::fmt; use anyhow::{anyhow, bail, Error}; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::{parse_str, Ident, Lit, LitByteStr, Meta, MetaList, MetaNameValue, NestedMeta, Path}; use crate::field::{bool_attr, set_option, tag_attr, Label}; @@ -194,7 +194,7 @@ impl Field { Kind::Plain(ref default) | Kind::Required(ref default) => { let default = default.typed(); match self.ty { - Ty::String | Ty::Bytes => quote!(#ident.clear()), + Ty::String | Ty::Bytes(..) => quote!(#ident.clear()), _ => quote!(#ident = #default), } } @@ -381,10 +381,33 @@ pub enum Ty { Sfixed64, Bool, String, - Bytes, + Bytes(BytesTy), Enumeration(Path), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BytesTy { + Vec, + Bytes, +} + +impl BytesTy { + fn try_from_str(s: &str) -> Result { + match s { + "vec" => Ok(BytesTy::Vec), + "bytes" => Ok(BytesTy::Bytes), + _ => bail!("Invalid bytes type: {}", s), + } + } + + fn rust_type(&self) -> TokenStream { + match self { + BytesTy::Vec => quote! { ::prost::alloc::vec::Vec }, + BytesTy::Bytes => quote! { ::prost::bytes::Bytes }, + } + } +} + impl Ty { pub fn from_attr(attr: &Meta) -> Result, Error> { let ty = match *attr { @@ -402,7 +425,12 @@ impl Ty { 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::Path(ref name) if name.is_ident("bytes") => Ty::Bytes(BytesTy::Vec), + Meta::NameValue(MetaNameValue { + ref path, + lit: Lit::Str(ref l), + .. + }) if path.is_ident("bytes") => Ty::Bytes(BytesTy::try_from_str(&l.value())?), Meta::NameValue(MetaNameValue { ref path, lit: Lit::Str(ref l), @@ -447,7 +475,7 @@ impl Ty { "sfixed64" => Ty::Sfixed64, "bool" => Ty::Bool, "string" => Ty::String, - "bytes" => Ty::Bytes, + "bytes" => Ty::Bytes(BytesTy::Vec), s if s.len() > enumeration_len && &s[..enumeration_len] == "enumeration" => { let s = &s[enumeration_len..].trim(); match s.chars().next() { @@ -483,16 +511,16 @@ impl Ty { Ty::Sfixed64 => "sfixed64", Ty::Bool => "bool", Ty::String => "string", - Ty::Bytes => "bytes", + Ty::Bytes(..) => "bytes", Ty::Enumeration(..) => "enum", } } // TODO: rename to 'owned_type'. pub fn rust_type(&self) -> TokenStream { - match *self { + match self { Ty::String => quote!(::prost::alloc::string::String), - Ty::Bytes => quote!(::prost::alloc::vec::Vec), + Ty::Bytes(ty) => ty.rust_type(), _ => self.rust_ref_type(), } } @@ -514,7 +542,7 @@ impl Ty { Ty::Sfixed64 => quote!(i64), Ty::Bool => quote!(bool), Ty::String => quote!(&str), - Ty::Bytes => quote!(&[u8]), + Ty::Bytes(..) => quote!(&[u8]), Ty::Enumeration(..) => quote!(i32), } } @@ -526,9 +554,12 @@ impl Ty { } } - /// Returns true if the scalar type is length delimited (i.e., `string` or `bytes`). + /// Returns false if the scalar type is length delimited (i.e., `string` or `bytes`). pub fn is_numeric(&self) -> bool { - *self != Ty::String && *self != Ty::Bytes + match self { + Ty::String | Ty::Bytes(..) => false, + _ => true, + } } } @@ -621,7 +652,11 @@ impl DefaultValue { Lit::Bool(ref lit) if *ty == Ty::Bool => DefaultValue::Bool(lit.value), 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::ByteStr(ref lit) + if *ty == Ty::Bytes(BytesTy::Bytes) || *ty == Ty::Bytes(BytesTy::Vec) => + { + DefaultValue::Bytes(lit.value()) + } Lit::Str(ref lit) => { let value = lit.value(); @@ -734,7 +769,7 @@ impl DefaultValue { Ty::Bool => DefaultValue::Bool(false), Ty::String => DefaultValue::String(String::new()), - Ty::Bytes => DefaultValue::Bytes(Vec::new()), + Ty::Bytes(..) => DefaultValue::Bytes(Vec::new()), Ty::Enumeration(ref path) => DefaultValue::Enumeration(quote!(#path::default())), } } @@ -744,13 +779,11 @@ impl DefaultValue { DefaultValue::String(ref value) if value.is_empty() => { quote!(::prost::alloc::string::String::new()) } - DefaultValue::String(ref value) => quote!(#value.to_owned()), - DefaultValue::Bytes(ref value) if value.is_empty() => { - quote!(::prost::alloc::vec::Vec::new()) - } + DefaultValue::String(ref value) => quote!(#value.into()), + DefaultValue::Bytes(ref value) if value.is_empty() => quote!(Default::default()), DefaultValue::Bytes(ref value) => { let lit = LitByteStr::new(value, Span::call_site()); - quote!(#lit.to_owned()) + quote!(#lit.as_ref().into()) } ref other => other.typed(), @@ -778,7 +811,8 @@ impl ToTokens for DefaultValue { DefaultValue::Bool(value) => value.to_tokens(tokens), DefaultValue::String(ref value) => value.to_tokens(tokens), DefaultValue::Bytes(ref value) => { - LitByteStr::new(value, Span::call_site()).to_tokens(tokens) + let byte_str = LitByteStr::new(value, Span::call_site()); + tokens.append_all(quote!(#byte_str as &[u8])); } DefaultValue::Enumeration(ref value) => value.to_tokens(tokens), DefaultValue::Path(ref value) => value.to_tokens(tokens), diff --git a/prost-types/src/protobuf.rs b/prost-types/src/protobuf.rs index 6649f7634..1b1f5c56b 100644 --- a/prost-types/src/protobuf.rs +++ b/prost-types/src/protobuf.rs @@ -694,7 +694,7 @@ pub struct UninterpretedOption { pub negative_int_value: ::core::option::Option, #[prost(double, optional, tag="6")] pub double_value: ::core::option::Option, - #[prost(bytes, optional, tag="7")] + #[prost(bytes="vec", optional, tag="7")] pub string_value: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(string, optional, tag="8")] pub aggregate_value: ::core::option::Option<::prost::alloc::string::String>, @@ -1003,7 +1003,7 @@ pub struct Any { #[prost(string, tag="1")] pub type_url: ::prost::alloc::string::String, /// Must be a valid serialized protocol buffer of the above specified type. - #[prost(bytes, tag="2")] + #[prost(bytes="vec", tag="2")] pub value: ::prost::alloc::vec::Vec, } /// `SourceContext` represents information about the source of a diff --git a/src/encoding.rs b/src/encoding.rs index cf9a25965..423665c6c 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -15,7 +15,7 @@ use core::str; use core::u32; use core::usize; -use ::bytes::{buf::ext::BufExt, Buf, BufMut}; +use ::bytes::{buf::ext::BufExt, Buf, BufMut, Bytes}; use crate::DecodeError; use crate::Message; @@ -789,26 +789,6 @@ macro_rules! length_delimited { .map(|value| encoded_len_varint(value.len() as u64) + value.len()) .sum::() } - - #[cfg(test)] - mod test { - use quickcheck::{quickcheck, TestResult}; - - use super::super::test::{check_collection_type, check_type}; - use super::*; - - quickcheck! { - fn check(value: $ty, tag: u32) -> TestResult { - super::test::check_type(value, tag, WireType::LengthDelimited, - encode, merge, encoded_len) - } - fn check_repeated(value: Vec<$ty>, tag: u32) -> TestResult { - super::test::check_collection_type(value, tag, WireType::LengthDelimited, - encode_repeated, merge_repeated, - encoded_len_repeated) - } - } - } }; } @@ -870,27 +850,121 @@ pub mod string { } length_delimited!(String); + + #[cfg(test)] + mod test { + use quickcheck::{quickcheck, TestResult}; + + use super::super::test::{check_collection_type, check_type}; + use super::*; + + quickcheck! { + fn check(value: String, tag: u32) -> TestResult { + super::test::check_type(value, tag, WireType::LengthDelimited, + encode, merge, encoded_len) + } + fn check_repeated(value: Vec, tag: u32) -> TestResult { + super::test::check_collection_type(value, tag, WireType::LengthDelimited, + encode_repeated, merge_repeated, + encoded_len_repeated) + } + } + } +} + +pub trait BytesAdapter: sealed::BytesAdapter {} + +mod sealed { + use super::{Buf, BufMut}; + + pub trait BytesAdapter: Default + Sized + 'static { + fn len(&self) -> usize; + + /// Replace contents of this buffer with the contents of another buffer. + fn replace_with(&mut self, buf: B) + where + B: Buf; + + /// Appends this buffer to the (contents of) other buffer. + fn append_to(&self, buf: &mut B) + where + B: BufMut; + + fn is_empty(&self) -> bool { + self.len() == 0 + } + } +} + +impl BytesAdapter for Bytes {} + +impl sealed::BytesAdapter for Bytes { + fn len(&self) -> usize { + Buf::remaining(self) + } + + fn replace_with(&mut self, mut buf: B) + where + B: Buf, + { + // TODO(tokio-rs/bytes#374): use a get_bytes(..)-like API to enable zero-copy merge + // when possible. + *self = buf.to_bytes(); + } + + fn append_to(&self, buf: &mut B) + where + B: BufMut, + { + buf.put(self.clone()) + } +} + +impl BytesAdapter for Vec {} + +impl sealed::BytesAdapter for Vec { + fn len(&self) -> usize { + Vec::len(self) + } + + fn replace_with(&mut self, buf: B) + where + B: Buf, + { + self.clear(); + self.reserve(buf.remaining()); + self.put(buf); + } + + fn append_to(&self, buf: &mut B) + where + B: BufMut, + { + buf.put(self.as_slice()) + } } pub mod bytes { use super::*; - pub fn encode(tag: u32, value: &Vec, buf: &mut B) + pub fn encode(tag: u32, value: &A, buf: &mut B) where + A: BytesAdapter, B: BufMut, { encode_key(tag, WireType::LengthDelimited, buf); encode_varint(value.len() as u64, buf); - buf.put_slice(value); + value.append_to(buf); } - pub fn merge( + pub fn merge( wire_type: WireType, - value: &mut Vec, + value: &mut A, buf: &mut B, _ctx: DecodeContext, ) -> Result<(), DecodeError> where + A: BytesAdapter, B: Buf, { check_wire_type(WireType::LengthDelimited, wire_type)?; @@ -908,13 +982,50 @@ pub mod bytes { // > last value it sees. // // [1]: https://developers.google.com/protocol-buffers/docs/encoding#optional - value.clear(); - value.reserve(len); - value.put(buf.take(len)); + + // NOTE: The use of BufExt::take() currently prevents zero-copy decoding + // for bytes fields backed by Bytes when docoding from Bytes. This could + // be addressed in the future by specialization. + // See also: https://github.com/tokio-rs/bytes/issues/374 + value.replace_with(buf.take(len)); Ok(()) } - length_delimited!(Vec); + length_delimited!(impl BytesAdapter); + + #[cfg(test)] + mod test { + use quickcheck::{quickcheck, TestResult}; + + use super::super::test::{check_collection_type, check_type}; + use super::*; + + quickcheck! { + fn check_vec(value: Vec, tag: u32) -> TestResult { + super::test::check_type::, Vec>(value, tag, WireType::LengthDelimited, + encode, merge, encoded_len) + } + + fn check_bytes(value: Vec, tag: u32) -> TestResult { + let value = Bytes::from(value); + super::test::check_type::(value, tag, WireType::LengthDelimited, + encode, merge, encoded_len) + } + + fn check_repeated_vec(value: Vec>, tag: u32) -> TestResult { + super::test::check_collection_type(value, tag, WireType::LengthDelimited, + encode_repeated, merge_repeated, + encoded_len_repeated) + } + + fn check_repeated_bytes(value: Vec>, tag: u32) -> TestResult { + let value = value.into_iter().map(Bytes::from).collect(); + super::test::check_collection_type(value, tag, WireType::LengthDelimited, + encode_repeated, merge_repeated, + encoded_len_repeated) + } + } + } } pub mod message { diff --git a/src/types.rs b/src/types.rs index a376f1b2f..864a2adda 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,7 +8,7 @@ use alloc::string::String; use alloc::vec::Vec; -use ::bytes::{Buf, BufMut}; +use ::bytes::{Buf, BufMut, Bytes}; use crate::{ encoding::{ @@ -360,6 +360,44 @@ impl Message for Vec { } } +/// `google.protobuf.BytesValue` +impl Message for Bytes { + fn encode_raw(&self, buf: &mut B) + where + B: BufMut, + { + if !self.is_empty() { + bytes::encode(1, self, buf) + } + } + fn merge_field( + &mut self, + tag: u32, + wire_type: WireType, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + { + if tag == 1 { + bytes::merge(wire_type, self, buf, ctx) + } else { + skip_field(wire_type, tag, buf, ctx) + } + } + fn encoded_len(&self) -> usize { + if !self.is_empty() { + bytes::encoded_len(1, self) + } else { + 0 + } + } + fn clear(&mut self) { + self.clear(); + } +} + /// `google.protobuf.Empty` impl Message for () { fn encode_raw(&self, _buf: &mut B) diff --git a/tests/src/message_encoding.rs b/tests/src/message_encoding.rs index e871b5e7e..367c0f837 100644 --- a/tests/src/message_encoding.rs +++ b/tests/src/message_encoding.rs @@ -1,3 +1,4 @@ +use bytes::Bytes; use prost::alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; use prost::{Enumeration, Message, Oneof}; @@ -60,8 +61,10 @@ pub struct ScalarTypes { pub _bool: bool, #[prost(string, tag = "014")] pub string: String, - #[prost(bytes, tag = "015")] - pub bytes: Vec, + #[prost(bytes = "vec", tag = "015")] + pub bytes_vec: Vec, + #[prost(bytes = "bytes", tag = "016")] + pub bytes_buf: Bytes, #[prost(int32, required, tag = "101")] pub required_int32: i32, @@ -91,8 +94,10 @@ pub struct ScalarTypes { pub required_bool: bool, #[prost(string, required, tag = "114")] pub required_string: String, - #[prost(bytes, required, tag = "115")] - pub required_bytes: Vec, + #[prost(bytes = "vec", required, tag = "115")] + pub required_bytes_vec: Vec, + #[prost(bytes = "bytes", required, tag = "116")] + pub required_bytes_buf: Bytes, #[prost(int32, optional, tag = "201")] pub optional_int32: Option, @@ -123,8 +128,10 @@ pub struct ScalarTypes { pub optional_bool: Option, #[prost(string, optional, tag = "214")] pub optional_string: Option, - #[prost(bytes, optional, tag = "215")] - pub optional_bytes: Option>, + #[prost(bytes = "vec", optional, tag = "215")] + pub optional_bytes_vec: Option>, + #[prost(bytes = "bytes", optional, tag = "216")] + pub optional_bytes_buf: Option, #[prost(int32, repeated, packed = "false", tag = "301")] pub repeated_int32: Vec, @@ -154,8 +161,10 @@ pub struct ScalarTypes { pub repeated_bool: Vec, #[prost(string, repeated, packed = "false", tag = "315")] pub repeated_string: Vec, - #[prost(bytes, repeated, packed = "false", tag = "316")] - pub repeated_bytes: Vec>, + #[prost(bytes = "vec", repeated, packed = "false", tag = "316")] + pub repeated_bytes_vec: Vec>, + #[prost(bytes = "bytes", repeated, packed = "false", tag = "317")] + pub repeated_bytes_buf: Vec, #[prost(int32, repeated, tag = "401")] pub packed_int32: Vec, @@ -186,8 +195,10 @@ pub struct ScalarTypes { pub packed_bool: Vec, #[prost(string, repeated, tag = "415")] pub packed_string: Vec, - #[prost(bytes, repeated, tag = "416")] - pub packed_bytes: Vec>, + #[prost(bytes = "vec", repeated, tag = "416")] + pub packed_bytes_vec: Vec>, + #[prost(bytes = "bytes", repeated, tag = "417")] + pub packed_bytes_buf: Vec, } #[test] @@ -276,6 +287,12 @@ pub struct DefaultValues { #[prost(string, tag = "3", default = "fourty two")] pub string: String, + #[prost(bytes = "vec", tag = "7", default = "b\"foo\\x00bar\"")] + pub bytes_vec: Vec, + + #[prost(bytes = "bytes", tag = "8", default = "b\"foo\\x00bar\"")] + pub bytes_buf: Bytes, + #[prost(enumeration = "BasicEnumeration", tag = "4", default = "ONE")] pub enumeration: i32, @@ -292,6 +309,8 @@ fn check_default_values() { assert_eq!(default.int32, 42); assert_eq!(default.optional_int32, None); assert_eq!(&default.string, "fourty two"); + assert_eq!(&default.bytes_vec.as_ref(), b"foo\0bar"); + assert_eq!(&default.bytes_buf.as_ref(), b"foo\0bar"); assert_eq!(default.enumeration, BasicEnumeration::ONE as i32); assert_eq!(default.optional_enumeration, None); assert_eq!(&default.repeated_enumeration, &[]);