use crate::generated::descriptor::field_descriptor_proto::{Label, Type};
use crate::generated::descriptor::DescriptorProto;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use crate::context::{ancillary_prefix, AncillaryKind, CodeGenContext, MessageScope};
use crate::defaults::parse_default_value;
use crate::features::ResolvedFeatures;
use crate::impl_message::{is_explicit_presence_scalar, is_real_oneof_member};
use crate::CodeGenError;
#[derive(Default)]
pub(crate) struct RegistryPaths {
pub json_any: Vec<TokenStream>,
pub text_any: Vec<TokenStream>,
pub json_ext: Vec<TokenStream>,
pub text_ext: Vec<TokenStream>,
}
impl RegistryPaths {
pub(crate) fn is_empty(&self) -> bool {
self.json_any.is_empty()
&& self.text_any.is_empty()
&& self.json_ext.is_empty()
&& self.text_ext.is_empty()
}
}
#[derive(Default)]
pub(crate) struct MessageOutput {
pub owned_top: TokenStream,
pub owned_mod: TokenStream,
pub oneof_tree: TokenStream,
pub view_tree: TokenStream,
pub view_oneof_tree: TokenStream,
pub reg: RegistryPaths,
}
pub(crate) fn generate_message(
ctx: &CodeGenContext,
msg: &DescriptorProto,
current_package: &str,
rust_name: &str,
proto_fqn: &str,
features: &ResolvedFeatures,
resolver: &crate::imports::ImportResolver,
) -> Result<MessageOutput, CodeGenError> {
let scope = MessageScope {
ctx,
current_package,
proto_fqn,
features,
nesting: 0,
};
generate_message_with_nesting(scope, msg, rust_name, resolver)
}
fn generate_message_with_nesting(
scope: MessageScope<'_>,
msg: &DescriptorProto,
rust_name: &str,
resolver: &crate::imports::ImportResolver,
) -> Result<MessageOutput, CodeGenError> {
let MessageScope {
ctx,
current_package,
proto_fqn,
features,
nesting,
} = scope;
let name_ident = format_ident!("{}", rust_name);
let is_message_set = msg
.options
.as_option()
.and_then(|o| o.message_set_wire_format)
.unwrap_or(false);
if is_message_set && !ctx.config.allow_message_set {
return Err(CodeGenError::MessageSetNotSupported {
message_name: proto_fqn.to_string(),
});
}
let nested_enums = msg
.enum_type
.iter()
.map(|e| {
let enum_name = e.name.as_deref().unwrap_or("");
let enum_fqn = format!("{}.{}", proto_fqn, enum_name);
crate::enumeration::generate_enum(ctx, e, enum_name, &enum_fqn, features, resolver)
})
.collect::<Result<Vec<_>, _>>()?;
let nested_msgs = msg
.nested_type
.iter()
.filter(|nested| {
!nested
.options
.as_option()
.and_then(|o| o.map_entry)
.unwrap_or(false)
})
.map(|nested| {
let nested_proto_name = nested.name.as_deref().unwrap_or("");
let nested_fqn = format!("{}.{}", proto_fqn, nested_proto_name);
let msg_features =
crate::features::resolve_child(features, crate::features::message_features(nested));
generate_message_with_nesting(
scope.nested(&nested_fqn, &msg_features),
nested,
nested_proto_name,
resolver,
)
})
.collect::<Result<Vec<_>, _>>()?;
let generated_fields: Vec<GeneratedField> = msg
.field
.iter()
.map(|f| generate_field(scope, msg, f, resolver))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect();
let direct_fields: Vec<&TokenStream> = generated_fields.iter().map(|f| &f.tokens).collect();
let mut debug_fields: Vec<(&Ident, bool)> = generated_fields
.iter()
.map(|f| (&f.ident, f.debug_redact))
.collect();
let setter_methods: TokenStream = generated_fields
.iter()
.filter_map(|f| f.setter.as_ref().map(|s| (f, s)))
.map(|(f, s)| {
let field_ident = &f.ident;
let setter_ident = &s.ident;
let field_name = field_ident.to_string();
let doc = if let Some(stripped) = field_name.strip_prefix("r#") {
format!("Sets `{stripped}` to `Some(value)`, consuming and returning `self`.")
} else {
format!(
"Sets [`Self::{field_name}`] to `Some(value)`, consuming and returning `self`."
)
};
let body = if s.use_into {
quote! { Some(value.into()) }
} else {
quote! { Some(value) }
};
let param = &s.param_type;
quote! {
#[must_use = "with_* setters return `self` by value; assign or chain the result"]
#[inline]
#[doc = #doc]
pub fn #setter_ident(mut self, value: #param) -> Self {
self.#field_ident = #body;
self
}
}
})
.collect();
let proto_name = msg.name.as_deref().unwrap_or(rust_name);
let mod_name_str = crate::oneof::to_snake_case(proto_name);
let mod_ident = make_field_ident(&mod_name_str);
let owned_wrap_ident = if nesting == 0 {
make_field_ident(&ctx.nested_module_name(current_package, proto_name))
} else {
mod_ident.clone()
};
let oneof_idents = crate::oneof::resolve_oneof_idents(msg);
let oneof_prefix = ancillary_prefix(AncillaryKind::Oneof, current_package, proto_fqn, nesting);
let gates = ctx.config.feature_gates();
let oneof_serde_attr = if ctx.config.generate_json {
crate::feature_gates::cfg_attr(quote! { serde(flatten) }, gates.json)
} else {
quote! {}
};
let oneof_generated: Vec<(TokenStream, Ident)> = msg
.oneof_decl
.iter()
.enumerate()
.filter_map(|(idx, oneof)| {
let enum_ident = oneof_idents.get(&idx)?;
let oneof_name = oneof.name.as_deref()?;
let field_ident = make_field_ident(oneof_name);
let opt = resolver.option();
let tokens = quote! {
#oneof_serde_attr
pub #field_ident: #opt<#oneof_prefix #enum_ident>,
};
Some((tokens, field_ident))
})
.collect();
let oneof_struct_fields: Vec<&TokenStream> = oneof_generated.iter().map(|(t, _)| t).collect();
debug_fields.extend(oneof_generated.iter().map(|(_, id)| (id, false)));
let has_extension_ranges = !msg.extension_range.is_empty();
let use_ext_json_wrapper =
ctx.config.generate_json && ctx.config.preserve_unknown_fields && has_extension_ranges;
let ext_json_wrapper_ident = format_ident!("__{}ExtJson", rust_name);
let (unknown_fields_field, ext_json_wrapper_def) = if use_ext_json_wrapper {
let arbitrary_attr = if ctx.config.generate_arbitrary {
quote! { #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] }
} else {
quote! {}
};
let proto_fqn_lit = proto_fqn;
let serde_impls = crate::feature_gates::cfg_block(
quote! {
impl ::serde::Serialize for #ext_json_wrapper_ident {
fn serialize<S: ::serde::Serializer>(&self, s: S)
-> ::core::result::Result<S::Ok, S::Error>
{
::buffa::extension_registry::serialize_extensions(#proto_fqn_lit, &self.0, s)
}
}
},
gates.json,
);
let serde_de_impl = crate::feature_gates::cfg_block(
quote! {
impl<'de> ::serde::Deserialize<'de> for #ext_json_wrapper_ident {
fn deserialize<D: ::serde::Deserializer<'de>>(d: D)
-> ::core::result::Result<Self, D::Error>
{
::buffa::extension_registry::deserialize_extensions(#proto_fqn_lit, d).map(Self)
}
}
},
gates.json,
);
let wrapper = quote! {
#[doc(hidden)]
#[derive(Clone, Debug, Default, PartialEq)]
#[repr(transparent)]
#arbitrary_attr
pub struct #ext_json_wrapper_ident(pub ::buffa::UnknownFields);
impl ::core::ops::Deref for #ext_json_wrapper_ident {
type Target = ::buffa::UnknownFields;
fn deref(&self) -> &::buffa::UnknownFields { &self.0 }
}
impl ::core::ops::DerefMut for #ext_json_wrapper_ident {
fn deref_mut(&mut self) -> &mut ::buffa::UnknownFields { &mut self.0 }
}
impl ::core::convert::From<::buffa::UnknownFields> for #ext_json_wrapper_ident {
fn from(u: ::buffa::UnknownFields) -> Self { Self(u) }
}
#serde_impls
#serde_de_impl
};
let flatten_attr = crate::feature_gates::cfg_attr(quote! { serde(flatten) }, gates.json);
let field = quote! {
#flatten_attr
#[doc(hidden)]
pub __buffa_unknown_fields: #ext_json_wrapper_ident,
};
(field, wrapper)
} else if ctx.config.preserve_unknown_fields {
let skip_attr = if ctx.config.generate_json {
crate::feature_gates::cfg_attr(quote! { serde(skip) }, gates.json)
} else {
quote! {}
};
let field = quote! {
#skip_attr
#[doc(hidden)]
pub __buffa_unknown_fields: ::buffa::UnknownFields,
};
(field, quote! {})
} else {
(quote! {}, quote! {})
};
let has_real_oneofs = !oneof_struct_fields.is_empty();
let needs_custom_deserialize = ctx.config.generate_json
&& (has_real_oneofs || (has_extension_ranges && ctx.config.preserve_unknown_fields));
let oneof_enums = msg
.oneof_decl
.iter()
.enumerate()
.map(|(idx, oneof)| {
crate::oneof::generate_oneof_enum(
ctx,
msg,
idx,
oneof,
current_package,
proto_fqn,
features,
resolver,
&oneof_idents,
nesting,
)
})
.collect::<Result<Vec<_>, _>>()?;
let message_impl = crate::impl_message::generate_message_impl(
ctx,
msg,
ctx.config.preserve_unknown_fields,
rust_name,
current_package,
proto_fqn,
features,
&oneof_idents,
&oneof_prefix,
nesting,
)?;
let text_impl = crate::impl_text::generate_text_impl(
ctx,
msg,
rust_name,
current_package,
proto_fqn,
features,
has_extension_ranges,
&oneof_idents,
&oneof_prefix,
nesting,
)?;
let type_url = format!("type.googleapis.com/{proto_fqn}");
let upper = crate::oneof::to_snake_case(rust_name).to_uppercase();
let (json_any_const, json_any_ident) = if ctx.config.generate_json {
let ident = format_ident!("__{}_JSON_ANY", upper);
let tokens = crate::feature_gates::cfg_block(
quote! {
#[doc(hidden)]
pub const #ident: ::buffa::type_registry::JsonAnyEntry
= ::buffa::type_registry::JsonAnyEntry {
type_url: #type_url,
to_json: ::buffa::type_registry::any_to_json::<#name_ident>,
from_json: ::buffa::type_registry::any_from_json::<#name_ident>,
is_wkt: false,
};
},
gates.json,
);
(tokens, Some(ident))
} else {
(quote! {}, None)
};
let (text_any_const, text_any_ident) = if ctx.config.generate_text {
let ident = format_ident!("__{}_TEXT_ANY", upper);
let tokens = crate::feature_gates::cfg_block(
quote! {
#[doc(hidden)]
pub const #ident: ::buffa::type_registry::TextAnyEntry
= ::buffa::type_registry::TextAnyEntry {
type_url: #type_url,
text_encode: ::buffa::type_registry::any_encode_text::<#name_ident>,
text_merge: ::buffa::type_registry::any_merge_text::<#name_ident>,
};
},
gates.text,
);
(tokens, Some(ident))
} else {
(quote! {}, None)
};
let serde_struct_derive = if ctx.config.generate_json {
let derives = if needs_custom_deserialize {
quote! { derive(::serde::Serialize) }
} else {
quote! { derive(::serde::Serialize, ::serde::Deserialize) }
};
let derive_attr = crate::feature_gates::cfg_attr(derives, gates.json);
let default_attr = crate::feature_gates::cfg_attr(quote! { serde(default) }, gates.json);
quote! {
#derive_attr
#default_attr
}
} else {
quote! {}
};
let arbitrary_derive = if ctx.config.generate_arbitrary {
quote! { #[cfg_attr(feature = "arbitrary", derive(::arbitrary::Arbitrary))] }
} else {
quote! {}
};
let custom_type_attrs =
CodeGenContext::matching_attributes(&ctx.config.type_attributes, proto_fqn)?;
let custom_message_attrs =
CodeGenContext::matching_attributes(&ctx.config.message_attributes, proto_fqn)?;
let custom_deserialize = if needs_custom_deserialize {
crate::feature_gates::cfg_block(
generate_custom_deserialize(
scope,
msg,
&name_ident,
&oneof_prefix,
resolver,
has_extension_ranges,
&oneof_idents,
)?,
gates.json,
)
} else {
quote! {}
};
let proto_elem_json_impl = if ctx.config.generate_json {
crate::feature_gates::cfg_block(
quote! {
impl ::buffa::json_helpers::ProtoElemJson for #name_ident {
fn serialize_proto_json<S: ::serde::Serializer>(
v: &Self,
s: S,
) -> ::core::result::Result<S::Ok, S::Error> {
::serde::Serialize::serialize(v, s)
}
fn deserialize_proto_json<'de, D: ::serde::Deserializer<'de>>(
d: D,
) -> ::core::result::Result<Self, D::Error> {
<Self as ::serde::Deserialize>::deserialize(d)
}
}
},
gates.json,
)
} else {
quote! {}
};
let custom_default_impl = generate_custom_default(
ctx,
msg,
&name_ident,
current_package,
proto_fqn,
features,
nesting,
)?;
let derive_default = if custom_default_impl.is_some() {
quote! {}
} else {
quote! { Default, }
};
let custom_default_impl = custom_default_impl.unwrap_or_default();
let mut nested_items = TokenStream::new();
let mut nested_oneof_tree = TokenStream::new();
let mut nested_view_tree = TokenStream::new();
let mut nested_view_oneof_tree = TokenStream::new();
let mut reg_paths = RegistryPaths::default();
if let Some(id) = &json_any_ident {
reg_paths.json_any.push(quote! { #id });
}
if let Some(id) = &text_any_ident {
reg_paths.text_any.push(quote! { #id });
}
let non_map_nested: Vec<&DescriptorProto> = msg
.nested_type
.iter()
.filter(|n| {
!n.options
.as_option()
.and_then(|o| o.map_entry)
.unwrap_or(false)
})
.collect();
for (nested_desc, nested_out) in non_map_nested.iter().zip(nested_msgs) {
nested_items.extend(nested_out.owned_top);
let nested_name = nested_desc.name.as_deref().unwrap_or("");
let nested_mod = make_field_ident(&crate::oneof::to_snake_case(nested_name));
for p in nested_out.reg.json_ext {
reg_paths.json_ext.push(quote! { #nested_mod :: #p });
}
for p in nested_out.reg.text_ext {
reg_paths.text_ext.push(quote! { #nested_mod :: #p });
}
for p in nested_out.reg.json_any {
reg_paths.json_any.push(quote! { #owned_wrap_ident :: #p });
}
for p in nested_out.reg.text_any {
reg_paths.text_any.push(quote! { #owned_wrap_ident :: #p });
}
if !nested_out.owned_mod.is_empty() {
let inner = nested_out.owned_mod;
nested_items.extend(quote! {
pub mod #nested_mod {
#[allow(unused_imports)]
use super::*;
#inner
}
});
}
nested_oneof_tree.extend(nested_out.oneof_tree);
nested_view_tree.extend(nested_out.view_tree);
nested_view_oneof_tree.extend(nested_out.view_oneof_tree);
}
let (nested_extensions, nested_ext_json, nested_ext_text) =
crate::extension::generate_extensions(
ctx,
&msg.extension,
current_package,
nesting + 1,
features,
proto_fqn,
)?;
for id in nested_ext_json {
reg_paths.json_ext.push(quote! { #id });
}
for id in nested_ext_text {
reg_paths.text_ext.push(quote! { #id });
}
let natural_reexports = collect_natural_reexports(scope, msg, &oneof_idents, &non_map_nested);
let owned_mod = quote! {
#(#nested_enums)*
#nested_items
#nested_extensions
#natural_reexports
};
let (own_view_top, own_view_oneofs) = if ctx.config.generate_views {
crate::view::generate_view_with_nesting(scope, msg, rust_name)?
} else {
(TokenStream::new(), TokenStream::new())
};
let wrap = |body: TokenStream| -> TokenStream {
if body.is_empty() {
return TokenStream::new();
}
quote! {
pub mod #mod_ident {
#[allow(unused_imports)]
use super::*;
#body
}
}
};
let oneof_tree = wrap(quote! { #(#oneof_enums)* #nested_oneof_tree });
let view_oneof_tree = wrap(quote! { #own_view_oneofs #nested_view_oneof_tree });
let view_tree = {
let nested_wrapped = wrap(nested_view_tree);
quote! { #own_view_top #nested_wrapped }
};
let struct_name_str = name_ident.to_string();
let debug_field_names: Vec<String> = debug_fields
.iter()
.map(|(id, _)| id.to_string().trim_start_matches("r#").to_string())
.collect();
let debug_field_values: Vec<TokenStream> = debug_fields
.iter()
.map(|(id, redacted)| {
if *redacted {
quote! { &::core::format_args!(#DEBUG_REDACT_PLACEHOLDER) }
} else {
quote! { &self.#id }
}
})
.collect();
let debug_impl = quote! {
impl ::core::fmt::Debug for #name_ident {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(#struct_name_str)
#(.field(#debug_field_names, #debug_field_values))*
.finish()
}
}
};
let message_doc =
crate::comments::doc_attrs_resolved(ctx.comment(proto_fqn), proto_fqn, &ctx.type_map);
let with_setters_impl = if ctx.config.generate_with_setters && !setter_methods.is_empty() {
quote! {
impl #name_ident {
#setter_methods
}
}
} else {
quote! {}
};
let top_level = quote! {
#message_doc
#[derive(Clone, PartialEq, #derive_default)]
#serde_struct_derive
#arbitrary_derive
#custom_type_attrs
#custom_message_attrs
pub struct #name_ident {
#(#direct_fields)*
#(#oneof_struct_fields)*
#unknown_fields_field
}
#debug_impl
#custom_default_impl
impl #name_ident {
pub const TYPE_URL: &'static str = #type_url;
}
#with_setters_impl
#message_impl
#text_impl
#custom_deserialize
#proto_elem_json_impl
#ext_json_wrapper_def
#json_any_const
#text_any_const
};
Ok(MessageOutput {
owned_top: top_level,
owned_mod,
oneof_tree,
view_tree,
view_oneof_tree,
reg: reg_paths,
})
}
pub(crate) struct ReexportCandidate {
pub(crate) name: String,
pub(crate) tokens: TokenStream,
}
fn collect_natural_reexports(
scope: MessageScope<'_>,
msg: &DescriptorProto,
oneof_idents: &std::collections::HashMap<usize, Ident>,
non_map_nested: &[&DescriptorProto],
) -> TokenStream {
use std::collections::BTreeSet;
let MessageScope {
ctx,
current_package,
proto_fqn,
nesting,
..
} = scope;
if !ctx.config.generate_views && oneof_idents.is_empty() {
return TokenStream::new();
}
let mut occupied: BTreeSet<String> = BTreeSet::new();
for nested in non_map_nested {
let name = nested.name.as_deref().unwrap_or("");
occupied.insert(name.to_string());
occupied.insert(crate::oneof::to_snake_case(name));
}
for e in &msg.enum_type {
occupied.insert(e.name.as_deref().unwrap_or("").to_string());
}
for ext in &msg.extension {
occupied.insert(
crate::extension::extension_const_ident(ext.name.as_deref().unwrap_or("")).to_string(),
);
}
let from_nesting = nesting + 1;
let view_prefix = ancillary_prefix(
AncillaryKind::View,
current_package,
proto_fqn,
from_nesting,
);
let oneof_prefix = ancillary_prefix(
AncillaryKind::Oneof,
current_package,
proto_fqn,
from_nesting,
);
let view_oneof_prefix = ancillary_prefix(
AncillaryKind::ViewOneof,
current_package,
proto_fqn,
from_nesting,
);
let mut candidates: Vec<ReexportCandidate> = Vec::new();
let inline = quote! { #[doc(inline)] };
let mut oneof_pairs: Vec<(usize, &Ident)> = oneof_idents.iter().map(|(k, v)| (*k, v)).collect();
oneof_pairs.sort_by_key(|(idx, _)| *idx);
for (_, enum_ident) in &oneof_pairs {
candidates.push(ReexportCandidate {
name: enum_ident.to_string(),
tokens: quote! { #inline pub use #oneof_prefix #enum_ident; },
});
}
if ctx.config.generate_views {
let views_gate = ctx.config.feature_gates().views;
for nested in non_map_nested {
let view_ident = format_ident!("{}View", nested.name.as_deref().unwrap_or(""));
candidates.push(ReexportCandidate {
name: view_ident.to_string(),
tokens: crate::feature_gates::cfg_block(
quote! { #inline pub use #view_prefix #view_ident; },
views_gate,
),
});
let owned_view_ident =
format_ident!("{}OwnedView", nested.name.as_deref().unwrap_or(""));
candidates.push(ReexportCandidate {
name: owned_view_ident.to_string(),
tokens: crate::feature_gates::cfg_block(
quote! { #inline pub use #view_prefix #owned_view_ident; },
views_gate,
),
});
}
for (_, enum_ident) in &oneof_pairs {
let view_ident = format_ident!("{}View", enum_ident);
candidates.push(ReexportCandidate {
name: view_ident.to_string(),
tokens: crate::feature_gates::cfg_block(
quote! { #inline pub use #view_oneof_prefix #enum_ident as #view_ident; },
views_gate,
),
});
}
}
emit_surviving_reexports(candidates, &occupied)
}
pub(crate) fn emit_surviving_reexports(
candidates: Vec<ReexportCandidate>,
occupied: &std::collections::BTreeSet<String>,
) -> TokenStream {
use std::collections::BTreeMap;
let mut counts: BTreeMap<String, usize> = BTreeMap::new();
for c in &candidates {
*counts.entry(c.name.clone()).or_insert(0) += 1;
}
let mut out = TokenStream::new();
for c in candidates {
if occupied.contains(&c.name) || counts[&c.name] > 1 {
continue;
}
out.extend(c.tokens);
}
out
}
fn generate_custom_deserialize(
scope: MessageScope<'_>,
msg: &DescriptorProto,
name_ident: &proc_macro2::Ident,
oneof_prefix: &TokenStream,
resolver: &crate::imports::ImportResolver,
has_extension_ranges: bool,
oneof_idents: &std::collections::HashMap<usize, Ident>,
) -> Result<TokenStream, CodeGenError> {
let MessageScope {
ctx,
current_package,
proto_fqn,
features,
nesting,
..
} = scope;
let mut field_vars = Vec::new();
let mut match_arms = Vec::new();
let mut field_inits = Vec::new();
for field in &msg.field {
if is_real_oneof_member(field) {
continue;
}
let (var, arm, init) = custom_deser_regular_field(scope, msg, field, resolver)?;
field_vars.push(var);
match_arms.push(arm);
field_inits.push(init);
}
for (idx, oneof) in msg.oneof_decl.iter().enumerate() {
let result = custom_deser_oneof_group(
ctx,
msg,
idx,
oneof,
current_package,
proto_fqn,
oneof_prefix,
features,
resolver,
oneof_idents,
nesting,
)?;
let Some((var, arms, init)) = result else {
continue;
};
field_vars.push(var);
match_arms.extend(arms);
field_inits.push(init);
}
let (ext_var, ext_arm, ext_init) = if has_extension_ranges && ctx.config.preserve_unknown_fields
{
let proto_fqn_lit = proto_fqn;
let var = quote! {
let mut __ext_records: ::buffa::alloc::vec::Vec<::buffa::UnknownField>
= ::buffa::alloc::vec::Vec::new();
};
let arm = quote! {
__k if __k.starts_with('[') => {
let __v: ::buffa::serde_json::Value = map.next_value()?;
match ::buffa::extension_registry::deserialize_extension_key(
#proto_fqn_lit, __k, __v,
) {
::core::option::Option::Some(::core::result::Result::Ok(__recs)) => {
for __rec in __recs {
__ext_records.push(__rec);
}
}
::core::option::Option::Some(::core::result::Result::Err(__e)) => {
return ::core::result::Result::Err(
<A::Error as ::serde::de::Error>::custom(__e),
);
}
::core::option::Option::None => {}
}
}
};
let init = quote! {
for __rec in __ext_records {
__r.__buffa_unknown_fields.push(__rec);
}
};
(var, arm, init)
} else {
(quote! {}, quote! {}, quote! {})
};
let expecting_msg = format!("struct {name_ident}");
Ok(quote! {
impl<'de> serde::Deserialize<'de> for #name_ident {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> ::core::result::Result<Self, D::Error> {
struct _V;
impl<'de> serde::de::Visitor<'de> for _V {
type Value = #name_ident;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(#expecting_msg)
}
#[allow(clippy::field_reassign_with_default)]
fn visit_map<A: serde::de::MapAccess<'de>>(
self,
mut map: A,
) -> ::core::result::Result<#name_ident, A::Error> {
#(#field_vars)*
#ext_var
while let Some(key) = map.next_key::<::buffa::alloc::string::String>()? {
match key.as_str() {
#(#match_arms)*
#ext_arm
_ => { map.next_value::<serde::de::IgnoredAny>()?; }
}
}
let mut __r = <#name_ident as ::core::default::Default>::default();
#(#field_inits)*
#ext_init
Ok(__r)
}
}
d.deserialize_map(_V)
}
}
})
}
fn deser_seed_expr(rust_type: &TokenStream, inner: TokenStream) -> TokenStream {
quote! {{
struct _S;
impl<'de> serde::de::DeserializeSeed<'de> for _S {
type Value = #rust_type;
fn deserialize<D: serde::Deserializer<'de>>(self, d: D)
-> ::core::result::Result<#rust_type, D::Error>
{
#inner
}
}
map.next_value_seed(_S)?
}}
}
fn custom_deser_regular_field(
scope: MessageScope<'_>,
msg: &DescriptorProto,
field: &crate::generated::descriptor::FieldDescriptorProto,
resolver: &crate::imports::ImportResolver,
) -> Result<(TokenStream, TokenStream, TokenStream), CodeGenError> {
let MessageScope { ctx, features, .. } = scope;
let field_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let json_name = field.json_name.as_deref().unwrap_or(field_name);
let var_ident = format_ident!("__f_{}", field_name);
let field_ident = make_field_ident(field_name);
let info = classify_field(scope, msg, field, resolver)?;
let rust_type = &info.rust_type;
let field_features = crate::features::resolve_field(ctx, field, features);
let (with_module, null_deser) = field_deser_modules(
crate::impl_message::effective_type(ctx, field, features),
&info,
&field_features,
);
let deser_expr = if is_value_field(field, info.is_repeated, info.is_map) {
let inner = quote! { ::buffa::json_helpers::message_field_always_present(d) };
deser_seed_expr(rust_type, inner)
} else if let Some(module) = with_module {
let module_path: syn::Path = syn::parse_str(module)
.map_err(|_| CodeGenError::InvalidTypePath(module.to_string()))?;
let inner = quote! { #module_path::deserialize(d) };
deser_seed_expr(rust_type, inner)
} else if null_deser.is_some() {
let inner = quote! { ::buffa::json_helpers::null_as_default(d) };
deser_seed_expr(rust_type, inner)
} else {
quote! { map.next_value::<#rust_type>()? }
};
let arm = if json_name != field_name {
quote! { #json_name | #field_name => { #var_ident = Some(#deser_expr); } }
} else {
quote! { #json_name => { #var_ident = Some(#deser_expr); } }
};
let var_decl = quote! { let mut #var_ident: ::core::option::Option<#rust_type> = None; };
let field_init = quote! {
if let ::core::option::Option::Some(v) = #var_ident { __r.#field_ident = v; }
};
Ok((var_decl, arm, field_init))
}
#[allow(clippy::too_many_arguments)]
fn custom_deser_oneof_group(
ctx: &CodeGenContext,
msg: &DescriptorProto,
idx: usize,
oneof: &crate::generated::descriptor::OneofDescriptorProto,
current_package: &str,
proto_fqn: &str,
oneof_prefix: &TokenStream,
features: &ResolvedFeatures,
resolver: &crate::imports::ImportResolver,
oneof_idents: &std::collections::HashMap<usize, Ident>,
nesting: usize,
) -> Result<Option<(TokenStream, Vec<TokenStream>, TokenStream)>, CodeGenError> {
let oneof_name = oneof
.name
.as_deref()
.ok_or(CodeGenError::MissingField("oneof.name"))?;
let enum_ident = match oneof_idents.get(&idx) {
Some(id) => id.clone(),
None => return Ok(None),
};
let var_ident = format_ident!("__oneof_{}", oneof_name);
let field_ident = make_field_ident(oneof_name);
let var_decl =
quote! { let mut #var_ident: ::core::option::Option<#oneof_prefix #enum_ident> = None; };
let mut arms = Vec::new();
for field in &msg.field {
if !is_real_oneof_member(field) || field.oneof_index != Some(idx as i32) {
continue;
}
let proto_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let json_name = field.json_name.as_deref().unwrap_or(proto_name);
let variant_ident = crate::oneof::oneof_variant_ident(proto_name);
let field_type = crate::impl_message::effective_type(ctx, field, features);
let variant_string_repr = if field_type == Type::TYPE_STRING {
crate::impl_message::field_string_repr(ctx, proto_fqn, proto_name)
} else {
crate::StringRepr::String
};
let variant_type = if field_type == Type::TYPE_BYTES
&& crate::impl_message::field_uses_bytes(ctx, proto_fqn, proto_name)
{
quote! { ::buffa::bytes::Bytes }
} else if field_type == Type::TYPE_STRING && !variant_string_repr.is_default() {
variant_string_repr.type_path(resolver)
} else {
scalar_or_message_type_nested(ctx, field, current_package, nesting, features, resolver)?
};
let qualified_enum: TokenStream = quote! { #oneof_prefix #enum_ident };
let arm = crate::oneof::oneof_variant_deser_arm(&crate::oneof::OneofVariantDeserInput {
variant_ident: &variant_ident,
variant_type: &variant_type,
json_name,
proto_name,
field_type,
null_forward: crate::oneof::null_is_valid_value(field),
is_boxed: crate::oneof::variant_boxed(
ctx,
field_type,
&format!(".{proto_fqn}.{oneof_name}.{proto_name}"),
),
enum_ident: &qualified_enum,
result_var: &var_ident,
oneof_name,
});
arms.push(arm);
}
let field_init = quote! { __r.#field_ident = #var_ident; };
Ok(Some((var_decl, arms, field_init)))
}
fn is_value_field(
field: &crate::generated::descriptor::FieldDescriptorProto,
is_repeated: bool,
is_map: bool,
) -> bool {
field.r#type.unwrap_or_default() == Type::TYPE_MESSAGE
&& !is_repeated
&& !is_map
&& field.type_name.as_deref() == Some(".google.protobuf.Value")
}
#[derive(Debug)]
struct FieldInfo {
rust_type: TokenStream,
struct_field_type: TokenStream,
is_repeated: bool,
is_map: bool,
is_optional: bool,
use_bytes: bool,
map_value_use_bytes: bool,
string_repr: crate::StringRepr,
is_required: bool,
map_key_type: Option<Type>,
map_value_type: Option<Type>,
map_value_enum_closed: Option<bool>,
inner_opt_type: Option<TokenStream>,
}
fn classify_field(
scope: MessageScope<'_>,
msg: &DescriptorProto,
field: &crate::generated::descriptor::FieldDescriptorProto,
resolver: &crate::imports::ImportResolver,
) -> Result<FieldInfo, CodeGenError> {
let MessageScope {
ctx,
current_package,
proto_fqn,
features,
nesting,
} = scope;
let label = field.label.unwrap_or_default();
let field_type = crate::impl_message::effective_type(ctx, field, features);
let is_repeated = label == Label::LABEL_REPEATED;
let map_entry = if is_repeated {
find_map_entry(msg, field)
} else {
None
};
let is_map = map_entry.is_some();
let is_optional = is_explicit_presence_scalar(field, field_type, features);
let is_required = crate::impl_message::is_required_field(field, features);
let field_name = field.name.as_deref().unwrap_or("");
let field_fqn = format!(".{}.{}", proto_fqn, field_name);
let use_bytes = field_type == Type::TYPE_BYTES && ctx.use_bytes_type(&field_fqn);
let map_value_use_bytes = map_entry.is_some_and(|e| {
crate::impl_message::map_value_use_bytes(
ctx,
map_entry_key_type(ctx, e, features),
map_entry_value_type(ctx, e, features),
proto_fqn,
field_name,
)
});
let bytes_type = if use_bytes {
quote! { ::buffa::bytes::Bytes }
} else {
let vec = resolver.vec();
quote! { #vec<u8> }
};
let string_repr = if field_type == Type::TYPE_STRING {
ctx.string_repr(&field_fqn)
} else {
crate::StringRepr::String
};
let string_type = string_repr.type_path(resolver);
let mut inner_opt_type: Option<TokenStream> = None;
let rust_type = if let Some(entry) = map_entry {
map_rust_type_from_entry(scope, entry, map_value_use_bytes, resolver)?
} else if is_repeated {
let elem = if field_type == Type::TYPE_BYTES {
bytes_type
} else if field_type == Type::TYPE_STRING {
string_type
} else {
scalar_or_message_type_nested(ctx, field, current_package, nesting, features, resolver)?
};
{
let vec = resolver.vec();
quote! { #vec<#elem> }
}
} else if field_type == Type::TYPE_MESSAGE || field_type == Type::TYPE_GROUP {
let inner = resolve_message_type(scope, field)?;
{
let mf = resolver.message_field();
quote! { #mf<#inner> }
}
} else if is_optional {
let inner = if field_type == Type::TYPE_ENUM {
resolve_enum_type(scope, field, resolver)?
} else if field_type == Type::TYPE_BYTES {
bytes_type
} else if field_type == Type::TYPE_STRING {
string_type
} else {
scalar_rust_type(field_type, resolver)?
};
inner_opt_type = Some(inner.clone());
{
let opt = resolver.option();
quote! { #opt<#inner> }
}
} else if field_type == Type::TYPE_ENUM {
resolve_enum_type(scope, field, resolver)?
} else if field_type == Type::TYPE_BYTES {
bytes_type
} else if field_type == Type::TYPE_STRING {
string_type
} else {
scalar_rust_type(field_type, resolver)?
};
let self_fqn = format!(".{proto_fqn}");
let is_self_ref = field.type_name.as_deref() == Some(self_fqn.as_str()) && !is_map;
let struct_field_type = if is_self_ref {
if is_repeated {
let vec = resolver.vec();
quote! { #vec<Self> }
} else {
let mf = resolver.message_field();
quote! { #mf<Self> }
}
} else {
rust_type.clone()
};
let map_key_type = map_entry.and_then(|e| map_entry_key_type(ctx, e, features));
let map_value_type = map_entry.and_then(|e| map_entry_value_type(ctx, e, features));
let map_value_enum_closed = if map_value_type == Some(Type::TYPE_ENUM) {
map_entry
.and_then(|e| e.field.iter().find(|f| f.number == Some(2)))
.map(|val_fd| {
let val_features = crate::features::resolve_field(ctx, val_fd, features);
is_closed_enum(&val_features)
})
} else {
None
};
Ok(FieldInfo {
rust_type,
struct_field_type,
is_repeated,
is_map,
is_optional,
is_required,
use_bytes,
map_value_use_bytes,
string_repr,
map_key_type,
map_value_type,
map_value_enum_closed,
inner_opt_type,
})
}
struct SetterInfo {
ident: Ident,
param_type: TokenStream,
use_into: bool,
}
struct GeneratedField {
tokens: TokenStream,
ident: Ident,
setter: Option<SetterInfo>,
debug_redact: bool,
}
fn generate_field(
scope: MessageScope<'_>,
msg: &DescriptorProto,
field: &crate::generated::descriptor::FieldDescriptorProto,
resolver: &crate::imports::ImportResolver,
) -> Result<Option<GeneratedField>, CodeGenError> {
let MessageScope {
ctx,
proto_fqn,
features,
..
} = scope;
let field_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let field_number = field.number.unwrap_or(0);
if is_real_oneof_member(field) {
return Ok(None);
}
let info = classify_field(scope, msg, field, resolver)?;
let rust_name = make_field_ident(field_name);
let field_fqn = format!("{}.{}", proto_fqn, field_name);
let tag_line = format!("Field {field_number}: `{field_name}`");
let doc = crate::comments::doc_attrs_with_tag_resolved(
ctx.comment(&field_fqn),
&tag_line,
proto_fqn,
&ctx.type_map,
);
let serde_attr = if ctx.config.generate_json {
serde_field_attr(ctx, field, field_name, &info, features)
} else {
quote! {}
};
let custom_field_attrs =
CodeGenContext::matching_attributes(&ctx.config.field_attributes, &field_fqn)?;
let arbitrary_field_attr = if ctx.config.generate_arbitrary && info.use_bytes && !info.is_map {
let helper = if info.is_optional {
quote! { ::buffa::__private::arbitrary_bytes_opt }
} else if info.is_repeated {
quote! { ::buffa::__private::arbitrary_bytes_vec }
} else {
quote! { ::buffa::__private::arbitrary_bytes }
};
quote! { #[cfg_attr(feature = "arbitrary", arbitrary(with = #helper))] }
} else if ctx.config.generate_arbitrary && info.map_value_use_bytes {
quote! { #[cfg_attr(feature = "arbitrary", arbitrary(with = ::buffa::__private::arbitrary_bytes_map))] }
} else if ctx.config.generate_arbitrary
&& info.string_repr == crate::StringRepr::EcoString
&& !info.is_map
{
let helper = if info.is_optional {
quote! { ::buffa::__private::arbitrary_ecow_opt }
} else if info.is_repeated {
quote! { ::buffa::__private::arbitrary_ecow_vec }
} else {
quote! { ::buffa::__private::arbitrary_ecow }
};
quote! { #[cfg_attr(feature = "arbitrary", arbitrary(with = #helper))] }
} else {
quote! {}
};
let rust_type = &info.struct_field_type;
let tokens = quote! {
#doc
#serde_attr
#arbitrary_field_attr
#custom_field_attrs
pub #rust_name: #rust_type,
};
let setter = if let Some(inner) = &info.inner_opt_type {
let field_type = crate::impl_message::effective_type(ctx, field, features);
let setter_ident = format_ident!("with_{}", field_name);
let (param_type, use_into) = match field_type {
Type::TYPE_STRING | Type::TYPE_BYTES | Type::TYPE_ENUM => {
(quote! { impl Into<#inner> }, true)
}
_ => (quote! { #inner }, false),
};
Some(SetterInfo {
ident: setter_ident,
param_type,
use_into,
})
} else {
None
};
Ok(Some(GeneratedField {
tokens,
ident: rust_name,
setter,
debug_redact: is_debug_redacted(field),
}))
}
pub(crate) fn is_map_field(
msg: &DescriptorProto,
field: &crate::generated::descriptor::FieldDescriptorProto,
) -> bool {
field.r#type.unwrap_or_default() == Type::TYPE_MESSAGE && find_map_entry(msg, field).is_some()
}
pub(crate) const DEBUG_REDACT_PLACEHOLDER: &str = "[REDACTED]";
pub(crate) fn is_debug_redacted(
field: &crate::generated::descriptor::FieldDescriptorProto,
) -> bool {
field
.options
.as_option()
.and_then(|o| o.debug_redact)
.unwrap_or(false)
}
pub(crate) fn find_map_entry<'a>(
msg: &'a DescriptorProto,
field: &crate::generated::descriptor::FieldDescriptorProto,
) -> Option<&'a DescriptorProto> {
let type_name = field.type_name.as_deref()?;
msg.nested_type.iter().find(|nested| {
nested
.options
.as_option()
.and_then(|o| o.map_entry)
.unwrap_or(false)
&& nested
.name
.as_deref()
.is_some_and(|n| type_name.ends_with(&format!(".{n}")))
})
}
fn map_entry_key_type(
ctx: &CodeGenContext,
entry: &DescriptorProto,
features: &ResolvedFeatures,
) -> Option<Type> {
let key_field = entry.field.iter().find(|f| f.number == Some(1))?;
Some(crate::impl_message::effective_type_in_map_entry(
ctx, key_field, features,
))
}
fn map_entry_value_type(
ctx: &CodeGenContext,
entry: &DescriptorProto,
features: &ResolvedFeatures,
) -> Option<Type> {
let value_field = entry.field.iter().find(|f| f.number == Some(2))?;
Some(crate::impl_message::effective_type_in_map_entry(
ctx,
value_field,
features,
))
}
fn map_rust_type_from_entry(
scope: MessageScope<'_>,
entry: &DescriptorProto,
value_use_bytes: bool,
resolver: &crate::imports::ImportResolver,
) -> Result<TokenStream, CodeGenError> {
let MessageScope {
ctx,
current_package,
features,
nesting,
..
} = scope;
let key_field = entry
.field
.iter()
.find(|f| f.number == Some(1))
.ok_or(CodeGenError::MissingField("map_entry.key"))?;
let value_field = entry
.field
.iter()
.find(|f| f.number == Some(2))
.ok_or(CodeGenError::MissingField("map_entry.value"))?;
let key_type = scalar_or_message_type_nested(
ctx,
key_field,
current_package,
nesting,
features,
resolver,
)?;
let value_type = if value_use_bytes
&& crate::impl_message::effective_type_in_map_entry(ctx, value_field, features)
== Type::TYPE_BYTES
{
quote! { ::buffa::bytes::Bytes }
} else {
scalar_or_message_type_nested(
ctx,
value_field,
current_package,
nesting,
features,
resolver,
)?
};
let hm = resolver.hashmap();
Ok(quote! { #hm<#key_type, #value_type> })
}
pub(crate) fn scalar_or_message_type_nested(
ctx: &CodeGenContext,
field: &crate::generated::descriptor::FieldDescriptorProto,
current_package: &str,
nesting: usize,
features: &ResolvedFeatures,
resolver: &crate::imports::ImportResolver,
) -> Result<TokenStream, CodeGenError> {
let scope = MessageScope {
ctx,
current_package,
proto_fqn: "",
features,
nesting,
};
match crate::impl_message::effective_type(ctx, field, features) {
Type::TYPE_MESSAGE | Type::TYPE_GROUP => resolve_message_type(scope, field),
Type::TYPE_ENUM => resolve_enum_type(scope, field, resolver),
other => scalar_rust_type(other, resolver),
}
}
fn resolve_message_type(
scope: MessageScope<'_>,
field: &crate::generated::descriptor::FieldDescriptorProto,
) -> Result<TokenStream, CodeGenError> {
let type_name = field
.type_name
.as_deref()
.ok_or(CodeGenError::MissingField("field.type_name"))?;
let path_str = scope
.ctx
.rust_type_relative(type_name, scope.current_package, scope.nesting)
.ok_or_else(|| {
CodeGenError::Other(format!(
"message type '{type_name}' not found in descriptor set; \
ensure all imports are included with --include_imports"
))
})?;
let ty = rust_path_to_tokens(&path_str);
Ok(quote! { #ty })
}
fn resolve_enum_type(
scope: MessageScope<'_>,
field: &crate::generated::descriptor::FieldDescriptorProto,
resolver: &crate::imports::ImportResolver,
) -> Result<TokenStream, CodeGenError> {
let type_name = field
.type_name
.as_deref()
.ok_or(CodeGenError::MissingField("field.type_name"))?;
let path_str = scope
.ctx
.rust_type_relative(type_name, scope.current_package, scope.nesting)
.ok_or_else(|| {
CodeGenError::Other(format!(
"enum type '{type_name}' not found in descriptor set; \
ensure all imports are included with --include_imports"
))
})?;
let ty = rust_path_to_tokens(&path_str);
let field_features = crate::features::resolve_field(scope.ctx, field, scope.features);
if is_closed_enum(&field_features) {
Ok(quote! { #ty })
} else {
let ev = resolver.enum_value();
Ok(quote! { #ev<#ty> })
}
}
pub(crate) fn is_closed_enum(features: &ResolvedFeatures) -> bool {
features.enum_type == crate::features::EnumType::Closed
}
fn scalar_rust_type(
t: Type,
resolver: &crate::imports::ImportResolver,
) -> Result<TokenStream, CodeGenError> {
match t {
Type::TYPE_DOUBLE => Ok(quote! { f64 }),
Type::TYPE_FLOAT => Ok(quote! { f32 }),
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => Ok(quote! { i64 }),
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => Ok(quote! { u64 }),
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => Ok(quote! { i32 }),
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => Ok(quote! { u32 }),
Type::TYPE_BOOL => Ok(quote! { bool }),
Type::TYPE_STRING => Ok(resolver.string()),
Type::TYPE_BYTES => {
let vec = resolver.vec();
Ok(quote! { #vec<u8> })
}
Type::TYPE_GROUP | Type::TYPE_MESSAGE | Type::TYPE_ENUM => Err(CodeGenError::Other(
format!("scalar_rust_type called for non-scalar type {:?}", t),
)),
}
}
fn field_deser_modules(
field_type: Type,
info: &FieldInfo,
features: &ResolvedFeatures,
) -> (Option<&'static str>, Option<&'static str>) {
let with_module = if info.is_map {
map_serde_module(info)
} else if info.is_repeated {
repeated_serde_module(field_type, features)
} else if info.is_optional {
optional_serde_module(field_type, features)
} else {
singular_serde_module(field_type, features)
};
let null_deser = if with_module.is_none() && (info.is_repeated || info.is_map) {
Some("::buffa::json_helpers::null_as_default")
} else {
None
};
(with_module, null_deser)
}
fn value_needs_proto_json(ty: Type) -> bool {
matches!(
ty,
Type::TYPE_INT64
| Type::TYPE_SINT64
| Type::TYPE_SFIXED64
| Type::TYPE_UINT64
| Type::TYPE_FIXED64
| Type::TYPE_FLOAT
| Type::TYPE_DOUBLE
| Type::TYPE_BYTES
)
}
fn map_serde_module(info: &FieldInfo) -> Option<&'static str> {
if matches!(info.map_key_type, Some(Type::TYPE_BYTES)) {
return Some(if matches!(info.map_value_type, Some(Type::TYPE_BYTES)) {
"::buffa::json_helpers::bytes_key_bytes_val_map"
} else {
"::buffa::json_helpers::bytes_key_map"
});
}
if let Some(closed) = info.map_value_enum_closed {
return Some(if closed {
"::buffa::json_helpers::map_closed_enum"
} else {
"::buffa::json_helpers::map_enum"
});
}
if matches!(info.map_value_type, Some(Type::TYPE_MESSAGE)) {
let is_string_key = matches!(info.map_key_type, Some(Type::TYPE_STRING));
return if is_string_key {
None
} else {
Some("::buffa::json_helpers::string_key_map")
};
}
let value_ty = info.map_value_type.unwrap_or(Type::TYPE_STRING);
let is_string_key = matches!(info.map_key_type, Some(Type::TYPE_STRING));
if value_needs_proto_json(value_ty) {
Some("::buffa::json_helpers::proto_map")
} else if is_string_key {
None
} else {
Some("::buffa::json_helpers::string_key_map")
}
}
fn repeated_serde_module(field_type: Type, features: &ResolvedFeatures) -> Option<&'static str> {
match field_type {
Type::TYPE_ENUM => Some(if is_closed_enum(features) {
"::buffa::json_helpers::repeated_closed_enum"
} else {
"::buffa::json_helpers::repeated_enum"
}),
Type::TYPE_MESSAGE | Type::TYPE_GROUP => None,
ty if value_needs_proto_json(ty) => Some("::buffa::json_helpers::proto_seq"),
_ => None,
}
}
fn optional_serde_module(field_type: Type, features: &ResolvedFeatures) -> Option<&'static str> {
match field_type {
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => {
Some("::buffa::json_helpers::opt_int32")
}
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => Some("::buffa::json_helpers::opt_uint32"),
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => {
Some("::buffa::json_helpers::opt_int64")
}
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => Some("::buffa::json_helpers::opt_uint64"),
Type::TYPE_FLOAT => Some("::buffa::json_helpers::opt_float"),
Type::TYPE_DOUBLE => Some("::buffa::json_helpers::opt_double"),
Type::TYPE_BYTES => Some("::buffa::json_helpers::opt_bytes"),
Type::TYPE_ENUM => Some(if is_closed_enum(features) {
"::buffa::json_helpers::opt_closed_enum"
} else {
"::buffa::json_helpers::opt_enum"
}),
_ => None,
}
}
fn singular_serde_module(field_type: Type, features: &ResolvedFeatures) -> Option<&'static str> {
match field_type {
Type::TYPE_BOOL => Some("::buffa::json_helpers::proto_bool"),
Type::TYPE_STRING => Some("::buffa::json_helpers::proto_string"),
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => {
Some("::buffa::json_helpers::int32")
}
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => Some("::buffa::json_helpers::uint32"),
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => {
Some("::buffa::json_helpers::int64")
}
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => Some("::buffa::json_helpers::uint64"),
Type::TYPE_FLOAT => Some("::buffa::json_helpers::float"),
Type::TYPE_DOUBLE => Some("::buffa::json_helpers::double"),
Type::TYPE_BYTES => Some("::buffa::json_helpers::bytes"),
Type::TYPE_ENUM => Some(if is_closed_enum(features) {
"::buffa::json_helpers::closed_enum"
} else {
"::buffa::json_helpers::proto_enum"
}),
_ => None,
}
}
fn skip_serializing_predicate(
field_type: Type,
info: &FieldInfo,
features: &ResolvedFeatures,
) -> Option<&'static str> {
if info.is_required {
None
} else if info.is_map {
Some("::buffa::__private::HashMap::is_empty")
} else if info.is_repeated {
Some("::buffa::json_helpers::skip_if::is_empty_vec")
} else if info.is_optional {
Some("::core::option::Option::is_none")
} else {
Some(singular_skip_predicate(field_type, features))
}
}
fn singular_skip_predicate(field_type: Type, features: &ResolvedFeatures) -> &'static str {
match field_type {
Type::TYPE_MESSAGE | Type::TYPE_GROUP => {
"::buffa::json_helpers::skip_if::is_unset_message_field"
}
Type::TYPE_ENUM => {
if is_closed_enum(features) {
"::buffa::json_helpers::skip_if::is_default_closed_enum"
} else {
"::buffa::json_helpers::skip_if::is_default_enum_value"
}
}
Type::TYPE_INT64 | Type::TYPE_SINT64 | Type::TYPE_SFIXED64 => {
"::buffa::json_helpers::skip_if::is_zero_i64"
}
Type::TYPE_UINT64 | Type::TYPE_FIXED64 => "::buffa::json_helpers::skip_if::is_zero_u64",
Type::TYPE_INT32 | Type::TYPE_SINT32 | Type::TYPE_SFIXED32 => {
"::buffa::json_helpers::skip_if::is_zero_i32"
}
Type::TYPE_UINT32 | Type::TYPE_FIXED32 => "::buffa::json_helpers::skip_if::is_zero_u32",
Type::TYPE_BOOL => "::buffa::json_helpers::skip_if::is_false",
Type::TYPE_FLOAT => "::buffa::json_helpers::skip_if::is_zero_f32",
Type::TYPE_DOUBLE => "::buffa::json_helpers::skip_if::is_zero_f64",
Type::TYPE_STRING => "::buffa::json_helpers::skip_if::is_empty_str",
Type::TYPE_BYTES => "::buffa::json_helpers::skip_if::is_empty_bytes",
}
}
fn serde_field_attr(
ctx: &CodeGenContext,
field: &crate::generated::descriptor::FieldDescriptorProto,
field_name: &str,
info: &FieldInfo,
features: &ResolvedFeatures,
) -> TokenStream {
let field_type = crate::impl_message::effective_type(ctx, field, features);
let field_features = crate::features::resolve_field(ctx, field, features);
let json_name = field.json_name.as_deref().unwrap_or(field_name);
let (with_module, null_deser) = field_deser_modules(field_type, info, &field_features);
let skip_if = skip_serializing_predicate(field_type, info, &field_features);
let needs_alias = json_name != field_name;
let alias_part = if needs_alias {
quote! { , alias = #field_name }
} else {
quote! {}
};
let with_part = if let Some(module) = with_module {
quote! { , with = #module }
} else {
quote! {}
};
let skip_part = if let Some(skip) = skip_if {
quote! { , skip_serializing_if = #skip }
} else {
quote! {}
};
let deser_part = if is_value_field(field, info.is_repeated, info.is_map) {
quote! { , deserialize_with = "::buffa::json_helpers::message_field_always_present" }
} else if let Some(deser) = null_deser {
quote! { , deserialize_with = #deser }
} else {
quote! {}
};
crate::feature_gates::cfg_attr(
quote! { serde(rename = #json_name #alias_part #with_part #skip_part #deser_part) },
ctx.config.feature_gates().json,
)
}
fn generate_custom_default(
ctx: &CodeGenContext,
msg: &DescriptorProto,
name_ident: &Ident,
current_package: &str,
proto_fqn: &str,
features: &ResolvedFeatures,
nesting: usize,
) -> Result<Option<TokenStream>, CodeGenError> {
if features.field_presence != crate::features::FieldPresence::Explicit {
return Ok(None);
}
let mut has_custom = false;
for field in &msg.field {
if is_real_oneof_member(field) {
continue;
}
let field_type = crate::impl_message::effective_type(ctx, field, features);
let is_optional = is_explicit_presence_scalar(field, field_type, features);
let is_repeated = field.label.unwrap_or_default() == Label::LABEL_REPEATED;
if is_optional
|| is_repeated
|| field_type == Type::TYPE_MESSAGE
|| field_type == Type::TYPE_GROUP
{
continue;
}
if field
.default_value
.as_deref()
.is_some_and(|s| !s.is_empty())
{
has_custom = true;
break;
}
}
if !has_custom {
return Ok(None);
}
let mut field_inits = Vec::new();
for field in &msg.field {
if is_real_oneof_member(field) {
continue;
}
let field_name = field
.name
.as_deref()
.ok_or(CodeGenError::MissingField("field.name"))?;
let field_ident = make_field_ident(field_name);
let field_type = crate::impl_message::effective_type(ctx, field, features);
let is_optional = is_explicit_presence_scalar(field, field_type, features);
let is_repeated = field.label.unwrap_or_default() == Label::LABEL_REPEATED;
if is_optional
|| is_repeated
|| field_type == Type::TYPE_MESSAGE
|| field_type == Type::TYPE_GROUP
{
field_inits.push(quote! { #field_ident: ::core::default::Default::default(), });
continue;
}
if let Some(expr) = parse_default_value(
field,
ctx,
current_package,
features,
nesting,
crate::impl_message::field_string_repr(ctx, proto_fqn, field_name),
)? {
field_inits.push(quote! { #field_ident: #expr, });
} else {
field_inits.push(quote! { #field_ident: ::core::default::Default::default(), });
}
}
for (idx, oneof) in msg.oneof_decl.iter().enumerate() {
let oneof_name = oneof
.name
.as_deref()
.ok_or(CodeGenError::MissingField("oneof.name"))?;
let has_real = msg
.field
.iter()
.any(|f| is_real_oneof_member(f) && f.oneof_index == Some(idx as i32));
if has_real {
let ident = make_field_ident(oneof_name);
field_inits.push(quote! { #ident: ::core::default::Default::default(), });
}
}
let unknown_fields_init = if ctx.config.preserve_unknown_fields {
quote! { __buffa_unknown_fields: ::core::default::Default::default(), }
} else {
quote! {}
};
Ok(Some(quote! {
impl ::core::default::Default for #name_ident {
fn default() -> Self {
Self {
#(#field_inits)*
#unknown_fields_init
}
}
}
}))
}
pub(crate) use crate::idents::{make_field_ident, rust_path_to_tokens};