diff --git a/Cargo.toml b/Cargo.toml index 8fc4b5481..7aa55151c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "prost" -version = "0.11.9" +version = "0.11.10" authors = [ - "Dan Burkert ", - "Lucio Franco ", + "Dan Burkert ", + "Lucio Franco ", ] license = "Apache-2.0" repository = "https://github.com/tokio-rs/prost" @@ -48,7 +48,7 @@ std = [] [dependencies] bytes = { version = "1", default-features = false } -prost-derive = { version = "0.11.9", path = "prost-derive", optional = true } +prost-derive = { version = "0.11.10", path = "prost-derive", optional = true } [dev-dependencies] criterion = "0.3" diff --git a/prost-build/Cargo.toml b/prost-build/Cargo.toml index 1d482dcb2..e28594e29 100644 --- a/prost-build/Cargo.toml +++ b/prost-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prost-build" -version = "0.11.9" +version = "0.11.10" authors = [ "Dan Burkert ", "Lucio Franco ", @@ -27,8 +27,8 @@ itertools = { version = "0.10", default-features = false, features = ["use_alloc log = "0.4" multimap = { version = "0.8", default-features = false } petgraph = { version = "0.6", default-features = false } -prost = { version = "0.11.9", path = "..", default-features = false } -prost-types = { version = "0.11.9", path = "../prost-types", default-features = false } +prost = { version = "0.11.10", path = "..", default-features = false } +prost-types = { version = "0.11.10", path = "../prost-types", default-features = false } tempfile = "3" lazy_static = "1.4.0" regex = { version = "1.5.5", default-features = false, features = ["std", "unicode-bool"] } diff --git a/prost-build/src/code_generator.rs b/prost-build/src/code_generator.rs index 2a4d24181..fc6a82187 100644 --- a/prost-build/src/code_generator.rs +++ b/prost-build/src/code_generator.rs @@ -18,7 +18,7 @@ use crate::ast::{Comments, Method, Service}; use crate::extern_paths::ExternPaths; use crate::ident::{to_snake, to_upper_camel}; use crate::message_graph::MessageGraph; -use crate::{BytesType, Config, MapType}; +use crate::{BytesType, Config, MapType, StringType}; #[derive(PartialEq)] enum Syntax { @@ -350,6 +350,17 @@ impl<'a> CodeGenerator<'a> { .push_str(&format!("={:?}", bytes_type.annotation())); } + if type_ == Type::String { + let string_type = self + .config + .string_type + .get_first_field(fq_message_name, field.name()) + .copied() + .unwrap_or_default(); + self.buf + .push_str(&format!("={:?}", string_type.annotation())); + } + match field.label() { Label::Optional => { if optional { @@ -862,8 +873,6 @@ impl<'a> CodeGenerator<'a> { } fn resolve_type(&self, field: &FieldDescriptorProto, fq_message_name: &str) -> String { - let prost_path = self.config.prost_path.as_deref().unwrap_or("::prost"); - match field.r#type() { Type::Float => String::from("f32"), Type::Double => String::from("f64"), @@ -872,7 +881,14 @@ impl<'a> CodeGenerator<'a> { Type::Int32 | Type::Sfixed32 | Type::Sint32 | Type::Enum => String::from("i32"), Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"), Type::Bool => String::from("bool"), - Type::String => format!("{}::alloc::string::String", prost_path), + Type::String => self + .config + .string_type + .get_first_field(fq_message_name, field.name()) + .copied() + .unwrap_or_default() + .rust_type() + .to_owned(), Type::Bytes => self .config .bytes_type @@ -1212,6 +1228,26 @@ impl BytesType { } } +impl StringType { + /// The `prost-derive` annotation type corresponding to the bytes type. + fn annotation(&self) -> &'static str { + match self { + StringType::String => "string", + StringType::ByteStr => "byte_str", + StringType::ByteStrUnchecked => "byte_str_unchecked", + } + } + + /// The fully-qualified Rust type corresponding to the bytes type. + fn rust_type(&self) -> &'static str { + match self { + StringType::String => "::prost::alloc::string::String", + StringType::ByteStr => "::prost::str::ByteStr", + StringType::ByteStrUnchecked => "::prost::str::ByteStrUnchecked", + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/prost-build/src/lib.rs b/prost-build/src/lib.rs index 06a7e1745..a9513ad1c 100644 --- a/prost-build/src/lib.rs +++ b/prost-build/src/lib.rs @@ -235,6 +235,14 @@ impl Default for BytesType { } } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +enum StringType { + #[default] + String, + ByteStr, + ByteStrUnchecked, +} + /// Configuration options for Protobuf code generation. /// /// This configuration builder can be used to set non-default code generation options. @@ -243,6 +251,7 @@ pub struct Config { service_generator: Option>, map_type: PathMap, bytes_type: PathMap, + string_type: PathMap, type_attributes: PathMap, message_attributes: PathMap, enum_attributes: PathMap, @@ -389,6 +398,30 @@ impl Config { self } + pub fn bytes_str(&mut self, paths: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for matcher in paths { + self.string_type + .insert(matcher.as_ref().to_string(), StringType::ByteStr) + } + self + } + + pub fn bytes_str_unchecked(&mut self, paths: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for matcher in paths { + self.string_type + .insert(matcher.as_ref().to_string(), StringType::ByteStrUnchecked) + } + self + } + /// Add additional attribute to matched fields. /// /// # Arguments @@ -1232,6 +1265,7 @@ impl default::Default for Config { service_generator: None, map_type: PathMap::default(), bytes_type: PathMap::default(), + string_type: PathMap::default(), type_attributes: PathMap::default(), message_attributes: PathMap::default(), enum_attributes: PathMap::default(), @@ -1259,6 +1293,7 @@ impl fmt::Debug for Config { .field("service_generator", &self.service_generator.is_some()) .field("map_type", &self.map_type) .field("bytes_type", &self.bytes_type) + .field("string_type", &self.string_type) .field("type_attributes", &self.type_attributes) .field("field_attributes", &self.field_attributes) .field("prost_types", &self.prost_types) diff --git a/prost-derive/Cargo.toml b/prost-derive/Cargo.toml index 56b480f7d..a0581aab3 100644 --- a/prost-derive/Cargo.toml +++ b/prost-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prost-derive" -version = "0.11.9" +version = "0.11.10" authors = [ "Dan Burkert ", "Lucio Franco ", diff --git a/prost-derive/src/field/map.rs b/prost-derive/src/field/map.rs index 6c70fb364..56284fd7b 100644 --- a/prost-derive/src/field/map.rs +++ b/prost-derive/src/field/map.rs @@ -365,7 +365,7 @@ fn key_ty_from_str(s: &str) -> Result { | scalar::Ty::Sfixed32 | scalar::Ty::Sfixed64 | scalar::Ty::Bool - | scalar::Ty::String => Ok(ty), + | scalar::Ty::String(_) => Ok(ty), _ => bail!("invalid map key type: {}", s), } } diff --git a/prost-derive/src/field/scalar.rs b/prost-derive/src/field/scalar.rs index 6a2fb96d6..761412ee3 100644 --- a/prost-derive/src/field/scalar.rs +++ b/prost-derive/src/field/scalar.rs @@ -196,7 +196,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), } } @@ -391,13 +391,14 @@ pub enum Ty { Sfixed32, Sfixed64, Bool, - String, + String(StringTy), Bytes(BytesTy), Enumeration(Path), } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Default, Clone, Debug, PartialEq, Eq)] pub enum BytesTy { + #[default] Vec, Bytes, } @@ -419,6 +420,41 @@ impl BytesTy { } } +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub enum StringTy { + #[default] + String, + ByteStr, + ByteStrUnchecked, +} + +impl StringTy { + fn try_from_str(s: &str) -> Result { + match s { + "string" => Ok(StringTy::String), + "byte_str" => Ok(StringTy::ByteStr), + "byte_str_unchecked" => Ok(StringTy::ByteStrUnchecked), + _ => bail!("Invalid bytes type: {}", s), + } + } + + fn rust_type(&self) -> TokenStream { + match self { + StringTy::String => quote! { ::prost::alloc::string::String }, + StringTy::ByteStr => quote! { ::prost::str::ByteStr }, + StringTy::ByteStrUnchecked => quote! { ::prost::str::ByteStrUnchecked }, + } + } + + fn module(&self) -> &'static str { + match self { + StringTy::String => "string", + StringTy::ByteStr => "byte_str", + StringTy::ByteStrUnchecked => "byte_str_unchecked", + } + } +} + impl Ty { pub fn from_attr(attr: &Meta) -> Result, Error> { let ty = match *attr { @@ -435,13 +471,18 @@ impl Ty { 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(BytesTy::Vec), + Meta::Path(ref name) if name.is_ident("string") => Ty::String(StringTy::default()), + Meta::Path(ref name) if name.is_ident("bytes") => Ty::Bytes(BytesTy::default()), 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), + .. + }) if path.is_ident("string") => Ty::String(StringTy::try_from_str(&l.value())?), Meta::NameValue(MetaNameValue { ref path, lit: Lit::Str(ref l), @@ -485,8 +526,8 @@ impl Ty { "sfixed32" => Ty::Sfixed32, "sfixed64" => Ty::Sfixed64, "bool" => Ty::Bool, - "string" => Ty::String, - "bytes" => Ty::Bytes(BytesTy::Vec), + "string" => Ty::String(StringTy::default()), + "bytes" => Ty::Bytes(BytesTy::default()), s if s.len() > enumeration_len && &s[..enumeration_len] == "enumeration" => { let s = &s[enumeration_len..].trim(); match s.chars().next() { @@ -521,7 +562,7 @@ impl Ty { Ty::Sfixed32 => "sfixed32", Ty::Sfixed64 => "sfixed64", Ty::Bool => "bool", - Ty::String => "string", + Ty::String(..) => "string", Ty::Bytes(..) => "bytes", Ty::Enumeration(..) => "enum", } @@ -530,7 +571,7 @@ impl Ty { // TODO: rename to 'owned_type'. pub fn rust_type(&self) -> TokenStream { match self { - Ty::String => quote!(::prost::alloc::string::String), + Ty::String(ty) => ty.rust_type(), Ty::Bytes(ty) => ty.rust_type(), _ => self.rust_ref_type(), } @@ -552,7 +593,7 @@ impl Ty { Ty::Sfixed32 => quote!(i32), Ty::Sfixed64 => quote!(i64), Ty::Bool => quote!(bool), - Ty::String => quote!(&str), + Ty::String(..) => quote!(&str), Ty::Bytes(..) => quote!(&[u8]), Ty::Enumeration(..) => quote!(i32), } @@ -561,13 +602,14 @@ impl Ty { pub fn module(&self) -> Ident { match *self { Ty::Enumeration(..) => Ident::new("int32", Span::call_site()), + Ty::String(ref sty) => Ident::new(sty.module(), Span::call_site()), _ => Ident::new(self.as_str(), Span::call_site()), } } /// Returns false if the scalar type is length delimited (i.e., `string` or `bytes`). pub fn is_numeric(&self) -> bool { - !matches!(self, Ty::String | Ty::Bytes(..)) + !matches!(self, Ty::String(..) | Ty::Bytes(..)) } } @@ -659,7 +701,13 @@ impl DefaultValue { 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()), + Lit::Str(ref lit) + if *ty == Ty::String(StringTy::String) + || *ty == Ty::String(StringTy::ByteStr) + || *ty == Ty::String(StringTy::ByteStrUnchecked) => + { + DefaultValue::String(lit.value()) + } Lit::ByteStr(ref lit) if *ty == Ty::Bytes(BytesTy::Bytes) || *ty == Ty::Bytes(BytesTy::Vec) => { @@ -768,7 +816,7 @@ impl DefaultValue { Ty::Uint64 | Ty::Fixed64 => DefaultValue::U64(0), Ty::Bool => DefaultValue::Bool(false), - Ty::String => DefaultValue::String(String::new()), + Ty::String(..) => DefaultValue::String(String::new()), Ty::Bytes(..) => DefaultValue::Bytes(Vec::new()), Ty::Enumeration(ref path) => DefaultValue::Enumeration(quote!(#path::default())), } @@ -777,7 +825,7 @@ impl DefaultValue { pub fn owned(&self) -> TokenStream { match *self { DefaultValue::String(ref value) if value.is_empty() => { - quote!(::prost::alloc::string::String::new()) + quote!(::core::default::Default::default()) } DefaultValue::String(ref value) => quote!(#value.into()), DefaultValue::Bytes(ref value) if value.is_empty() => { diff --git a/prost-types/Cargo.toml b/prost-types/Cargo.toml index 34cbd574b..a839ce350 100644 --- a/prost-types/Cargo.toml +++ b/prost-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prost-types" -version = "0.11.9" +version = "0.11.10" authors = [ "Dan Burkert ", "Lucio Franco (tag: u32, value: &ByteStr, buf: &mut B) + where + B: BufMut, + { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(value.len() as u64, buf); + buf.put_slice(value.as_bytes()); + } + + pub fn merge( + wire_type: WireType, + value: &mut ByteStr, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + { + // DropGuard makes sure that we never leak a `ByteStr` that contains invalid UTF-8 + // data. See the comment in `encoding::string::merge` for more detail. + + struct DropGuard<'a>(&'a mut Bytes); + impl<'a> Drop for DropGuard<'a> { + #[inline] + fn drop(&mut self) { + self.0.clear(); + } + } + + let guard = DropGuard(&mut value.buf); + super::bytes::merge(wire_type, guard.0, buf, ctx)?; + match core::str::from_utf8(&guard.0[..]) { + Ok(_) => { + mem::forget(guard); + Ok(()) + } + Err(_) => Err(DecodeError::new( + "invalid ByteStr: data is not UTF-8 encoded", + )), + } + } + + length_delimited!(ByteStr); + + // #[cfg(test)] + // mod test { + // use proptest::prelude::*; + + // use super::super::test::{check_collection_type, check_type}; + // use super::*; + + // proptest! { + // #[test] + // fn check_vec(value: Vec, tag in MIN_TAG..=MAX_TAG) { + // super::test::check_type::, Vec>(value, tag, WireType::LengthDelimited, + // encode, merge, encoded_len)?; + // } + + // #[test] + // fn check_bytes(value: Vec, tag in MIN_TAG..=MAX_TAG) { + // let value = Bytes::from(value); + // super::test::check_type::(value, tag, WireType::LengthDelimited, + // encode, merge, encoded_len)?; + // } + + // #[test] + // fn check_repeated_vec(value: Vec>, tag in MIN_TAG..=MAX_TAG) { + // super::test::check_collection_type(value, tag, WireType::LengthDelimited, + // encode_repeated, merge_repeated, + // encoded_len_repeated)?; + // } + + // #[test] + // fn check_repeated_bytes(value: Vec>, tag in MIN_TAG..=MAX_TAG) { + // 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 byte_str_unchecked { + use crate::str::ByteStrUnchecked; + + use super::*; + + pub fn encode(tag: u32, value: &ByteStrUnchecked, buf: &mut B) + where + B: BufMut, + { + encode_key(tag, WireType::LengthDelimited, buf); + encode_varint(value.len() as u64, buf); + buf.put_slice(value.as_bytes()); + } + + pub fn merge( + wire_type: WireType, + value: &mut ByteStrUnchecked, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + { + // We're the Unchecked variant, so no need to valid it's UTF-8. + super::bytes::merge(wire_type, &mut value.buf, buf, ctx) + } + + length_delimited!(ByteStrUnchecked); + + // #[cfg(test)] + // mod test { + // use proptest::prelude::*; + + // use super::super::test::{check_collection_type, check_type}; + // use super::*; + + // proptest! { + // #[test] + // fn check_vec(value: Vec, tag in MIN_TAG..=MAX_TAG) { + // super::test::check_type::, Vec>(value, tag, WireType::LengthDelimited, + // encode, merge, encoded_len)?; + // } + + // #[test] + // fn check_bytes(value: Vec, tag in MIN_TAG..=MAX_TAG) { + // let value = Bytes::from(value); + // super::test::check_type::(value, tag, WireType::LengthDelimited, + // encode, merge, encoded_len)?; + // } + + // #[test] + // fn check_repeated_vec(value: Vec>, tag in MIN_TAG..=MAX_TAG) { + // super::test::check_collection_type(value, tag, WireType::LengthDelimited, + // encode_repeated, merge_repeated, + // encoded_len_repeated)?; + // } + + // #[test] + // fn check_repeated_bytes(value: Vec>, tag in MIN_TAG..=MAX_TAG) { + // 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 { use super::*; diff --git a/src/error.rs b/src/error.rs index d59569944..b61241e14 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ //! Protobuf encoding and decoding errors. +use alloc::string::ToString; use alloc::borrow::Cow; use alloc::boxed::Box; use alloc::collections::TryReserveError; diff --git a/src/lib.rs b/src/lib.rs index 4a97bb819..a781d8b00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub extern crate alloc; // Re-export the bytes crate for use within derived code. pub use bytes; +pub use types::str; mod error; mod message; diff --git a/src/types.rs b/src/types.rs index ce0369967..471941051 100644 --- a/src/types.rs +++ b/src/types.rs @@ -422,3 +422,245 @@ impl Message for () { } fn clear(&mut self) {} } + +pub mod str { + use alloc::string::String; + use core::fmt; + use core::hash::Hash; + use core::marker::PhantomData; + use core::ops::Deref; + use core::str::Utf8Error; + + use ::bytes::{Buf, BufMut, Bytes}; + + use crate::encoding::{self, DecodeContext, WireType}; + use crate::error::DecodeError; + use crate::Message; + + #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] + pub struct Checked; + #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] + pub struct Unchecked; + + #[derive(Debug, Clone)] + pub struct ByteStr { + pub(crate) buf: Bytes, + _marker: PhantomData, + } + pub type ByteStrUnchecked = ByteStr; + + impl ByteStr { + pub fn from_utf8(buf: Bytes) -> Result { + // Validate that the provided buffer is UTF-8. + core::str::from_utf8(&buf[..])?; + + Ok(ByteStr { + buf, + _marker: PhantomData::default(), + }) + } + } + + impl ByteStr { + pub fn from_utf8(buf: Bytes) -> Self { + Self { + buf, + _marker: PhantomData::default(), + } + } + } + + impl ByteStr { + /// Returns the number of bytes in this [`ByteStr`]. + pub fn len(&self) -> usize { + self.buf.len() + } + + /// Returns if the [`ByteStr`] is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Clears the [`ByteStr`]. + pub fn clear(&mut self) { + self.buf.clear() + } + + pub fn as_str(&self) -> &str { + // SAFETY: We checked that the provided buffer was valid UTF-8 at creation. + unsafe { core::str::from_utf8_unchecked(&self.buf[..]) } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.buf + } + + /// Consume the [`ByteStr`] returning the backing [`bytes::Bytes`]. + pub fn into_bytes(self) -> ::bytes::Bytes { + self.buf + } + } + + impl Deref for ByteStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } + } + + impl AsRef for ByteStr { + fn as_ref(&self) -> &str { + self.as_str() + } + } + + impl From for ByteStr { + fn from(value: String) -> Self { + // Note: We're creating from a String, which is already guaranteed to be UTF-8. + Self { + buf: Bytes::from(value.into_bytes()), + _marker: PhantomData::default(), + } + } + } + + impl<'a, T> From<&'a str> for ByteStr { + fn from(value: &'a str) -> Self { + Self::from(value.to_owned()) + } + } + + impl Default for ByteStr { + fn default() -> Self { + Self { + buf: Bytes::default(), + _marker: PhantomData::default(), + } + } + } + + impl> PartialEq for ByteStr { + fn eq(&self, other: &S) -> bool { + self.as_str() == other.as_ref() + } + } + + impl PartialEq> for String { + fn eq(&self, other: &ByteStr) -> bool { + self.as_str() == other.as_str() + } + } + + impl PartialEq> for &str { + fn eq(&self, other: &ByteStr) -> bool { + *self == other.as_str() + } + } + + impl Eq for ByteStr {} + + impl Ord for ByteStr { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.as_str().cmp(other.as_str()) + } + } + + impl PartialOrd for ByteStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl Hash for ByteStr { + fn hash(&self, state: &mut H) { + self.as_str().hash(state) + } + } + + impl fmt::Display for ByteStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } + } + + impl Message for ByteStr { + fn encode_raw(&self, buf: &mut B) + where + B: BufMut, + { + if !self.is_empty() { + encoding::bytes::encode(1, &self.buf, buf) + } + } + + fn merge_field( + &mut self, + tag: u32, + wire_type: WireType, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + { + if tag == 1 { + encoding::byte_str::merge(wire_type, self, buf, ctx) + } else { + encoding::skip_field(wire_type, tag, buf, ctx) + } + } + + fn encoded_len(&self) -> usize { + if !self.is_empty() { + encoding::bytes::encoded_len(1, &self.buf) + } else { + 0 + } + } + + fn clear(&mut self) { + self.clear(); + } + } + + impl Message for ByteStr { + fn encode_raw(&self, buf: &mut B) + where + B: BufMut, + { + if !self.is_empty() { + encoding::bytes::encode(1, &self.buf, buf) + } + } + + fn merge_field( + &mut self, + tag: u32, + wire_type: WireType, + buf: &mut B, + ctx: DecodeContext, + ) -> Result<(), DecodeError> + where + B: Buf, + { + if tag == 1 { + encoding::byte_str_unchecked::merge(wire_type, self, buf, ctx) + } else { + encoding::skip_field(wire_type, tag, buf, ctx) + } + } + + fn encoded_len(&self) -> usize { + if !self.is_empty() { + encoding::bytes::encoded_len(1, &self.buf) + } else { + 0 + } + } + + fn clear(&mut self) { + self.clear(); + } + } +} diff --git a/tests/single-include/src/outdir/outdir.rs b/tests/single-include/src/outdir/outdir.rs index eee461c1b..970860f13 100644 --- a/tests/single-include/src/outdir/outdir.rs +++ b/tests/single-include/src/outdir/outdir.rs @@ -1,7 +1,7 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct OutdirRequest { - #[prost(string, tag = "1")] + #[prost(string = "string", tag = "1")] pub query: ::prost::alloc::string::String, #[prost(int32, tag = "2")] pub page_number: i32, diff --git a/tests/src/build.rs b/tests/src/build.rs index eb5fa1141..eafa64e32 100644 --- a/tests/src/build.rs +++ b/tests/src/build.rs @@ -106,6 +106,7 @@ fn main() { config .bytes(["."]) + .bytes_str(["."]) .compile_protos(&[src.join("well_known_types.proto")], includes) .unwrap(); @@ -116,6 +117,7 @@ fn main() { prost_build::Config::new() .bytes(["."]) + .bytes_str(["."]) .out_dir(out_path) .include_file("wellknown_include.rs") .compile_protos(&[src.join("well_known_types.proto")], includes)