use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::context::CodeGenContext;
use crate::features::ResolvedFeatures;
use crate::generated::descriptor::field_descriptor_proto::{Label, Type};
use crate::generated::descriptor::{DescriptorProto, FieldDescriptorProto};
use crate::idents::rust_path_to_tokens;
use crate::impl_message::{
effective_type, effective_type_in_map_entry, field_string_repr, field_uses_bytes,
find_map_entry_fields, is_explicit_presence_scalar, is_non_default_expr, is_real_oneof_member,
is_required_field, is_supported_field_type, map_value_use_bytes,
};
use crate::message::{is_closed_enum, is_map_field, make_field_ident};
use crate::CodeGenError;
fn text_string_into(repr: crate::StringRepr, owned: TokenStream) -> TokenStream {
if repr.is_default() {
owned
} else {
quote! { ::core::convert::Into::into(#owned) }
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn generate_text_impl(
ctx: &CodeGenContext,
msg: &DescriptorProto,
rust_name: &str,
current_package: &str,
proto_fqn: &str,
features: &ResolvedFeatures,
has_extension_ranges: bool,
oneof_idents: &std::collections::HashMap<usize, proc_macro2::Ident>,
oneof_prefix: &TokenStream,
nesting: usize,
) -> Result<TokenStream, CodeGenError> {
if !ctx.config.generate_text {
return Ok(TokenStream::new());
}
if proto_fqn == "google.protobuf.Any" {
return Ok(TokenStream::new());
}
let is_message_set = msg
.options
.as_option()
.and_then(|o| o.message_set_wire_format)
.unwrap_or(false);
if is_message_set {
let name_ident = format_ident!("{}", rust_name);
return Ok(crate::feature_gates::cfg_block(
quote! {
impl ::buffa::text::TextFormat for #name_ident {
fn encode_text(
&self,
_enc: &mut ::buffa::text::TextEncoder<'_>,
) -> ::core::fmt::Result {
::core::result::Result::Ok(())
}
fn merge_text(
&mut self,
dec: &mut ::buffa::text::TextDecoder<'_>,
) -> ::core::result::Result<(), ::buffa::text::ParseError> {
while dec.read_field_name()?.is_some() {
dec.skip_value()?;
}
::core::result::Result::Ok(())
}
}
},
ctx.config.feature_gates().text,
));
}
let name_ident = format_ident!("{}", rust_name);
let scalar_fields: Vec<_> = msg
.field
.iter()
.filter(|f| {
!is_real_oneof_member(f)
&& f.label.unwrap_or_default() != Label::LABEL_REPEATED
&& is_supported_field_type(f.r#type.unwrap_or_default())
})
.collect();
let repeated_fields: Vec<_> = msg
.field
.iter()
.filter(|f| {
f.label.unwrap_or_default() == Label::LABEL_REPEATED
&& !is_map_field(msg, f)
&& is_supported_field_type(f.r#type.unwrap_or_default())
})
.collect();
let map_fields: Vec<_> = msg
.field
.iter()
.filter(|f| f.label.unwrap_or_default() == Label::LABEL_REPEATED && is_map_field(msg, f))
.collect();
let oneof_groups: Vec<(String, proc_macro2::Ident, Vec<&FieldDescriptorProto>)> = msg
.oneof_decl
.iter()
.enumerate()
.filter_map(|(idx, oneof)| {
let enum_ident = oneof_idents.get(&idx)?;
let fields: Vec<_> = msg
.field
.iter()
.filter(|f| is_real_oneof_member(f) && f.oneof_index == Some(idx as i32))
.collect();
if fields.is_empty() {
return None;
}
Some((
oneof.name.as_deref()?.to_string(),
enum_ident.clone(),
fields,
))
})
.collect();
let scalar_encode: Vec<_> = scalar_fields
.iter()
.map(|f| scalar_encode_stmt(ctx, f, features))
.collect::<Result<_, _>>()?;
let repeated_encode: Vec<_> = repeated_fields
.iter()
.map(|f| repeated_encode_stmt(ctx, f, features))
.collect::<Result<_, _>>()?;
let oneof_encode: Vec<_> = oneof_groups
.iter()
.map(|(name, enum_ident, fields)| {
oneof_encode_stmt(
ctx,
enum_ident,
name,
fields,
oneof_prefix,
proto_fqn,
features,
)
})
.collect::<Result<_, _>>()?;
let map_encode: Vec<_> = map_fields
.iter()
.map(|f| map_encode_stmt(ctx, msg, f, features))
.collect::<Result<_, _>>()?;
let scalar_merge: Vec<_> = scalar_fields
.iter()
.map(|f| scalar_merge_arm(ctx, f, current_package, proto_fqn, features, nesting))
.collect::<Result<_, _>>()?;
let repeated_merge: Vec<_> = repeated_fields
.iter()
.map(|f| repeated_merge_arm(ctx, f, current_package, proto_fqn, features, nesting))
.collect::<Result<_, _>>()?;
let mut oneof_merge: Vec<TokenStream> = Vec::new();
for (name, enum_ident, fields) in &oneof_groups {
oneof_merge.extend(oneof_merge_arms(
ctx,
enum_ident,
name,
fields,
oneof_prefix,
current_package,
proto_fqn,
features,
nesting,
)?);
}
let map_merge: Vec<_> = map_fields
.iter()
.map(|f| map_merge_arm(ctx, msg, f, current_package, proto_fqn, features, nesting))
.collect::<Result<_, _>>()?;
let use_ext_text = ctx.config.preserve_unknown_fields && has_extension_ranges;
let proto_fqn_lit = proto_fqn;
let (ext_encode, ext_merge_arm) = if use_ext_text {
(
quote! {
enc.write_extension_fields(#proto_fqn_lit, &self.__buffa_unknown_fields)?;
},
quote! {
__name if __name.starts_with('[') => {
for __r in dec.read_extension(__name, #proto_fqn_lit)? {
self.__buffa_unknown_fields.push(__r);
}
}
},
)
} else {
(quote! {}, quote! {})
};
let unknown_encode = if ctx.config.preserve_unknown_fields {
quote! { enc.write_unknown_fields(&self.__buffa_unknown_fields)?; }
} else {
quote! {}
};
let has_encode = !scalar_encode.is_empty()
|| !repeated_encode.is_empty()
|| !oneof_encode.is_empty()
|| !map_encode.is_empty()
|| ctx.config.preserve_unknown_fields;
let enc_param = if has_encode {
quote! { enc }
} else {
quote! { _enc }
};
Ok(crate::feature_gates::cfg_block(
quote! {
impl ::buffa::text::TextFormat for #name_ident {
fn encode_text(
&self,
#enc_param: &mut ::buffa::text::TextEncoder<'_>,
) -> ::core::fmt::Result {
#[allow(unused_imports)]
use ::buffa::Enumeration as _;
#(#scalar_encode)*
#(#repeated_encode)*
#(#oneof_encode)*
#(#map_encode)*
#ext_encode
#unknown_encode
::core::result::Result::Ok(())
}
fn merge_text(
&mut self,
dec: &mut ::buffa::text::TextDecoder<'_>,
) -> ::core::result::Result<(), ::buffa::text::ParseError> {
#[allow(unused_imports)]
use ::buffa::Enumeration as _;
while let ::core::option::Option::Some(__name) = dec.read_field_name()? {
match __name {
#(#scalar_merge)*
#(#repeated_merge)*
#(#oneof_merge)*
#(#map_merge)*
#ext_merge_arm
_ => dec.skip_value()?,
}
}
::core::result::Result::Ok(())
}
}
},
ctx.config.feature_gates().text,
))
}
fn write_call(ty: Type, val: &TokenStream) -> TokenStream {
match ty {
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => {
quote! { enc.write_i32(#val)?; }
}
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => {
quote! { enc.write_i64(#val)?; }
}
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => quote! { enc.write_u32(#val)?; },
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => quote! { enc.write_u64(#val)?; },
Type::TYPE_FLOAT => quote! { enc.write_f32(#val)?; },
Type::TYPE_DOUBLE => quote! { enc.write_f64(#val)?; },
Type::TYPE_BOOL => quote! { enc.write_bool(#val)?; },
Type::TYPE_STRING => quote! { enc.write_string(#val)?; },
Type::TYPE_BYTES => quote! { enc.write_bytes(#val)?; },
Type::TYPE_MESSAGE | Type::TYPE_GROUP => quote! { enc.write_message(#val)?; },
Type::TYPE_ENUM => {
unreachable!("write_call: enum handled by caller (closed vs open split)")
}
}
}
fn read_call(ty: Type) -> TokenStream {
match ty {
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => quote! { dec.read_i32()? },
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => quote! { dec.read_i64()? },
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => quote! { dec.read_u32()? },
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => quote! { dec.read_u64()? },
Type::TYPE_FLOAT => quote! { dec.read_f32()? },
Type::TYPE_DOUBLE => quote! { dec.read_f64()? },
Type::TYPE_BOOL => quote! { dec.read_bool()? },
Type::TYPE_STRING => quote! { dec.read_string()? },
Type::TYPE_BYTES => quote! { dec.read_bytes()? },
Type::TYPE_ENUM | Type::TYPE_MESSAGE | Type::TYPE_GROUP => {
unreachable!("read_call: enum/message handled by caller")
}
}
}
fn is_copy_scalar(ty: Type) -> bool {
!matches!(
ty,
Type::TYPE_STRING | Type::TYPE_BYTES | Type::TYPE_MESSAGE | Type::TYPE_GROUP
)
}
fn text_field_name(
proto_name: &str,
field: &FieldDescriptorProto,
ty: Type,
) -> (String, TokenStream) {
if ty == Type::TYPE_GROUP {
if let Some(type_name) = field.type_name.as_deref() {
let simple = type_name.rsplit('.').next().unwrap_or(type_name);
if simple != proto_name && simple.to_ascii_lowercase() == proto_name {
return (simple.to_string(), quote! { #simple | #proto_name });
}
}
}
(proto_name.to_string(), quote! { #proto_name })
}
fn enum_write(closed: bool, val: &TokenStream) -> TokenStream {
if closed {
quote! { enc.write_enum_name(#val.proto_name())?; }
} else {
quote! {
match #val {
::buffa::EnumValue::Known(__e) => enc.write_enum_name(__e.proto_name())?,
::buffa::EnumValue::Unknown(__n) => enc.write_enum_number(*__n)?,
}
}
}
}
fn enum_type_path(
ctx: &CodeGenContext,
field: &FieldDescriptorProto,
current_package: &str,
nesting: usize,
) -> Result<TokenStream, CodeGenError> {
let type_name = field
.type_name
.as_deref()
.ok_or(CodeGenError::MissingField("field.type_name"))?;
let path = ctx
.rust_type_relative(type_name, current_package, nesting)
.ok_or_else(|| CodeGenError::Other(format!("enum type '{type_name}' not found")))?;
Ok(rust_path_to_tokens(&path))
}
fn enum_read(closed: bool, enum_ty: &TokenStream, dec: &proc_macro2::Ident) -> TokenStream {
if closed {
quote! { #dec.read_closed_enum_by_name::<#enum_ty>() }
} else {
quote! {
#dec.read_enum_by_name::<#enum_ty>().map(::buffa::EnumValue::from)
}
}
}
fn scalar_encode_stmt(
ctx: &CodeGenContext,
field: &FieldDescriptorProto,
parent_features: &ResolvedFeatures,
) -> Result<TokenStream, CodeGenError> {
let features = &crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let ty = effective_type(ctx, field, features);
let (name_lit, _) = text_field_name(proto_name, field, ty);
let required = is_required_field(field, features);
if is_explicit_presence_scalar(field, ty, features) {
let body = match ty {
Type::TYPE_ENUM => {
let closed = is_closed_enum(features);
enum_write(closed, "e! { __v })
}
_ if is_copy_scalar(ty) => write_call(ty, "e! { *__v }),
_ => write_call(ty, "e! { __v }),
};
return Ok(quote! {
if let ::core::option::Option::Some(ref __v) = self.#ident {
enc.write_field_name(#name_lit)?;
#body
}
});
}
if matches!(ty, Type::TYPE_MESSAGE | Type::TYPE_GROUP) {
return Ok(quote! {
if self.#ident.is_set() {
enc.write_field_name(#name_lit)?;
enc.write_message(&*self.#ident)?;
}
});
}
if ty == Type::TYPE_ENUM {
let closed = is_closed_enum(features);
let val = if closed {
quote! { self.#ident }
} else {
quote! { &self.#ident }
};
let write = enum_write(closed, &val);
return Ok(if required {
quote! {
enc.write_field_name(#name_lit)?;
#write
}
} else {
quote! {
if self.#ident.to_i32() != 0 {
enc.write_field_name(#name_lit)?;
#write
}
}
});
}
if matches!(ty, Type::TYPE_STRING | Type::TYPE_BYTES) {
let write = write_call(ty, "e! { &self.#ident });
return Ok(if required {
quote! {
enc.write_field_name(#name_lit)?;
#write
}
} else {
quote! {
if !self.#ident.is_empty() {
enc.write_field_name(#name_lit)?;
#write
}
}
});
}
let write = write_call(ty, "e! { self.#ident });
if required {
return Ok(quote! {
enc.write_field_name(#name_lit)?;
#write
});
}
let check = is_non_default_expr(ty, &ident);
Ok(quote! {
if #check {
enc.write_field_name(#name_lit)?;
#write
}
})
}
fn scalar_merge_arm(
ctx: &CodeGenContext,
field: &FieldDescriptorProto,
current_package: &str,
proto_fqn: &str,
parent_features: &ResolvedFeatures,
nesting: usize,
) -> Result<TokenStream, CodeGenError> {
let features = &crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let ty = effective_type(ctx, field, features);
let (_, name_pat) = text_field_name(proto_name, field, ty);
let explicit = is_explicit_presence_scalar(field, ty, features);
let use_bytes = ty == Type::TYPE_BYTES && field_uses_bytes(ctx, proto_fqn, proto_name);
if matches!(ty, Type::TYPE_MESSAGE | Type::TYPE_GROUP) {
return Ok(quote! {
#name_pat => dec.merge_message(self.#ident.get_or_insert_default())?,
});
}
if ty == Type::TYPE_ENUM {
let closed = is_closed_enum(features);
let enum_ty = enum_type_path(ctx, field, current_package, nesting)?;
let read = enum_read(closed, &enum_ty, &format_ident!("dec"));
return Ok(if explicit {
quote! { #name_pat => self.#ident = ::core::option::Option::Some(#read?), }
} else {
quote! { #name_pat => self.#ident = #read?, }
});
}
let read = match ty {
Type::TYPE_STRING => text_string_into(
field_string_repr(ctx, proto_fqn, proto_name),
quote! { dec.read_string()?.into_owned() },
),
Type::TYPE_BYTES if use_bytes => quote! { ::buffa::bytes::Bytes::from(dec.read_bytes()?) },
_ => read_call(ty),
};
Ok(if explicit {
quote! { #name_pat => self.#ident = ::core::option::Option::Some(#read), }
} else {
quote! { #name_pat => self.#ident = #read, }
})
}
fn repeated_encode_stmt(
ctx: &CodeGenContext,
field: &FieldDescriptorProto,
parent_features: &ResolvedFeatures,
) -> Result<TokenStream, CodeGenError> {
let features = &crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let ty = effective_type(ctx, field, features);
let (name_lit, _) = text_field_name(proto_name, field, ty);
let body = match ty {
Type::TYPE_ENUM => {
let closed = is_closed_enum(features);
enum_write(closed, "e! { __v })
}
Type::TYPE_MESSAGE | Type::TYPE_GROUP => write_call(ty, "e! { __v }),
_ if is_copy_scalar(ty) => write_call(ty, "e! { *__v }),
_ => write_call(ty, "e! { __v }),
};
Ok(quote! {
for __v in &self.#ident {
enc.write_field_name(#name_lit)?;
#body
}
})
}
fn repeated_merge_arm(
ctx: &CodeGenContext,
field: &FieldDescriptorProto,
current_package: &str,
proto_fqn: &str,
parent_features: &ResolvedFeatures,
nesting: usize,
) -> Result<TokenStream, CodeGenError> {
let features = &crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let ty = effective_type(ctx, field, features);
let (_, name_pat) = text_field_name(proto_name, field, ty);
let use_bytes = ty == Type::TYPE_BYTES && field_uses_bytes(ctx, proto_fqn, proto_name);
let elem = match ty {
Type::TYPE_MESSAGE | Type::TYPE_GROUP => quote! {
{
let mut __m = ::core::default::Default::default();
__d.merge_message(&mut __m)?;
::core::result::Result::Ok(__m)
}
},
Type::TYPE_ENUM => {
let closed = is_closed_enum(features);
let enum_ty = enum_type_path(ctx, field, current_package, nesting)?;
enum_read(closed, &enum_ty, &format_ident!("__d"))
}
Type::TYPE_STRING => {
let v = text_string_into(
field_string_repr(ctx, proto_fqn, proto_name),
quote! { __d.read_string()?.into_owned() },
);
quote! { ::core::result::Result::Ok(#v) }
}
Type::TYPE_BYTES if use_bytes => {
quote! { ::core::result::Result::Ok(::buffa::bytes::Bytes::from(__d.read_bytes()?)) }
}
Type::TYPE_BYTES => {
quote! { __d.read_bytes() }
}
_ => {
let call = match ty {
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => {
quote! { __d.read_i32() }
}
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => {
quote! { __d.read_i64() }
}
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => quote! { __d.read_u32() },
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => quote! { __d.read_u64() },
Type::TYPE_FLOAT => quote! { __d.read_f32() },
Type::TYPE_DOUBLE => quote! { __d.read_f64() },
Type::TYPE_BOOL => quote! { __d.read_bool() },
Type::TYPE_STRING
| Type::TYPE_BYTES
| Type::TYPE_ENUM
| Type::TYPE_MESSAGE
| Type::TYPE_GROUP => {
unreachable!("text repeated read arm: non-numeric type {:?}", ty)
}
};
quote! { #call }
}
};
Ok(quote! {
#name_pat => dec.read_repeated_into(&mut self.#ident, |__d| #elem)?,
})
}
fn oneof_encode_stmt(
ctx: &CodeGenContext,
enum_ident: &proc_macro2::Ident,
oneof_name: &str,
fields: &[&FieldDescriptorProto],
oneof_prefix: &TokenStream,
proto_fqn: &str,
parent_features: &ResolvedFeatures,
) -> Result<TokenStream, CodeGenError> {
let field_ident = make_field_ident(oneof_name);
let qualified: TokenStream = quote! { #oneof_prefix #enum_ident };
let mut arms: Vec<TokenStream> = Vec::new();
for field in fields {
let features = crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let variant = crate::oneof::oneof_variant_ident(proto_name);
let ty = effective_type(ctx, field, &features);
let (name_lit, _) = text_field_name(proto_name, field, ty);
let boxed = crate::oneof::variant_boxed(
ctx,
ty,
&format!(".{proto_fqn}.{oneof_name}.{proto_name}"),
);
let body = match ty {
Type::TYPE_ENUM => {
let closed = is_closed_enum(&features);
enum_write(closed, "e! { __v })
}
Type::TYPE_MESSAGE | Type::TYPE_GROUP => {
let val = if boxed {
quote! { &**__v }
} else {
quote! { __v }
};
write_call(ty, &val)
}
_ if is_copy_scalar(ty) => write_call(ty, "e! { *__v }),
_ => write_call(ty, "e! { __v }),
};
arms.push(quote! {
#qualified::#variant(__v) => {
enc.write_field_name(#name_lit)?;
#body
}
});
}
Ok(quote! {
if let ::core::option::Option::Some(ref __v) = self.#field_ident {
match __v {
#(#arms)*
}
}
})
}
#[allow(clippy::too_many_arguments)]
fn oneof_merge_arms(
ctx: &CodeGenContext,
enum_ident: &proc_macro2::Ident,
oneof_name: &str,
fields: &[&FieldDescriptorProto],
oneof_prefix: &TokenStream,
current_package: &str,
proto_fqn: &str,
parent_features: &ResolvedFeatures,
nesting: usize,
) -> Result<Vec<TokenStream>, CodeGenError> {
let field_ident = make_field_ident(oneof_name);
let qualified: TokenStream = quote! { #oneof_prefix #enum_ident };
let mut arms: Vec<TokenStream> = Vec::new();
for field in fields {
let features = crate::features::resolve_field(ctx, field, parent_features);
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let variant = crate::oneof::oneof_variant_ident(proto_name);
let ty = effective_type(ctx, field, &features);
let (_, name_pat) = text_field_name(proto_name, field, ty);
let use_bytes = ty == Type::TYPE_BYTES && field_uses_bytes(ctx, proto_fqn, proto_name);
let boxed = crate::oneof::variant_boxed(
ctx,
ty,
&format!(".{proto_fqn}.{oneof_name}.{proto_name}"),
);
let assign = match ty {
Type::TYPE_MESSAGE | Type::TYPE_GROUP => {
let existing_ref = if boxed {
quote! { &mut **__existing }
} else {
quote! { __existing }
};
let wrapped = if boxed {
quote! { ::buffa::alloc::boxed::Box::new(__m) }
} else {
quote! { __m }
};
quote! {
if let ::core::option::Option::Some(
#qualified::#variant(ref mut __existing)
) = self.#field_ident {
dec.merge_message(#existing_ref)?;
} else {
let mut __m = ::core::default::Default::default();
dec.merge_message(&mut __m)?;
self.#field_ident = ::core::option::Option::Some(
#qualified::#variant(#wrapped)
);
}
}
}
Type::TYPE_ENUM => {
let closed = is_closed_enum(&features);
let enum_ty = enum_type_path(ctx, field, current_package, nesting)?;
let read = enum_read(closed, &enum_ty, &format_ident!("dec"));
quote! {
self.#field_ident = ::core::option::Option::Some(
#qualified::#variant(#read?)
);
}
}
Type::TYPE_STRING => {
let read = text_string_into(
field_string_repr(ctx, proto_fqn, proto_name),
quote! { dec.read_string()?.into_owned() },
);
quote! {
self.#field_ident = ::core::option::Option::Some(
#qualified::#variant(#read)
);
}
}
Type::TYPE_BYTES => {
let read = if use_bytes {
quote! { ::buffa::bytes::Bytes::from(dec.read_bytes()?) }
} else {
quote! { dec.read_bytes()? }
};
quote! {
self.#field_ident = ::core::option::Option::Some(
#qualified::#variant(#read)
);
}
}
_ => {
let read = read_call(ty);
quote! {
self.#field_ident = ::core::option::Option::Some(
#qualified::#variant(#read)
);
}
}
};
arms.push(quote! { #name_pat => { #assign } });
}
Ok(arms)
}
fn map_encode_stmt(
ctx: &CodeGenContext,
msg: &DescriptorProto,
field: &FieldDescriptorProto,
features: &ResolvedFeatures,
) -> Result<TokenStream, CodeGenError> {
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let name_lit = proto_name;
let (key_fd, val_fd) = find_map_entry_fields(msg, field)?;
let key_ty = effective_type_in_map_entry(ctx, key_fd, features);
let val_ty = effective_type_in_map_entry(ctx, val_fd, features);
let val_features = crate::features::resolve_field(ctx, val_fd, features);
let key_write = if key_ty == Type::TYPE_STRING {
write_call(key_ty, "e! { __k })
} else {
write_call(key_ty, "e! { *__k })
};
let val_write = match val_ty {
Type::TYPE_ENUM => {
let closed = is_closed_enum(&val_features);
enum_write(closed, "e! { __v })
}
Type::TYPE_MESSAGE => write_call(val_ty, "e! { __v }),
Type::TYPE_STRING | Type::TYPE_BYTES => write_call(val_ty, "e! { __v }),
_ => write_call(val_ty, "e! { *__v }),
};
Ok(quote! {
for (__k, __v) in &self.#ident {
enc.write_field_name(#name_lit)?;
enc.write_map_entry(|enc| {
enc.write_field_name("key")?;
#key_write
enc.write_field_name("value")?;
#val_write
::core::result::Result::Ok(())
})?;
}
})
}
fn map_merge_arm(
ctx: &CodeGenContext,
msg: &DescriptorProto,
field: &FieldDescriptorProto,
current_package: &str,
proto_fqn: &str,
features: &ResolvedFeatures,
nesting: usize,
) -> Result<TokenStream, CodeGenError> {
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let ident = make_field_ident(proto_name);
let name_lit = proto_name;
let (key_fd, val_fd) = find_map_entry_fields(msg, field)?;
let key_ty = effective_type_in_map_entry(ctx, key_fd, features);
let val_ty = effective_type_in_map_entry(ctx, val_fd, features);
let val_features = crate::features::resolve_field(ctx, val_fd, features);
let key_read = match key_ty {
Type::TYPE_STRING => quote! { __d.read_string()?.into_owned() },
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => quote! { __d.read_i32()? },
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => quote! { __d.read_i64()? },
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => quote! { __d.read_u32()? },
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => quote! { __d.read_u64()? },
Type::TYPE_BOOL => quote! { __d.read_bool()? },
_ => {
return Err(CodeGenError::Other(format!(
"unsupported map key type {key_ty:?}"
)))
}
};
let value_use_bytes =
map_value_use_bytes(ctx, Some(key_ty), Some(val_ty), proto_fqn, proto_name);
let val_read = match val_ty {
Type::TYPE_MESSAGE => quote! {
{
let mut __m = ::core::default::Default::default();
__d.merge_message(&mut __m)?;
__m
}
},
Type::TYPE_ENUM => {
let closed = is_closed_enum(&val_features);
let enum_ty = enum_type_path(ctx, val_fd, current_package, nesting)?;
let read = enum_read(closed, &enum_ty, &format_ident!("__d"));
quote! { #read? }
}
Type::TYPE_STRING => quote! { __d.read_string()?.into_owned() },
Type::TYPE_BYTES if value_use_bytes => {
quote! { ::buffa::bytes::Bytes::from(__d.read_bytes()?) }
}
Type::TYPE_BYTES => quote! { __d.read_bytes()? },
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => quote! { __d.read_i32()? },
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => quote! { __d.read_i64()? },
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => quote! { __d.read_u32()? },
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => quote! { __d.read_u64()? },
Type::TYPE_FLOAT => quote! { __d.read_f32()? },
Type::TYPE_DOUBLE => quote! { __d.read_f64()? },
Type::TYPE_BOOL => quote! { __d.read_bool()? },
Type::TYPE_GROUP => {
return Err(CodeGenError::Other("map value is a group".into()));
}
};
Ok(quote! {
#name_lit => {
let mut __pairs: ::buffa::alloc::vec::Vec<_> = ::buffa::alloc::vec::Vec::new();
dec.read_repeated_into(&mut __pairs, |__d| {
let mut __k = ::core::option::Option::None;
let mut __v = ::core::option::Option::None;
__d.merge_map_entry(|__d| {
while let ::core::option::Option::Some(__n) = __d.read_field_name()? {
match __n {
"key" => __k = ::core::option::Option::Some(#key_read),
"value" => __v = ::core::option::Option::Some(#val_read),
_ => __d.skip_value()?,
}
}
::core::result::Result::Ok(())
})?;
::core::result::Result::Ok((
__k.unwrap_or_default(),
__v.unwrap_or_default(),
))
})?;
for (__k, __v) in __pairs {
self.#ident.insert(__k, __v);
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
fn group_field(proto_name: &str, type_name: &str) -> FieldDescriptorProto {
FieldDescriptorProto {
name: Some(proto_name.into()),
type_name: Some(type_name.into()),
r#type: Some(Type::TYPE_GROUP),
..Default::default()
}
}
#[test]
fn text_name_non_group_is_field_name() {
let f = FieldDescriptorProto {
name: Some("child".into()),
type_name: Some(".pkg.Child".into()),
r#type: Some(Type::TYPE_MESSAGE),
..Default::default()
};
let (enc, pat) = text_field_name("child", &f, Type::TYPE_MESSAGE);
assert_eq!(enc, "child");
assert_eq!(pat.to_string(), "\"child\"");
}
#[test]
fn text_name_proto2_group_uses_type_name() {
let f = group_field("data", ".pkg.Parent.Data");
let (enc, pat) = text_field_name("data", &f, Type::TYPE_GROUP);
assert_eq!(enc, "Data");
assert_eq!(pat.to_string(), "\"Data\" | \"data\"");
}
#[test]
fn text_name_multi_word_group() {
let f = group_field("multiwordgroupfield", ".pkg.MultiWordGroupField");
let (enc, pat) = text_field_name("multiwordgroupfield", &f, Type::TYPE_GROUP);
assert_eq!(enc, "MultiWordGroupField");
assert_eq!(
pat.to_string(),
"\"MultiWordGroupField\" | \"multiwordgroupfield\""
);
}
#[test]
fn text_name_editions_delimited_unrelated_name_keeps_field_name() {
let f = group_field("foo", ".pkg.SomeMsg");
let (enc, pat) = text_field_name("foo", &f, Type::TYPE_GROUP);
assert_eq!(enc, "foo");
assert_eq!(pat.to_string(), "\"foo\"");
}
#[test]
fn text_name_editions_delimited_matching_name_uses_type_name() {
let f = group_field("data", ".pkg.Parent.Data");
let (enc, _) = text_field_name("data", &f, Type::TYPE_GROUP);
assert_eq!(enc, "Data");
}
#[test]
fn text_name_all_lowercase_type_no_duplicate_pattern() {
let f = group_field("data", ".pkg.data");
let (enc, pat) = text_field_name("data", &f, Type::TYPE_GROUP);
assert_eq!(enc, "data");
assert_eq!(pat.to_string(), "\"data\"");
}
}