Skip to main content

buffa_codegen/
lib.rs

1//! Shared code generation logic for buffa.
2//!
3//! This crate takes protobuf descriptors (`google.protobuf.FileDescriptorProto`,
4//! decoded from binary `FileDescriptorSet` data) and emits Rust source code
5//! that uses the `buffa` runtime.
6//!
7//! It is used by:
8//! - `protoc-gen-buffa` (protoc plugin)
9//! - `buffa-build` (build.rs integration)
10//!
11//! # Architecture
12//!
13//! The code generator is intentionally decoupled from how descriptors are
14//! obtained. It receives fully-resolved `FileDescriptorProto`s and produces
15//! Rust source strings. This means:
16//!
17//! - It doesn't parse `.proto` files.
18//! - It doesn't invoke `protoc`.
19//! - It doesn't do import resolution or name linking.
20//!
21//! All of that is handled upstream (by protoc, buf, or a future parser).
22
23pub(crate) mod comments;
24pub mod context;
25pub(crate) mod defaults;
26pub(crate) mod enumeration;
27pub(crate) mod extension;
28pub(crate) mod feature_gates;
29pub use feature_gates::FeatureGateNames;
30pub(crate) mod features;
31pub(crate) mod field_names;
32#[doc(hidden)]
33pub use buffa_descriptor::generated;
34pub(crate) mod feature_overrides;
35pub mod idents;
36pub(crate) mod impl_message;
37pub(crate) mod impl_text;
38pub(crate) mod imports;
39pub(crate) mod lazy_view;
40pub(crate) mod message;
41pub(crate) mod oneof;
42pub(crate) mod owned_view;
43pub(crate) mod reflect;
44pub(crate) mod reflect_owned;
45pub(crate) mod reflect_view;
46pub(crate) mod view;
47
48use crate::generated::descriptor::FileDescriptorProto;
49use proc_macro2::TokenStream;
50use quote::{format_ident, quote};
51
52/// Environment variable overriding the element-memory bound buffa's tooling
53/// applies when decoding a descriptor set.
54///
55/// Accepts a byte count, or `unlimited` / `max` (both lowercase) for no bound.
56/// A value that is not valid UTF-8 is treated as unset.
57pub const ELEMENT_MEMORY_LIMIT_ENV: &str = "BUFFA_ELEMENT_MEMORY_LIMIT";
58
59/// Element-memory bound buffa's build tooling applies to a descriptor set —
60/// the `protoc` plugins to a `CodeGeneratorRequest`, `buffa-build` to a
61/// `FileDescriptorSet`.
62///
63/// Much higher than [`buffa::DEFAULT_ELEMENT_MEMORY_LIMIT`], which is sized for
64/// untrusted input: a build's descriptors come from a compiler the caller
65/// invoked, or a file the caller named. It is deliberately not unbounded, so a
66/// truncated or corrupt descriptor set still fails with an error rather than
67/// exhausting memory.
68///
69/// 1 GiB is roughly twice what the largest schemas need. Descriptor types are
70/// wide structs, so the element footprint runs several times the encoded size
71/// — around 6x in practice, making this about 160 MB of descriptors.
72pub const TOOLING_ELEMENT_MEMORY_LIMIT: usize = 1024 * 1024 * 1024;
73
74/// Decode options for a descriptor set produced by the caller's own build,
75/// honouring [`ELEMENT_MEMORY_LIMIT_ENV`].
76///
77/// The override is an environment variable rather than a `protoc` plugin
78/// option because the options string travels *inside* the request: it cannot
79/// be read until the request has already been decoded.
80///
81/// # Errors
82///
83/// Returns a message naming the variable if its value is not a byte count or
84/// `unlimited` / `max`.
85pub fn tooling_decode_options() -> Result<buffa::DecodeOptions, String> {
86    let raw = std::env::var(ELEMENT_MEMORY_LIMIT_ENV).ok();
87    let limit = parse_element_memory_limit(raw.as_deref())?;
88    Ok(buffa::DecodeOptions::new().with_element_memory_limit(limit))
89}
90
91/// Describe a failure to decode a descriptor set, naming the override when the
92/// element-memory bound is what rejected it.
93///
94/// `subject` is the message being decoded, e.g. `"CodeGeneratorRequest"`.
95/// `limit` is the bound that was actually in force, which is not
96/// [`TOOLING_ELEMENT_MEMORY_LIMIT`] once the environment overrides it — read it
97/// back with `DecodeOptions::element_memory_limit`.
98///
99/// The bare error text names no remedy, and whoever hits the bound is exactly
100/// who needs [`ELEMENT_MEMORY_LIMIT_ENV`] — so the hint belongs on the message
101/// they see, not only in the guide.
102#[must_use]
103pub fn decode_failure(subject: &str, err: &buffa::DecodeError, limit: usize) -> String {
104    let base = format!("failed to decode {subject}: {err}");
105    match err {
106        buffa::DecodeError::ElementMemoryLimitExceeded => format!(
107            "{base}\n\
108             These descriptors exceed the {budget} element-memory budget in force. \
109             Raise it with the {opt}=<bytes> plugin option or the {env} environment \
110             variable; either accepts 'unlimited'. See \"Very large schemas\" in the \
111             buffa guide.",
112            budget = describe_byte_budget(limit),
113            opt = ELEMENT_MEMORY_LIMIT_OPT,
114            env = ELEMENT_MEMORY_LIMIT_ENV,
115        ),
116        _ => base,
117    }
118}
119
120/// Render a byte budget for a human: MiB once that is a whole number, bytes
121/// below that. A sub-MiB limit is legitimate (the environment variable takes a
122/// raw byte count), and truncating it to "0 MiB" would make the hint absurd.
123fn describe_byte_budget(bytes: usize) -> String {
124    const MIB: usize = 1024 * 1024;
125    if bytes >= MIB && bytes % MIB == 0 {
126        format!("{} MiB", bytes / MIB)
127    } else {
128        format!("{bytes}-byte")
129    }
130}
131
132/// Plugin option, settable in the `protoc`/`buf` parameter string, that sets
133/// the element-memory bound used to decode the request carrying it.
134pub const ELEMENT_MEMORY_LIMIT_OPT: &str = "element_memory_limit";
135
136/// Read `CodeGeneratorRequest.parameter` (field 2) straight off the wire,
137/// without decoding the request.
138///
139/// An option that governs how the request itself is decoded cannot be read
140/// from the decoded request. Scanning for the one field is what makes such
141/// options possible: it skips every other field rather than materialising it,
142/// so the cost is a wire walk, not a decode — microseconds against the tens of
143/// milliseconds a full decode of a large request takes.
144///
145/// Returns `None` when the request carries no parameter.
146///
147/// # Errors
148///
149/// Returns a [`DecodeError`](buffa::DecodeError) if the bytes are not
150/// well-formed protobuf, or if the parameter is not valid UTF-8.
151pub fn peek_request_parameter(mut buf: &[u8]) -> Result<Option<&str>, buffa::DecodeError> {
152    use buffa::encoding::{decode_varint, skip_field, Tag, WireType};
153
154    /// `CodeGeneratorRequest.parameter`.
155    const PARAMETER_FIELD: u32 = 2;
156
157    let mut found = None;
158    while !buf.is_empty() {
159        let tag = Tag::decode(&mut buf)?;
160        if tag.field_number() == PARAMETER_FIELD && tag.wire_type() == WireType::LengthDelimited {
161            let len = usize::try_from(decode_varint(&mut buf)?)
162                .map_err(|_| buffa::DecodeError::MessageTooLarge)?;
163            if len > buf.len() {
164                return Err(buffa::DecodeError::UnexpectedEof);
165            }
166            let (value, rest) = buf.split_at(len);
167            // Last wins, matching how protobuf merges a repeated scalar field.
168            found = Some(core::str::from_utf8(value).map_err(|_| buffa::DecodeError::InvalidUtf8)?);
169            buf = rest;
170            continue;
171        }
172        skip_field(tag, &mut buf)?;
173    }
174    Ok(found)
175}
176
177/// Find `element_memory_limit=<value>` in a `protoc` plugin parameter string.
178///
179/// Returns `None` when the option is absent, leaving
180/// [`ELEMENT_MEMORY_LIMIT_ENV`] and then [`TOOLING_ELEMENT_MEMORY_LIMIT`] to
181/// supply the value.
182///
183/// # Errors
184///
185/// Returns a message if the option is present with an unparseable value.
186pub fn element_memory_limit_opt(parameter: &str) -> Result<Option<usize>, String> {
187    let mut found = None;
188    for entry in parameter.split(',').map(str::trim) {
189        if let Some((key, value)) = entry.split_once('=') {
190            if key.trim() == ELEMENT_MEMORY_LIMIT_OPT {
191                found = Some(parse_element_memory_limit(Some(value))?);
192            }
193        }
194    }
195    Ok(found)
196}
197
198/// Decode a `CodeGeneratorRequest` from a `protoc` plugin's stdin.
199///
200/// The element-memory bound is taken from the `element_memory_limit` plugin
201/// option if the request carries one, else [`ELEMENT_MEMORY_LIMIT_ENV`], else
202/// [`TOOLING_ELEMENT_MEMORY_LIMIT`]. The option is read by scanning for it
203/// ([`peek_request_parameter`]) rather than from the decoded request, since it
204/// governs that decode.
205///
206/// Both plugins compose exactly this, so it lives here: a hint improved in one
207/// binary should never disagree with the other.
208///
209/// # Errors
210///
211/// Returns a message describing the failure, naming the ways to raise the
212/// bound when that is what rejected the request.
213pub fn decode_request(input: &[u8]) -> Result<generated::compiler::CodeGeneratorRequest, String> {
214    let parameter = peek_request_parameter(input)
215        .map_err(|e| format!("failed to read the plugin parameter: {e}"))?
216        .unwrap_or_default();
217    let options = match element_memory_limit_opt(parameter)? {
218        Some(limit) => buffa::DecodeOptions::new().with_element_memory_limit(limit),
219        None => tooling_decode_options()?,
220    };
221    options
222        .decode_from_slice::<generated::compiler::CodeGeneratorRequest>(input)
223        .map_err(|e| decode_failure("CodeGeneratorRequest", &e, options.element_memory_limit()))
224}
225
226/// Resolve [`ELEMENT_MEMORY_LIMIT_ENV`]'s value to a byte count.
227///
228/// `None` and an all-whitespace value both mean "unset", yielding
229/// [`TOOLING_ELEMENT_MEMORY_LIMIT`].
230///
231/// # Errors
232///
233/// Returns a message naming the variable if the value is neither a byte count
234/// nor `unlimited` / `max`.
235pub(crate) fn parse_element_memory_limit(raw: Option<&str>) -> Result<usize, String> {
236    let Some(raw) = raw else {
237        return Ok(TOOLING_ELEMENT_MEMORY_LIMIT);
238    };
239    match raw.trim() {
240        "" => Ok(TOOLING_ELEMENT_MEMORY_LIMIT),
241        "unlimited" | "max" => Ok(usize::MAX),
242        n => n.parse::<usize>().map_err(|_| {
243            format!("{ELEMENT_MEMORY_LIMIT_ENV}: expected a byte count or 'unlimited', got '{raw}'")
244        }),
245    }
246}
247
248/// Lints suppressed on generated code at module boundaries.
249///
250/// Consumed by [`generate_module_tree`], the per-package `.mod.rs`
251/// stitcher, and `buffa-build`'s `_include.rs` writer. One list keeps
252/// them in sync.
253pub const ALLOW_LINTS: &[&str] = &[
254    "non_camel_case_types",
255    "dead_code",
256    "unused_imports",
257    // Cross-proto refs within the same package are emitted through the
258    // canonical `super::super::__buffa::view::…` path even though the
259    // target lives in the same generated module — using the bare name
260    // would resolve, but the canonical path is stable when a sibling
261    // proto defines a same-named natural-path re-export.
262    "unused_qualifications",
263    "clippy::derivable_impls",
264    "clippy::match_single_binding",
265    "clippy::uninlined_format_args",
266    "clippy::doc_lazy_continuation",
267    // A user `message View { message Inner }` produces
268    // `__buffa::view::view::InnerView`; harmless but trips this lint.
269    "clippy::module_inception",
270];
271
272/// Render [`ALLOW_LINTS`] as a `#[allow(…)]` attribute token stream.
273pub fn allow_lints_attr() -> TokenStream {
274    let lints: Vec<TokenStream> = ALLOW_LINTS
275        .iter()
276        .map(|l| syn::parse_str(l).expect("lint name parses as path"))
277        .collect();
278    quote! { #[allow( #(#lints),* )] }
279}
280
281/// One generated output file.
282///
283/// Each `.proto` produces up to five **content files** (`<stem>.rs`,
284/// `<stem>.__view.rs`, `<stem>.__oneof.rs`, `<stem>.__view_oneof.rs`,
285/// `<stem>.__ext.rs`) and each proto package produces one
286/// `<dotted.pkg>.mod.rs` **stitcher** that `include!`s the content files
287/// and authors the `pub mod __buffa { … }` ancillary tree.
288/// Ancillary kinds with no content for that input file (e.g. a message
289/// with no oneofs and no extensions) are omitted, and the stitcher's
290/// `include!` set is filtered to match. The `__buffa` wrapper (and each
291/// `view` / `oneof` / `ext` submodule inside it) is itself omitted when
292/// it would be empty, so packages with only owned messages emit no
293/// `__buffa` block at all.
294/// See `DESIGN.md` → "Generated code layout".
295///
296/// Consumers normally only need to wire up the
297/// [`GeneratedFileKind::PackageMod`] entries (one per package); the
298/// per-proto content kinds are reached transitively via `include!` from
299/// the stitcher. Write all files to disk; build a module tree from only
300/// the `PackageMod` ones.
301///
302/// With [`CodeGenConfig::file_per_package`] set, the per-proto content
303/// kinds are not emitted at all — the single `<dotted.pkg>.rs` (still
304/// kind `PackageMod`) inlines what the stitcher would `include!`.
305#[derive(Debug)]
306pub struct GeneratedFile {
307    /// The output file path (e.g., `"my.pkg.foo.rs"` or `"my.pkg.mod.rs"`).
308    pub name: String,
309    /// The proto package this file belongs to.
310    pub package: String,
311    /// What this file contains. Build integrations only need to wire up
312    /// [`GeneratedFileKind::PackageMod`] files; everything else is reached
313    /// via `include!` from there.
314    pub kind: GeneratedFileKind,
315    /// The generated Rust source code.
316    pub content: String,
317}
318
319/// Kind of [`GeneratedFile`].
320///
321/// [`generate`] produces up to five per-proto content kinds — one each
322/// of [`Owned`](Self::Owned), [`View`](Self::View), [`Oneof`](Self::Oneof),
323/// [`ViewOneof`](Self::ViewOneof), and [`Ext`](Self::Ext) per input
324/// `.proto` file — plus one [`PackageMod`](Self::PackageMod) stitcher per
325/// package. Kinds with no content for the input (a proto with no oneofs
326/// emits no [`Oneof`](Self::Oneof) / [`ViewOneof`](Self::ViewOneof);
327/// no extensions, no [`Ext`](Self::Ext); etc.) are omitted. Build
328/// integrations only need to wire up `PackageMod` entries; the per-proto
329/// content kinds are reached via `include!` from the stitcher and need
330/// only be written to disk alongside it. Under
331/// [`CodeGenConfig::file_per_package`] only `PackageMod` is emitted.
332///
333/// [`Companion`](Self::Companion) is the one kind *not* produced by
334/// [`generate`]: downstream code generators construct `Companion` files
335/// themselves and merge them into buffa's output via
336/// [`apply_companions`].
337///
338/// This enum is `#[non_exhaustive]` — match with a wildcard arm so new
339/// kinds can be added without a major version bump.
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341#[non_exhaustive]
342pub enum GeneratedFileKind {
343    /// Owned message structs and enums (`<stem>.rs`).
344    Owned,
345    /// View structs (`<stem>.__view.rs`).
346    View,
347    /// Lazy view structs (`<stem>.__lazy_view.rs`).
348    LazyView,
349    /// Owned oneof enums (`<stem>.__oneof.rs`).
350    Oneof,
351    /// View oneof enums (`<stem>.__view_oneof.rs`).
352    ViewOneof,
353    /// File-level proto-extension consts (`<stem>.__ext.rs`) — the
354    /// `pub const` `ExtensionDescriptor` items generated from `extend`
355    /// blocks. Not to be confused with [`Companion`](Self::Companion),
356    /// which is unrelated downstream-supplied content.
357    Ext,
358    /// Per-package stitcher (`<dotted.pkg>.mod.rs`). The only file build
359    /// systems need to wire up directly.
360    PackageMod,
361    /// Extra per-proto content from a downstream code generator (service
362    /// stubs, extra trait impls, etc.) that travels with buffa's output.
363    ///
364    /// Not produced by [`generate`]. Construct these in your own generator
365    /// and pass them to [`apply_companions`], which appends an `include!`
366    /// for each one at file scope in the matching package's
367    /// [`PackageMod`](Self::PackageMod) — after buffa's own output, at
368    /// package root alongside the owned message types (**not** under the
369    /// `__buffa::` sentinel module). Items declared `pub` in a companion
370    /// file are visible at `crate::<pkg>::*`.
371    ///
372    /// Not to be confused with [`Ext`](Self::Ext), which is the buffa-
373    /// generated file holding protobuf `extend` consts.
374    Companion,
375}
376
377/// Parse a custom owned-type path string (e.g. `"::smol_str::SmolStr"`) into a
378/// token stream, validating it as a Rust type so a malformed path surfaces as a
379/// codegen error rather than unparseable generated output.
380pub(crate) fn parse_custom_type_path(path: &str) -> Result<proc_macro2::TokenStream, CodeGenError> {
381    let ty: syn::Type =
382        syn::parse_str(path).map_err(|_| CodeGenError::InvalidTypePath(path.to_string()))?;
383    Ok(quote::quote! { #ty })
384}
385
386/// Parse a custom **map** container path, which is applied as `path<K, V>`.
387///
388/// The path must therefore be a bare type path with no `<...>` parameters of its
389/// own (and, unlike the box/repeated knobs, no `*` placeholder — a map's key and
390/// value are appended positionally). Reject anything else with a message that
391/// names the convention, rather than letting `Foo<Bar><K, V>` surface as an
392/// opaque whole-file parse error later.
393pub(crate) fn parse_custom_map_path(path: &str) -> Result<proc_macro2::TokenStream, CodeGenError> {
394    let ty: syn::Type = syn::parse_str(path).map_err(|_| {
395        CodeGenError::InvalidTypePath(format!(
396            "{path} (map custom path takes no `<K, V>` parameters and no `*` placeholder)"
397        ))
398    })?;
399    let syn::Type::Path(tp) = &ty else {
400        return Err(CodeGenError::InvalidTypePath(format!(
401            "{path} (map custom path must be a plain type path)"
402        )));
403    };
404    if tp
405        .path
406        .segments
407        .iter()
408        .any(|s| !matches!(s.arguments, syn::PathArguments::None))
409    {
410        return Err(CodeGenError::InvalidTypePath(format!(
411            "{path} (map custom path must not include `<K, V>`; the key and value are appended automatically)"
412        )));
413    }
414    Ok(quote::quote! { #ty })
415}
416
417/// Build a custom wrapper type from a `*`-templated path and a resolved inner
418/// type, validating the result as a Rust type.
419///
420/// `*` cannot be a parsed placeholder (it is not valid in Rust type position),
421/// so substitution is textual — every `*` in `template` is replaced by `inner`'s
422/// token text before the whole string is parsed. Used by the pluggable pointer
423/// knob, where the wrapped type sits inside extra generic parameters (e.g.
424/// `"smallbox::SmallBox<*, S4>"`). The template must contain at least one `*`.
425pub(crate) fn parse_wildcard_type_path(
426    template: &str,
427    inner: &proc_macro2::TokenStream,
428) -> Result<proc_macro2::TokenStream, CodeGenError> {
429    if !template.contains('*') {
430        return Err(CodeGenError::MissingWildcard(template.to_string()));
431    }
432    let substituted = template.replace('*', &inner.to_string());
433    let ty: syn::Type = syn::parse_str(&substituted)
434        .map_err(|_| CodeGenError::InvalidTypePath(format!("{template} (as {substituted})")))?;
435    Ok(quote::quote! { #ty })
436}
437
438/// Build a custom collection type from a `*`-templated path and the resolved
439/// element type, validating the result as a Rust type.
440///
441/// `*` cannot be a parsed placeholder (it is not valid in Rust type position),
442/// so substitution is textual — every `*` in `template` is replaced by the
443/// element's token text before the whole string is parsed. The template must
444/// contain at least one `*`, otherwise the element type would have nowhere to
445/// go and the field would silently drop its element type.
446pub(crate) fn parse_custom_list_path(
447    template: &str,
448    elem: &proc_macro2::TokenStream,
449) -> Result<proc_macro2::TokenStream, CodeGenError> {
450    if !template.contains('*') {
451        return Err(CodeGenError::MissingListPlaceholder(template.to_string()));
452    }
453    let substituted = template.replace('*', &elem.to_string());
454    let ty: syn::Type = syn::parse_str(&substituted)
455        .map_err(|_| CodeGenError::InvalidTypePath(template.to_string()))?;
456    Ok(quote::quote! { #ty })
457}
458
459/// The Rust type a proto `string` field maps to in generated owned structs.
460///
461/// The default is [`String`](StringRepr::String).
462/// [`Custom`](StringRepr::Custom) substitutes any type named by its
463/// fully-qualified Rust path — for example `::smol_str::SmolStr`,
464/// `::ecow::EcoString`, or `::compact_str::CompactString` for read-mostly
465/// schemas — that satisfies the `buffa::ProtoString` bound. The downstream crate
466/// must itself depend on the crate providing that type (buffa does not re-export
467/// it).
468///
469/// Select a representation through `buffa_build`'s `string_type` /
470/// `string_type_custom` builder methods. The wire format is identical regardless
471/// of representation — only the in-memory owned type changes; view types keep
472/// borrowing `&str`, and `map<_, string>` / `map<string, _>` keys and values
473/// always stay `String`.
474#[derive(Debug, Clone, PartialEq, Eq, Default)]
475#[non_exhaustive]
476pub enum StringRepr {
477    /// `::buffa::alloc::string::String` — growable and mutable (the default).
478    #[default]
479    String,
480    /// A custom type named by its fully-qualified Rust path (e.g.
481    /// `"::smol_str::SmolStr"`). Must satisfy `buffa::ProtoString` and be
482    /// provided by a crate the downstream depends on.
483    ///
484    /// # Limitations
485    ///
486    /// - A *foreign* custom type used as a `repeated` element fails to compile
487    ///   (the emitted `ReflectElement` impl violates the orphan rule). Wrap it
488    ///   in a crate-local newtype for that case; singular / optional / oneof /
489    ///   map uses work with a foreign type directly.
490    /// - A path that does not parse as a Rust type surfaces as
491    ///   [`CodeGenError::InvalidTypePath`] at generation (`.compile()`) time.
492    /// - The per-element impls are deduplicated within a single generation, but
493    ///   the *same* crate-local type used as a `repeated` element across two
494    ///   separate `compile()` invocations in one crate emits the impl twice (a
495    ///   duplicate-impl `E0119`). Generate from a single `compile()`, or use
496    ///   distinct element types.
497    Custom(String),
498}
499
500impl StringRepr {
501    /// The owned Rust type path emitted for a `string` field with this
502    /// representation.
503    ///
504    /// `ctx` and `nesting` route the default `String` through the package-root
505    /// import registry (`idiomatic_imports`); a custom path is parsed and
506    /// emitted fully qualified.
507    ///
508    /// # Errors
509    ///
510    /// Returns [`CodeGenError::InvalidTypePath`] if a custom path does not parse
511    /// as a Rust type.
512    pub(crate) fn type_path(
513        &self,
514        resolver: &imports::ImportResolver,
515        ctx: &context::CodeGenContext,
516        nesting: usize,
517    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
518        match self {
519            StringRepr::String => Ok(resolver.string_at(ctx, nesting)),
520            StringRepr::Custom(path) => parse_custom_type_path(path),
521        }
522    }
523
524    /// Whether this is the default `String` representation, which keeps the
525    /// `String`-specialized fast paths (in-place `merge_string`, `clear()`,
526    /// native `Arbitrary`) instead of the generic `ProtoString` ones.
527    pub(crate) fn is_default(&self) -> bool {
528        matches!(self, StringRepr::String)
529    }
530}
531
532/// The Rust type a proto `bytes` field maps to in generated owned structs.
533///
534/// The default is [`Vec`](BytesRepr::Vec) (`Vec<u8>`). [`Bytes`](BytesRepr::Bytes)
535/// uses `bytes::Bytes`, which decodes zero-copy from a
536/// `Bytes`-backed buffer. [`Custom`](BytesRepr::Custom) substitutes any type
537/// named by its fully-qualified Rust path that satisfies the `buffa::ProtoBytes`
538/// bound; the downstream crate must itself depend on the providing crate.
539///
540/// Select a representation through `buffa_build`'s `bytes_type` /
541/// `bytes_type_custom` builder methods (or the legacy `use_bytes_type`, which
542/// selects [`Bytes`](BytesRepr::Bytes)). The wire format is identical regardless
543/// of representation; view types keep borrowing `&[u8]`, and `map` bytes values
544/// follow the same rules as the string path.
545#[derive(Debug, Clone, PartialEq, Eq, Default)]
546#[non_exhaustive]
547pub enum BytesRepr {
548    /// `::buffa::alloc::vec::Vec<u8>` — growable and mutable (the default).
549    #[default]
550    Vec,
551    /// `::buffa::bytes::Bytes` — reference-counted, immutable, decodes zero-copy
552    /// from a `Bytes`-backed buffer.
553    Bytes,
554    /// A custom type named by its fully-qualified Rust path. Must satisfy
555    /// `buffa::ProtoBytes` and be provided by a crate the downstream depends on.
556    ///
557    /// # Limitations
558    ///
559    /// - A *foreign* custom type used as a `repeated` element fails to compile
560    ///   (the emitted `ReflectElement` / `ProtoElemJson` impls violate the
561    ///   orphan rule). Wrap it in a crate-local newtype for that case; singular
562    ///   / optional / oneof uses work with a foreign type directly.
563    /// - A `Custom` rule does **not** apply to `map<K, bytes>` values — they
564    ///   stay `Vec<u8>`. Only the built-in [`Bytes`](BytesRepr::Bytes) applies
565    ///   to map values.
566    /// - A path that does not parse as a Rust type surfaces as
567    ///   [`CodeGenError::InvalidTypePath`] at generation (`.compile()`) time.
568    /// - The per-element impls are deduplicated within a single generation, but
569    ///   the *same* crate-local type used as a `repeated` element across two
570    ///   separate `compile()` invocations in one crate emits the impl twice (a
571    ///   duplicate-impl `E0119`). Generate from a single `compile()`, or use
572    ///   distinct element types.
573    Custom(String),
574}
575
576impl BytesRepr {
577    /// The owned Rust type path emitted for a `bytes` field with this
578    /// representation.
579    ///
580    /// `ctx` and `nesting` route the default `Vec<u8>` through the package-root
581    /// import registry; `Bytes` and a custom path are emitted fully qualified.
582    ///
583    /// # Errors
584    ///
585    /// Returns [`CodeGenError::InvalidTypePath`] if a custom path does not parse
586    /// as a Rust type.
587    pub(crate) fn type_path(
588        &self,
589        resolver: &imports::ImportResolver,
590        ctx: &context::CodeGenContext,
591        nesting: usize,
592    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
593        use quote::quote;
594        match self {
595            BytesRepr::Vec => {
596                let vec = resolver.vec_at(ctx, nesting);
597                Ok(quote! { #vec<u8> })
598            }
599            BytesRepr::Bytes => Ok(quote! { ::buffa::bytes::Bytes }),
600            BytesRepr::Custom(path) => parse_custom_type_path(path),
601        }
602    }
603
604    /// Whether this is the default `Vec<u8>` representation, which keeps the
605    /// `Vec`-specialized fast paths (in-place `merge_bytes`, `clear()`, native
606    /// `Arbitrary`) instead of the generic `ProtoBytes` ones.
607    pub(crate) fn is_default(&self) -> bool {
608        matches!(self, BytesRepr::Vec)
609    }
610}
611
612/// The owned Rust collection a proto `map<K, V>` field maps to in generated
613/// owned structs.
614///
615/// The default is [`HashMap`](MapRepr::HashMap) (`std::collections::HashMap`, or
616/// `hashbrown::HashMap` under `no_std`). [`BTreeMap`](MapRepr::BTreeMap) selects
617/// the buffa-provided `alloc::collections::BTreeMap` for deterministic iteration
618/// order with no extra dependency or consumer code.
619/// [`Custom`](MapRepr::Custom) substitutes any map that satisfies the
620/// `buffa::map_codec::MapStorage` bound — for example a crate-local newtype
621/// wrapping `indexmap::IndexMap`.
622///
623/// Unlike the `repeated` knob (which wraps the element type and needs a `*`
624/// placeholder template), a map type is always `path<K, V>` with both
625/// parameters positional and buffa-resolved, so a custom path is a plain type
626/// path (e.g. `"::my_crate::OrderedMap"`) with no placeholder.
627///
628/// Select a representation through `buffa_build`'s `map_type` /
629/// `map_type_custom` builder methods. The wire format is identical regardless of
630/// the collection; only the in-memory owned type changes.
631#[derive(Debug, Clone, PartialEq, Eq, Default)]
632#[non_exhaustive]
633pub enum MapRepr {
634    /// `::buffa::__private::HashMap<K, V>` — the default. Generated output is
635    /// byte-identical to a build without the knob.
636    #[default]
637    HashMap,
638    /// `::buffa::alloc::collections::BTreeMap<K, V>` — buffa-provided, no extra
639    /// dependency, deterministic key order (so encoded bytes are stable across
640    /// runs). The key type must be `Ord`, which every proto map key type
641    /// (integers, bool, string) satisfies.
642    BTreeMap,
643    /// A custom map named by a fully-qualified Rust type path (e.g.
644    /// `"::my_crate::OrderedMap"`). The named type must satisfy
645    /// `buffa::map_codec::MapStorage` and be a **crate-local newtype** (a foreign
646    /// map cannot implement the buffa-owned reflection / serde traits).
647    ///
648    /// # Limitations
649    ///
650    /// - The path is a plain type path applied as `path<K, V>` — it must **not**
651    ///   include the `<K, V>` parameters or a `*` placeholder. A path that does
652    ///   not parse as a Rust type surfaces as [`CodeGenError::InvalidTypePath`]
653    ///   at generation (`.compile()`) time.
654    /// - The newtype must implement `buffa::map_codec::MapStorage` plus the
655    ///   derive / `FromIterator` / `ReflectMap` / serde / `arbitrary` bounds
656    ///   listed on that trait's docs (the canonical list). JSON and `arbitrary`
657    ///   now work for every proto map key/value type regardless of the container.
658    ///   The buffa-provided [`BTreeMap`](MapRepr::BTreeMap) already satisfies every
659    ///   bound, so prefer it unless you need a specific foreign map.
660    Custom(String),
661}
662
663impl MapRepr {
664    /// The owned Rust map type emitted for a `map<K, V>` field with this
665    /// representation, given the already-resolved key and value type tokens.
666    ///
667    /// `ctx` and `nesting` route the default `HashMap` through the package-root
668    /// import registry; `BTreeMap` and a custom path are emitted fully
669    /// qualified.
670    ///
671    /// # Errors
672    ///
673    /// Returns [`CodeGenError::InvalidTypePath`] if a custom path does not parse
674    /// as a Rust type.
675    pub(crate) fn type_path(
676        &self,
677        key: &proc_macro2::TokenStream,
678        value: &proc_macro2::TokenStream,
679        resolver: &imports::ImportResolver,
680        ctx: &context::CodeGenContext,
681        nesting: usize,
682    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
683        use quote::quote;
684        match self {
685            MapRepr::HashMap => {
686                let hm = resolver.hashmap_at(ctx, nesting);
687                Ok(quote! { #hm<#key, #value> })
688            }
689            MapRepr::BTreeMap => Ok(quote! { ::buffa::alloc::collections::BTreeMap<#key, #value> }),
690            MapRepr::Custom(path) => {
691                let ty = parse_custom_map_path(path)?;
692                Ok(quote! { #ty<#key, #value> })
693            }
694        }
695    }
696
697    /// Whether this is the default `HashMap` representation, whose generated
698    /// output is byte-identical to a build without the knob.
699    pub(crate) fn is_default(&self) -> bool {
700        matches!(self, MapRepr::HashMap)
701    }
702}
703
704/// The owned smart pointer a singular message field's `buffa::MessageField`
705/// wraps in generated owned structs.
706///
707/// The default is [`Box`](PointerRepr::Box). [`Custom`](PointerRepr::Custom)
708/// substitutes any pointer that satisfies the `buffa::ProtoBox<T>` bound — for
709/// example a `smallbox`-style pointer that stores small messages inline.
710/// Because the pointer *wraps* the message type, its path is a **template**
711/// containing a `*` placeholder for the message type (e.g.
712/// `"::smallbox::SmallBox<*, ::smallbox::space::S4>"` or
713/// `"::my_crate::SmallBox<*>"`).
714///
715/// Because `buffa::ProtoBox` is buffa-owned, a *foreign* pointer cannot
716/// implement it directly (orphan rule) — the template must name a crate-local
717/// newtype, mirroring the `ProtoString` newtype expectation.
718///
719/// Select a representation through `buffa_build`'s `box_type_custom` builder
720/// method. The wire format is identical regardless of the pointer; view types
721/// are unaffected. Applies to singular message fields and **boxed** oneof
722/// message/group variants (a variant opted into inline storage via
723/// `unboxed_oneof_fields` takes precedence and gets no pointer). Repeated
724/// message fields use a collection, not a pointer.
725#[derive(Debug, Clone, PartialEq, Eq, Default)]
726#[non_exhaustive]
727pub enum PointerRepr {
728    /// `::buffa::alloc::boxed::Box<T>` (inside `MessageField<T>`). The opt-out
729    /// from the `Inline` default for large or rarely-set submessages, via
730    /// `box_type_in(PointerRepr::Box, paths)` (or `box_type(PointerRepr::Box)`
731    /// to restore the pre-0.9 global default).
732    Box,
733    /// `::buffa::Inline<T>` — store the message directly in the parent struct,
734    /// no heap allocation. `MessageField<T, Inline<T>>` is laid out as
735    /// `Option<T>`. The default.
736    ///
737    /// Recursion-aware: a singular field that would form an infinite-size cycle
738    /// (directly, mutually, or via an
739    /// [`unbox_oneof`](CodeGenConfig::unboxed_oneof_fields)-inlined oneof
740    /// variant) is silently kept on `Box`, so the default is always sized. An
741    /// *exact-path* `Inline` rule that names a recursive field is rejected at
742    /// codegen time.
743    #[default]
744    Inline,
745    /// A custom pointer named by a Rust type-path **template** with a `*`
746    /// placeholder for the message type. Must satisfy `buffa::ProtoBox<T>` and
747    /// be a crate-local newtype.
748    ///
749    /// # Limitations
750    ///
751    /// - The template must contain at least one `*`; a template that omits it
752    ///   surfaces as [`CodeGenError::MissingWildcard`], and one whose
753    ///   substitution does not parse as [`CodeGenError::InvalidTypePath`], at
754    ///   generation (`.compile()`) time.
755    /// - `Rc` / `Arc` and other shared/COW pointers are unusable: the decoder
756    ///   merges in place (needs `DerefMut`), so only an exclusively-owned
757    ///   pointer (heap `Box`, inline `SmallBox`) can implement `ProtoBox`.
758    /// - An inline pointer inflates the parent struct per field, so select it
759    ///   per field/prefix, never as a blanket default.
760    /// - On a **boxed oneof variant** under the `arbitrary` feature, the custom
761    ///   pointer must implement `arbitrary::Arbitrary` (the oneof enum derives it
762    ///   and stores the pointer directly in the variant). The singular-field path
763    ///   needs no such impl — `MessageField` constructs the pointer itself.
764    Custom(String),
765}
766
767impl PointerRepr {
768    /// The owned `MessageField<...>` type emitted for a singular message field
769    /// with this representation, given the resolved inner message type tokens
770    /// and the `MessageField` path from the resolver.
771    ///
772    /// # Errors
773    ///
774    /// Returns [`CodeGenError::MissingWildcard`] if a custom template omits `*`,
775    /// or [`CodeGenError::InvalidTypePath`] if it does not parse once the message
776    /// type is substituted.
777    pub(crate) fn type_path(
778        &self,
779        message_field: &proc_macro2::TokenStream,
780        inner: &proc_macro2::TokenStream,
781    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
782        use quote::quote;
783        match self {
784            PointerRepr::Box => Ok(quote! { #message_field<#inner> }),
785            PointerRepr::Inline => Ok(quote! { #message_field<#inner, ::buffa::Inline<#inner>> }),
786            PointerRepr::Custom(template) => {
787                let ptr = parse_wildcard_type_path(template, inner)?;
788                Ok(quote! { #message_field<#inner, #ptr> })
789            }
790        }
791    }
792
793    /// The fully-qualified `::buffa::MessageField::<...>` path for a
794    /// `::some(value)` construction of a singular message field with this
795    /// representation: `<inner>` for `Box` (the pointer param defaults), or
796    /// `<inner, ptr>` for a custom pointer. The view→owned conversion uses this
797    /// so the constructed `MessageField` matches the field's declared type.
798    ///
799    /// # Errors
800    ///
801    /// As [`type_path`](Self::type_path) for a custom template.
802    pub(crate) fn some_path(
803        &self,
804        inner: &proc_macro2::TokenStream,
805    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
806        use quote::quote;
807        match self {
808            PointerRepr::Box => Ok(quote! { ::buffa::MessageField::<#inner> }),
809            PointerRepr::Inline => {
810                Ok(quote! { ::buffa::MessageField::<#inner, ::buffa::Inline<#inner>> })
811            }
812            PointerRepr::Custom(template) => {
813                let ptr = parse_wildcard_type_path(template, inner)?;
814                Ok(quote! { ::buffa::MessageField::<#inner, #ptr> })
815            }
816        }
817    }
818
819    /// The bare pointer type wrapping `inner` for a **boxed oneof variant**
820    /// (`Box<inner>` by default, or the custom pointer). Unlike
821    /// [`type_path`](Self::type_path) this is the pointer alone, not wrapped in
822    /// `MessageField`, because a oneof enum stores the pointer directly in the
823    /// variant.
824    ///
825    /// # Errors
826    ///
827    /// As [`type_path`](Self::type_path) for a custom template.
828    pub(crate) fn pointer_type(
829        &self,
830        inner: &proc_macro2::TokenStream,
831    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
832        use quote::quote;
833        match self {
834            PointerRepr::Box => Ok(quote! { ::buffa::alloc::boxed::Box<#inner> }),
835            PointerRepr::Inline => Ok(quote! { ::buffa::Inline<#inner> }),
836            PointerRepr::Custom(template) => parse_wildcard_type_path(template, inner),
837        }
838    }
839
840    /// Construct the pointer from a value expression for a boxed oneof variant:
841    /// `Box::new(value)` (byte-identical default) or the fully-qualified
842    /// `<Ptr as ProtoBox<inner>>::new(value)` for a custom pointer (so an
843    /// inherent `new` on the pointer can't shadow the trait method).
844    ///
845    /// # Errors
846    ///
847    /// As [`type_path`](Self::type_path) for a custom template.
848    pub(crate) fn pointer_new(
849        &self,
850        inner: &proc_macro2::TokenStream,
851        value: &proc_macro2::TokenStream,
852    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
853        use quote::quote;
854        match self {
855            PointerRepr::Box => Ok(quote! { ::buffa::alloc::boxed::Box::new(#value) }),
856            PointerRepr::Inline => Ok(quote! { ::buffa::Inline(#value) }),
857            PointerRepr::Custom(template) => {
858                let ptr = parse_wildcard_type_path(template, inner)?;
859                Ok(quote! { <#ptr as ::buffa::ProtoBox<#inner>>::new(#value) })
860            }
861        }
862    }
863}
864
865/// The owned Rust collection a proto `repeated` field maps to in generated
866/// owned structs.
867///
868/// The default is [`Vec`](RepeatedRepr::Vec) (`Vec<T>`).
869/// [`Custom`](RepeatedRepr::Custom) substitutes any collection that satisfies
870/// the `buffa::ProtoList<T>` bound — for example a crate-local newtype wrapping
871/// a `SmallVec`-backed inline collection. Unlike the scalar `string`/`bytes`
872/// knobs the custom collection *wraps* the element type, so its path is a
873/// **template** containing a `*` placeholder where the element type is
874/// substituted (e.g. `"::my_crate::SmallList<*>"`).
875///
876/// Because `buffa::ProtoList` is buffa-owned, a *foreign* collection cannot
877/// implement it directly (orphan rule) — the template must always name a
878/// crate-local newtype, mirroring the `ProtoString` newtype expectation.
879///
880/// Select a representation through `buffa_build`'s `repeated_type_custom`
881/// builder method. The wire format is identical regardless of the collection;
882/// view types keep borrowing `&[T]`.
883#[derive(Debug, Clone, PartialEq, Eq, Default)]
884#[non_exhaustive]
885pub enum RepeatedRepr {
886    /// `::buffa::alloc::vec::Vec<T>` — the default. Keeps the `Vec`-specialized
887    /// fast paths (in-place `push`/`reserve`/`clear`, native `Arbitrary`)
888    /// instead of the generic `ProtoList` ones, so generated output for the
889    /// default is byte-identical to a build without the knob.
890    #[default]
891    Vec,
892    /// A custom collection named by a Rust type-path **template** with a `*`
893    /// placeholder for the element type (e.g. `"::my_crate::SmallList<*>"`). The
894    /// named type must satisfy `buffa::ProtoList<T>` and be a **crate-local
895    /// newtype** (a foreign collection cannot implement the buffa-owned
896    /// `ProtoList`).
897    ///
898    /// # Limitations
899    ///
900    /// - The template must contain at least one `*`; the element type is
901    ///   substituted for every `*` before the result is parsed as a Rust type.
902    ///   A template that omits `*` surfaces as
903    ///   [`CodeGenError::MissingListPlaceholder`], and one whose substitution
904    ///   does not parse as [`CodeGenError::InvalidTypePath`], at generation
905    ///   (`.compile()`) time.
906    /// - A custom collection always needs a crate-local newtype — this is not
907    ///   limited to the reflection path. The generated decode and clear code
908    ///   require `Field: ProtoList`, so even a binary-only build cannot use a
909    ///   foreign collection directly.
910    /// - Under reflection / vtable the newtype must implement
911    ///   `buffa_descriptor`'s `ReflectList` (a `Vec`-backed newtype can delegate
912    ///   to the inner `Vec<T>: ReflectList`). Under JSON it must implement
913    ///   `serde::Serialize` / `Deserialize`; under the `arbitrary` feature,
914    ///   `arbitrary::Arbitrary` (derivable on a newtype).
915    /// - A `repeated <self-type>` field becomes `Collection<Self>`, so the
916    ///   collection must be heap-backed; an inline collection (`SmallVec<[Self;
917    ///   N]>`) would be infinitely sized and fail to compile.
918    Custom(String),
919}
920
921impl RepeatedRepr {
922    /// The owned Rust collection type emitted for a `repeated` field with this
923    /// representation, given the already-resolved element type tokens.
924    ///
925    /// `ctx` and `nesting` route the default `Vec` through the package-root
926    /// import registry; a custom template has its `*` placeholders replaced by
927    /// `elem` and the result is parsed and emitted fully qualified.
928    ///
929    /// # Errors
930    ///
931    /// Returns [`CodeGenError::MissingListPlaceholder`] if a custom template
932    /// omits `*`, or [`CodeGenError::InvalidTypePath`] if it does not parse as a
933    /// Rust type once the element is substituted.
934    pub(crate) fn type_path(
935        &self,
936        elem: &proc_macro2::TokenStream,
937        resolver: &imports::ImportResolver,
938        ctx: &context::CodeGenContext,
939        nesting: usize,
940    ) -> Result<proc_macro2::TokenStream, CodeGenError> {
941        use quote::quote;
942        match self {
943            RepeatedRepr::Vec => {
944                let vec = resolver.vec_at(ctx, nesting);
945                Ok(quote! { #vec<#elem> })
946            }
947            RepeatedRepr::Custom(template) => parse_custom_list_path(template, elem),
948        }
949    }
950
951    /// Whether this is the default `Vec` representation, which keeps the
952    /// `Vec`-specialized fast paths instead of the generic `ProtoList` ones.
953    pub(crate) fn is_default(&self) -> bool {
954        matches!(self, RepeatedRepr::Vec)
955    }
956}
957
958/// How much reflection support generated types get.
959///
960/// Selected through `buffa_build`'s `reflect_mode` builder method (or the
961/// `protoc-gen-buffa` `reflect_mode=` option). All modes need the consuming
962/// crate to depend on `buffa-descriptor` with its `reflect` feature and on
963/// `std`; the call site is `foo.reflect().get(fd)` regardless of mode.
964#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
965#[non_exhaustive]
966pub enum ReflectMode {
967    /// No reflection impls.
968    #[default]
969    Off,
970    /// `Reflectable::reflect()` round-trips the message through a
971    /// `DynamicMessage` (encode → decode → boxed handle). Smaller generated
972    /// code; pays an allocation and a re-encode per `reflect()` call.
973    Bridge,
974    /// `impl ReflectMessage` directly on the owned and view types, and
975    /// `Reflectable::reflect()` borrows `self` with no round-trip. Larger
976    /// generated code; near-free reflective access. Does not require view
977    /// generation — with views off, only the owned impls are emitted.
978    VTable,
979}
980
981impl ReflectMode {
982    /// Apply this mode to a [`CodeGenConfig`] (sets `generate_reflection` /
983    /// `generate_reflection_vtable`). Used by the `buffa-build` and
984    /// `protoc-gen-buffa` front-ends.
985    pub fn apply(self, config: &mut CodeGenConfig) {
986        let (reflection, vtable) = match self {
987            ReflectMode::Off => (false, false),
988            ReflectMode::Bridge => (true, false),
989            ReflectMode::VTable => (true, true),
990        };
991        config.generate_reflection = reflection;
992        config.generate_reflection_vtable = vtable;
993    }
994}
995
996/// A path-scoped protobuf editions feature override, applied by mutating the
997/// parsed descriptors before generation (see
998/// [`feature_overrides`](CodeGenConfig::feature_overrides)).
999///
1000/// Editions unification models proto2 and proto3 as editions with fixed
1001/// feature defaults, so an override's semantics are "what this proto would
1002/// say had it been migrated to editions and this feature set at this path".
1003/// Each variant is admitted only once buffa's codegen, runtime, and
1004/// validation handle the descriptor states it can create — the enum is the
1005/// allowlist. Overrides never change the wire format.
1006#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1007#[non_exhaustive]
1008pub enum FeatureOverride {
1009    /// Override `features.enum_type` for matching enums or enum fields.
1010    ///
1011    /// An enum-type path mutates the enum's own descriptor (a spec-valid
1012    /// editions construct that also flows into the embedded reflection
1013    /// pool); a field path injects a field-level override that buffa
1014    /// resolves onto the field, in both generated code and the
1015    /// descriptor-driven codecs. `enum_type` is not a legal field target in
1016    /// the spec, so other runtimes reading the exported descriptors ignore a
1017    /// field path.
1018    EnumType(EnumTypeOverride),
1019}
1020
1021impl FeatureOverride {
1022    /// The editions feature name this override sets, as spelled in
1023    /// `google.protobuf.FeatureSet` (e.g. for diagnostics).
1024    #[must_use]
1025    pub fn feature_name(&self) -> &'static str {
1026        match self {
1027            Self::EnumType(_) => "enum_type",
1028        }
1029    }
1030
1031    /// The feature value this override sets, as spelled in the descriptor
1032    /// enum (e.g. for diagnostics).
1033    #[must_use]
1034    pub fn value_name(&self) -> &'static str {
1035        match self {
1036            Self::EnumType(EnumTypeOverride::Open) => "OPEN",
1037        }
1038    }
1039}
1040
1041/// Supported values for [`FeatureOverride::EnumType`].
1042///
1043/// Only `OPEN` is currently supported — closing an open enum would
1044/// reintroduce closed-enum unknown-value routing on fields that never had
1045/// it, a combination buffa's codegen does not yet validate or test.
1046#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1047#[non_exhaustive]
1048pub enum EnumTypeOverride {
1049    /// `features.enum_type = OPEN`: matching closed enum fields generate as
1050    /// `EnumValue<E>`, making unknown wire values directly visible as
1051    /// `EnumValue::Unknown(n)`.
1052    Open,
1053}
1054
1055/// Configuration for code generation.
1056#[derive(Debug, Clone)]
1057#[non_exhaustive]
1058pub struct CodeGenConfig {
1059    /// Whether to generate borrowed view types (`MyMessageView<'a>`) in
1060    /// addition to owned types.
1061    pub generate_views: bool,
1062    /// Whether to additionally generate the lazy view family
1063    /// (`MyMessageLazyView<'a>`) alongside the eager views (default: false).
1064    ///
1065    /// Lazy views implement `buffa::LazyMessageView`: `decode_lazy` performs
1066    /// a single non-recursive scan, recording singular/repeated message
1067    /// fields as undecoded byte ranges (`LazyMessageFieldView` /
1068    /// `LazyRepeatedView`) that decode on access — reading a few fields of
1069    /// many sub-messages no longer allocates or recurses into untouched
1070    /// sub-trees. The eager `MyMessageView` family is unchanged (output is
1071    /// byte-identical with or without this flag), so eager and lazy views
1072    /// coexist and generic `MessageView` consumers never silently inherit
1073    /// deferred validation.
1074    ///
1075    /// Semantics of the lazy family:
1076    ///
1077    /// - **Eager carve-outs**: groups / editions `DELIMITED` fields (no
1078    ///   length prefix to defer), oneof message variants, and map message
1079    ///   values use the eager view types.
1080    /// - **Merge preserved**: a singular message field split across wire
1081    ///   occurrences is recorded as fragments and merged on access.
1082    /// - **Budgets flow**: the recursion depth and unknown-field allowance
1083    ///   remaining at each deferred field are recorded and replayed per
1084    ///   access (a per-subtree approximation of the shared allowance).
1085    /// - **Deferred validation**: malformed deferred bytes error on access,
1086    ///   from the fallible `to_owned_message`, and as a serde error from the
1087    ///   view `Serialize` impl. `ViewEncode` replays recorded fragments
1088    ///   **without validating them**.
1089    /// - No `ReflectMessage`, `OwnedView`, or text-format surface — use the
1090    ///   eager family for those.
1091    ///
1092    /// Requires [`generate_views`](Self::generate_views) (the lazy family
1093    /// reuses the eager view-oneof enums and eager sub-view types); with
1094    /// views disabled the flag is ignored with a warning.
1095    pub lazy_views: bool,
1096    /// Whether to preserve unknown fields (default: true).
1097    pub preserve_unknown_fields: bool,
1098    /// Whether to derive `serde::Serialize` / `serde::Deserialize` on
1099    /// generated message structs and enum types, and emit `#[serde(with = "...")]`
1100    /// attributes for proto3 JSON's special scalar encodings (int64 as quoted
1101    /// string, bytes as base64, etc.).
1102    ///
1103    /// When this is `true`, the downstream crate must depend on `serde` and
1104    /// must enable the `buffa/json` feature for the runtime helpers.
1105    ///
1106    /// Oneof fields use `#[serde(flatten)]` with custom `Serialize` /
1107    /// `Deserialize` impls so that each variant appears as a top-level
1108    /// JSON field (proto3 JSON inline oneof encoding).
1109    pub generate_json: bool,
1110    /// Whether to emit `#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]`
1111    /// on generated message structs and enum types.
1112    ///
1113    /// When this is `true`, the downstream crate must add `arbitrary` as an
1114    /// optional dependency and enable the `buffa/arbitrary` feature. The
1115    /// downstream crate's Cargo feature that gates `arbitrary` must be named
1116    /// exactly `"arbitrary"` — the generated `cfg_attr` uses that literal
1117    /// string and cannot be customized. This applies to both the struct-level
1118    /// `derive(Arbitrary)` and the per-field `#[arbitrary(with = ...)]`
1119    /// attributes emitted for `bytes_fields`-typed fields.
1120    ///
1121    /// For `bytes_fields`-typed fields, codegen emits `#[arbitrary(with = ...)]`
1122    /// using helpers in `::buffa::__private` since `bytes::Bytes` has no
1123    /// `Arbitrary` impl. Singular, optional, and repeated bytes fields are all
1124    /// covered. Map values are always `Vec<u8>` regardless of `bytes_fields`
1125    /// and require no special handling.
1126    pub generate_arbitrary: bool,
1127    /// External type path mappings.
1128    ///
1129    /// Each entry maps either a fully-qualified protobuf package prefix
1130    /// (e.g., `".my.common"`) to a Rust module path (e.g.,
1131    /// `"::common_protos"`), or a single type FQN (e.g.,
1132    /// `".my.common.Shared"`) to a full Rust type path (e.g.,
1133    /// `"::shared_types::Shared"`). Matched types reference the extern Rust
1134    /// path instead of being generated, allowing shared proto packages to be
1135    /// compiled once in a dedicated crate and referenced from others. An
1136    /// exact type-FQN entry wins over a covering package prefix; otherwise
1137    /// the longest matching prefix wins.
1138    ///
1139    /// Well-known types (`google.protobuf.*`) are automatically mapped to
1140    /// `::buffa_types::google::protobuf::*` without needing an explicit
1141    /// entry here. To override with a custom implementation, add an
1142    /// `extern_path` for `.google.protobuf` pointing to your crate.
1143    pub extern_paths: Vec<(String, String)>,
1144    /// Ordered (proto-path-prefix, [`BytesRepr`]) rules selecting the Rust type
1145    /// for `bytes` fields. Later rules win, so a broad rule (e.g. `"."` →
1146    /// `Bytes`) can be refined by a more specific one. Fields matching no rule
1147    /// use `Vec<u8>`. The path is matched with the same proto-segment-aware
1148    /// prefix logic as [`string_fields`](Self::string_fields).
1149    pub bytes_fields: Vec<(String, BytesRepr)>,
1150    /// Ordered (proto-path-prefix, [`StringRepr`]) rules selecting the Rust type
1151    /// for `string` fields. Later rules win, so a broad rule (e.g. `"."` →
1152    /// `SmolStr`) can be refined by a more specific one
1153    /// (`".my.pkg.Msg.field"` → `CompactString`). Fields matching no rule use
1154    /// `String`. The path is matched with the same proto-segment-aware prefix
1155    /// logic as [`bytes_fields`](Self::bytes_fields).
1156    ///
1157    /// Applies to singular, optional, and repeated `string` fields and oneof
1158    /// `string` variants. Map keys and values always stay `String`, mirroring
1159    /// the bytes path (where map values always stay `Vec<u8>`).
1160    pub string_fields: Vec<(String, StringRepr)>,
1161    /// Ordered (proto-path-prefix, [`MapRepr`]) rules selecting the owned Rust
1162    /// map collection for `map` fields. Later rules win, with the same
1163    /// proto-segment-aware prefix matching as [`bytes_fields`](Self::bytes_fields)
1164    /// (`"."` matches every field). Fields matching no rule use `HashMap<K, V>`.
1165    ///
1166    /// Independent of the element/value representation: a `map` field's key and
1167    /// value types are chosen by the usual scalar/string/bytes/message rules,
1168    /// and this knob only changes the surrounding collection.
1169    pub map_fields: Vec<(String, MapRepr)>,
1170    /// Ordered (proto-path-prefix, [`PointerRepr`]) rules selecting the owned
1171    /// smart pointer for singular message fields (the pointer inside
1172    /// `MessageField<T>`). Later rules win, same proto-segment-aware prefix
1173    /// matching as [`bytes_fields`](Self::bytes_fields). Fields matching no rule
1174    /// use `Box<T>`.
1175    ///
1176    /// Applies to singular (and proto2 optional/required) message fields only —
1177    /// not repeated message fields (a collection) or oneof message variants.
1178    pub pointer_fields: Vec<(String, PointerRepr)>,
1179    /// Ordered (proto-path-prefix, [`RepeatedRepr`]) rules selecting the owned
1180    /// Rust collection for `repeated` fields. Later rules win, with the same
1181    /// proto-segment-aware prefix matching as [`bytes_fields`](Self::bytes_fields)
1182    /// (`"."` matches every field). Fields matching no rule use `Vec<T>`.
1183    ///
1184    /// Applies only to `repeated` fields (not `map`, whose collection stays
1185    /// the configured map type). The element type is chosen by the usual
1186    /// scalar/string/bytes/message rules and substituted into the collection
1187    /// template.
1188    pub repeated_fields: Vec<(String, RepeatedRepr)>,
1189    /// Path-scoped editions feature overrides, applied by mutating the parsed
1190    /// descriptors before generation.
1191    ///
1192    /// Each entry pairs a fully-qualified proto path prefix with a
1193    /// [`FeatureOverride`]. Paths are matched with the same
1194    /// proto-segment-aware logic as [`bytes_fields`](Self::bytes_fields): a
1195    /// rule may name a type (`".my.pkg.E"`), a field (`".my.pkg.Msg.e"`), a
1196    /// package/message prefix, or `"."` for everything the override targets.
1197    /// Leading dots are optional, trailing dots are ignored, and
1198    /// blank/all-dot entries match nothing. Map enum values match the outer
1199    /// map field path; oneof enum variants match the direct field path.
1200    ///
1201    /// The mutated descriptors are what codegen — and, under reflection, the
1202    /// embedded descriptor pool — see, so runtime reflection and
1203    /// descriptor-driven dynamic JSON stay consistent with the generated
1204    /// types for both enum-scoped and field-scoped rules; see each variant's
1205    /// docs for its semantics. A rule that matches nothing is reported as
1206    /// [`CodeGenWarning::FeatureOverrideMatchedNothing`] through
1207    /// [`generate_with_diagnostics`] (the plain [`generate`] entry point
1208    /// discards warnings). Overrides never change the wire format. The
1209    /// default is empty, so generated output and semantics are unchanged
1210    /// unless configured.
1211    pub feature_overrides: Vec<(String, FeatureOverride)>,
1212    /// Fully-qualified proto paths whose message-typed oneof variants should
1213    /// **not** be wrapped in `Box<T>`. By default every message/group oneof
1214    /// variant is boxed (so recursive types compile); entries here opt matching
1215    /// variants out, storing the message inline in the enum.
1216    ///
1217    /// Each entry is a proto path prefix matched with the same
1218    /// proto-segment-aware logic as [`bytes_fields`](Self::bytes_fields)
1219    /// (`"."` matches every variant). Recursive variants cannot be stored
1220    /// inline (the type would be unsized): an entry naming one *exactly* is
1221    /// rejected at codegen time, while a broader prefix entry silently keeps
1222    /// recursive variants boxed and inlines the rest.
1223    pub unboxed_oneof_fields: Vec<String>,
1224    /// Honor `features.utf8_validation = NONE` by emitting `Vec<u8>` / `&[u8]`
1225    /// for such string fields instead of `String` / `&str`.
1226    ///
1227    /// When `false` (the default), buffa emits `String` for all string fields
1228    /// and **validates UTF-8 on decode** — stricter than proto2 requires, but
1229    /// ergonomic and safe.
1230    ///
1231    /// When `true`, string fields with `utf8_validation = NONE` (all proto2
1232    /// strings by default, and editions fields that opt into `NONE`) become
1233    /// `Vec<u8>` / `&[u8]`. Decode skips validation; the caller decides at the
1234    /// call site whether to `std::str::from_utf8` (checked) or
1235    /// `from_utf8_unchecked` (trusted-input fast path). This is the only
1236    /// sound Rust mapping when strings may actually contain non-UTF-8 bytes.
1237    ///
1238    /// **This is a breaking change for proto2** — enable only for new code or
1239    /// when profiling identifies UTF-8 validation as a bottleneck.
1240    pub strict_utf8_mapping: bool,
1241    /// Permit `option message_set_wire_format = true` on input messages.
1242    ///
1243    /// MessageSet is a legacy Google-internal wire format that wraps each
1244    /// extension in a group structure instead of using regular field tags.
1245    /// When `false` (the default), encountering such a message is a codegen
1246    /// error — the flag exists to make MessageSet use explicit, since the
1247    /// format is obsolete outside of interop with very old Google protos.
1248    pub allow_message_set: bool,
1249    /// Whether to emit `impl buffa::text::TextFormat` on generated message
1250    /// structs for textproto (human-readable text format) encoding/decoding.
1251    ///
1252    /// When this is `true`, the downstream crate must enable the `buffa/text`
1253    /// feature for the runtime encoder/decoder.
1254    pub generate_text: bool,
1255    /// Whether the per-package `.mod.rs` stitcher emits
1256    /// `__buffa::register_types(&mut TypeRegistry)`.
1257    ///
1258    /// Default `true`. The fn aggregates `Any` type entries and extension
1259    /// entries for every message in the package. Set to `false` for
1260    /// crates that don't use extensions/`Any`, or that hand-roll
1261    /// registration (e.g. `buffa-types`' `register_wkt_types`, which
1262    /// knows the JSON-Any `is_wkt` special-casing the generic fn does
1263    /// not). The per-message `__*_JSON_ANY` / `__*_TEXT_ANY` consts are
1264    /// still emitted; only the aggregating fn is suppressed.
1265    pub emit_register_fn: bool,
1266    /// Emit one `<dotted.package>.rs` per proto package instead of the
1267    /// per-proto-file content set plus `<pkg>.mod.rs` stitcher.
1268    ///
1269    /// The single file inlines what the stitcher would otherwise `include!`,
1270    /// producing the same `__buffa::{view,oneof,ext,...}` module structure.
1271    /// Intended for Buf Schema Registry generated SDKs, whose `lib.rs`
1272    /// synthesis builds the module tree from `<dotted.package>.rs` filenames.
1273    ///
1274    /// Under `strategy: directory` this only sees one directory's files per
1275    /// invocation, so the input module must be `PACKAGE_DIRECTORY_MATCH`-clean
1276    /// (one package per directory) for the output to be complete. BSR-hosted
1277    /// modules satisfy this by lint default. If a package spans multiple
1278    /// directories, separate invocations each emit their own `<pkg>.rs` and
1279    /// the last write wins — silent partial output, not a codegen error.
1280    pub file_per_package: bool,
1281    /// Custom attributes to inject on generated types (messages, enums, and
1282    /// oneof enums — the latter matched on the oneof's own path,
1283    /// `.my.pkg.MyMessage.my_oneof`).
1284    ///
1285    /// Each entry is `(proto_path, attribute)`. The `proto_path` is matched
1286    /// as a prefix against the fully-qualified proto name: `"."` applies to
1287    /// all types, `".my.pkg"` to types in that package, `".my.pkg.MyMessage"`
1288    /// to a specific type. The `attribute` is a raw Rust attribute string
1289    /// (e.g., `"#[derive(serde::Serialize)]"`).
1290    pub type_attributes: Vec<(String, String)>,
1291    /// Custom attributes to inject on generated struct fields.
1292    ///
1293    /// Each entry is `(proto_path, attribute)`. The `proto_path` is matched
1294    /// as a prefix against the fully-qualified field path (e.g.,
1295    /// `".my.pkg.MyMessage.my_field"`). `"."` applies to all fields.
1296    pub field_attributes: Vec<(String, String)>,
1297    /// Custom attributes to inject on generated message structs only (not enums).
1298    ///
1299    /// Same path-matching semantics as `type_attributes`, but only applied to
1300    /// message structs, not enum types. Useful for struct-only attributes like
1301    /// `#[serde(default)]`.
1302    pub message_attributes: Vec<(String, String)>,
1303    /// Custom attributes to inject on generated enum types only (not messages).
1304    ///
1305    /// Same path-matching semantics as `type_attributes`, but only applied to
1306    /// enum types. Useful for enum-only attributes like
1307    /// `#[derive(strum::EnumIter)]` when the user does not want to apply the
1308    /// same attribute to every message in the matched scope.
1309    pub enum_attributes: Vec<(String, String)>,
1310    /// Custom attributes to inject on generated oneof enums only (not messages,
1311    /// not regular enums).
1312    ///
1313    /// Same path-matching semantics as `type_attributes`, matched against the
1314    /// oneof's fully-qualified path (`.pkg.Message.oneof_name`). Useful when a
1315    /// oneof needs a different attribute set than the surrounding types — e.g.
1316    /// keeping `#[derive(serde::Serialize)]` on messages and oneofs while a
1317    /// separate `enum_attributes` entry puts a different serde derive on the
1318    /// regular enums.
1319    pub oneof_attributes: Vec<(String, String)>,
1320    /// Wrap generated `impl`s in `#[cfg(feature = "...")]` instead of
1321    /// emitting them unconditionally.
1322    ///
1323    /// When `true`, the impls controlled by [`generate_json`],
1324    /// [`generate_views`], and [`generate_text`] are emitted wrapped in
1325    /// `#[cfg(feature = "json" | "views" | "text")]` (or
1326    /// `#[cfg_attr(feature = ..., ...)]` for derives and field attributes)
1327    /// rather than unconditionally. The consuming crate must define matching
1328    /// Cargo features that enable the corresponding runtime support, e.g.:
1329    ///
1330    /// ```toml
1331    /// [features]
1332    /// json  = ["buffa/json", "dep:serde", "dep:serde_json"]
1333    /// views = []
1334    /// text  = ["buffa/text"]
1335    /// ```
1336    ///
1337    /// The [`generate_*`] flags still control *whether* an impl kind is
1338    /// emitted at all — this flag only controls whether it is `cfg`-gated.
1339    /// `generate_arbitrary` is always `cfg_attr`-gated on
1340    /// `feature = "arbitrary"` regardless of this flag, because `arbitrary`
1341    /// is an optional dependency by design.
1342    ///
1343    /// When [`generate_reflection`](Self::generate_reflection) is also on, the
1344    /// reflection impls are gated on `feature = "reflect"` alongside
1345    /// json/views/text. To gate *only* reflection without gating json/views/text,
1346    /// use [`gate_reflect_on_crate_feature`](Self::gate_reflect_on_crate_feature)
1347    /// instead.
1348    ///
1349    /// This is the mechanism that lets `buffa-descriptor` and `buffa-types`
1350    /// ship every impl while keeping the codegen toolchain
1351    /// (`buffa-codegen`/`buffa-build`/`protoc-gen-buffa`) lean: those crates
1352    /// depend on `buffa-descriptor` with `default-features = false` and so
1353    /// don't pull `serde`/`serde_json`/`base64`. Most consumers don't need
1354    /// this — they decide at build-script time whether to generate JSON, and
1355    /// if they say yes, they want `impl Serialize` to just exist.
1356    ///
1357    /// [`generate_json`]: Self::generate_json
1358    /// [`generate_views`]: Self::generate_views
1359    /// [`generate_text`]: Self::generate_text
1360    /// [`generate_*`]: Self::generate_json
1361    pub gate_impls_on_crate_features: bool,
1362    /// Generate `with_*` builder-style setter methods for explicit-presence fields.
1363    ///
1364    /// Each explicit-presence scalar, bytes, or enum field gets a
1365    /// `pub fn with_<name>(mut self, value: T) -> Self` method that wraps the
1366    /// value in `Some` and returns `self`, enabling chained construction:
1367    ///
1368    /// ```ignore
1369    /// let req = MyRequest::default()
1370    ///     .with_name("alice")
1371    ///     .with_timeout_ms(30_000);
1372    /// ```
1373    ///
1374    /// **Fields that receive a setter:** proto3 `optional`, proto2 `optional`,
1375    /// and editions fields with `field_presence = EXPLICIT`.
1376    ///
1377    /// **Fields that do not receive a setter:** message fields
1378    /// (`MessageField<T>`), repeated fields, map fields, oneof variant fields,
1379    /// proto2 `required` fields, and any implicit-presence field.
1380    ///
1381    /// There is no `clear_<name>` companion — to clear a field, assign `None`
1382    /// directly: `msg.name = None;`.
1383    ///
1384    /// Defaults to `true`.
1385    pub generate_with_setters: bool,
1386    /// Generate `impl Reflectable` for owned message types (bridge mode).
1387    ///
1388    /// When enabled, each generated message gets an
1389    /// `impl ::buffa_descriptor::reflect::Reflectable` whose `reflect()`
1390    /// round-trips through `DynamicMessage` (encode → decode → reflective
1391    /// handle), and the package's `__buffa::reflect` submodule embeds the
1392    /// `FileDescriptorSet` bytes plus a lazily-built `DescriptorPool`.
1393    ///
1394    /// **Runtime requirements** — the consuming crate must depend on:
1395    /// - `buffa-descriptor` with the `reflect` feature.
1396    /// - `std` (the lazy pool accessor uses `std::sync::OnceLock`).
1397    ///
1398    /// When [`gate_impls_on_crate_features`](Self::gate_impls_on_crate_features)
1399    /// is on, the impls are wrapped in `#[cfg(feature = "reflect")]` so the
1400    /// consuming crate can opt out per build.
1401    ///
1402    /// **Performance** — `reflect()` is one full encode/decode round-trip
1403    /// plus a heap allocation. The first call also pays a one-time pool
1404    /// build cost (linking the embedded `FileDescriptorSet`). For zero-copy
1405    /// reflective access over view types without the round-trip, additionally
1406    /// enable [`generate_reflection_vtable`](Self::generate_reflection_vtable).
1407    ///
1408    /// **Binary size** — each package embeds its own copy of the full
1409    /// `FileDescriptorSet` (transitive closure). For a multi-package
1410    /// codegen run this duplicates the FDS bytes per package. Acceptable
1411    /// for the bridge prototype; deduplication via a crate-root module is
1412    /// a planned follow-up.
1413    ///
1414    /// Defaults to `false`.
1415    pub generate_reflection: bool,
1416    /// Emit vtable-mode reflection: `impl ReflectMessage` / `impl
1417    /// ReflectElement` on the owned message structs and (when views are
1418    /// generated) the view types, and switch the owned
1419    /// `Reflectable::reflect()` body to borrow `self`
1420    /// (`ReflectCow::Borrowed(self)`) instead of the bridge round-trip.
1421    ///
1422    /// Reflective access then reads struct fields in place — no encode/decode
1423    /// round-trip and no per-field allocation — for both a decoded view and an
1424    /// in-memory owned message.
1425    ///
1426    /// Requires [`generate_reflection`](Self::generate_reflection) (the impls
1427    /// resolve against the same embedded `DescriptorPool`) but not
1428    /// [`generate_views`](Self::generate_views) — with views off, only the
1429    /// owned impls are emitted. Set via [`ReflectMode::VTable`]
1430    /// — front-ends expose it as `buffa_build::Config::reflect_mode` /
1431    /// `protoc-gen-buffa`'s `reflect_mode=vtable`.
1432    ///
1433    /// Defaults to `false`.
1434    pub generate_reflection_vtable: bool,
1435    /// Gate the reflection impls behind a `reflect` crate feature, *without*
1436    /// gating json/views/text (unlike
1437    /// [`gate_impls_on_crate_features`](Self::gate_impls_on_crate_features),
1438    /// which gates them all together).
1439    ///
1440    /// Used by crates that ship view/text impls unconditionally but want the
1441    /// reflection surface — which pulls a `buffa-descriptor` dependency and
1442    /// `std` — to be opt-in. `buffa-types` is the motivating case: its WKT
1443    /// views are always available, but `impl ReflectMessage` for them is gated
1444    /// behind `buffa-types`'s `reflect` feature.
1445    ///
1446    /// When [`gate_impls_on_crate_features`](Self::gate_impls_on_crate_features)
1447    /// is already on, reflection is gated regardless and this flag is ignored.
1448    ///
1449    /// A low-level knob for crates whose generated code is a public interface
1450    /// (`buffa-types`, the conformance harness). Set directly by `gen_wkt_types`
1451    /// and exposed through `buffa_build::Config::gate_reflect_on_crate_feature`
1452    /// (currently `#[doc(hidden)]`).
1453    ///
1454    /// Defaults to `false`.
1455    pub gate_reflect_on_crate_feature: bool,
1456    /// Emit idiomatic `UpperCamelCase` constant aliases alongside each enum
1457    /// variant.
1458    ///
1459    /// Protobuf style names enum values in `SHOUTY_SNAKE_CASE`, conventionally
1460    /// prefixed with the enum name (`RULE_LEVEL_HIGH`). Those names remain the
1461    /// definitive Rust variants — they are guaranteed unique and valid by
1462    /// protobuf, and existing references (including `Debug` output) are
1463    /// unchanged. When this is enabled, codegen additionally emits associated
1464    /// `const`s with the prefix stripped and the name converted to
1465    /// `UpperCamelCase` (`RULE_LEVEL_HIGH` → `High`), so downstream code can
1466    /// write `RuleLevel::High`.
1467    ///
1468    /// The conversion is lossy, so two values can collide (`FOO_BAR` and
1469    /// `FOO__BAR` both map to `FooBar`). The rule is all-or-nothing per enum:
1470    /// if any two values would collide after conversion, or a value would yield
1471    /// an invalid identifier, **no** aliases are emitted for that enum (a
1472    /// [`CodeGenWarning`] and an enum doc note explain why). This keeps every
1473    /// match either fully `SHOUTY_SNAKE_CASE` or fully idiomatic, never a forced
1474    /// mix.
1475    ///
1476    /// The aliases are associated `const`s, which work in pattern position too:
1477    /// a `match` written entirely against aliases is still exhaustiveness-checked
1478    /// (the "non-exhaustive" error names the underlying `SHOUTY_SNAKE_CASE`
1479    /// variant, since that is the canonical name).
1480    ///
1481    /// Defaults to `true`: the aliases are purely additive (the proto names
1482    /// remain the variants, and `Debug` is unchanged), so enabling by default is
1483    /// backward-compatible, and the all-or-nothing rule guarantees correctness on
1484    /// any enum.
1485    pub idiomatic_enum_aliases: bool,
1486    /// Emit `use`-backed short type names at the package root instead of
1487    /// fully-qualified paths, so generated code reads like hand-written
1488    /// Rust (`pub at: MessageField<Timestamp>` instead of
1489    /// `pub at: ::buffa::MessageField<::buffa_types::google::protobuf::Timestamp>`).
1490    ///
1491    /// Requires [`file_per_package`](Self::file_per_package): only there is
1492    /// the package-root scope a single-writer file whose complete name set
1493    /// is known at generation time. In the multi-file layout the stitcher
1494    /// `include!`-merges every proto's content files into the shared root
1495    /// scope, where emitted `use` directives could collide across files —
1496    /// [`generate`] returns an error for that combination rather than
1497    /// silently ignoring the flag.
1498    ///
1499    /// Off by default; default output is byte-for-byte unchanged. Short
1500    /// names are always backed by an explicit `use` (never glob reliance),
1501    /// are refused when they would collide with the package's own items or
1502    /// names referenced bare by sibling emissions, and fall back to
1503    /// parent-module qualification and then the fully-qualified path. The
1504    /// short-name *assignment* (use block and per-path choices) is computed
1505    /// from a collection pre-pass and is stable under `.proto` file
1506    /// reordering; item order within the file still follows input order,
1507    /// so whole-file output is not reorder-invariant. The pre-pass
1508    /// generates the package twice, roughly doubling codegen time for it.
1509    ///
1510    /// Scope: only package-root *type declarations* (struct fields, oneof
1511    /// `Option` wrappers) are shortened. Impl bodies, nested-message
1512    /// modules, and `__buffa` internals keep fully-qualified paths — the
1513    /// readability payoff lands where consumers look (struct definitions
1514    /// and rustdoc), not in the codec internals.
1515    ///
1516    /// **Experimental** means: the generated-output shape may change
1517    /// between releases (requiring regeneration of checked-in code), and
1518    /// the option itself may be renamed or removed outside semver
1519    /// guarantees.
1520    pub idiomatic_imports: bool,
1521    /// Convert proto field and oneof names to idiomatic snake_case Rust
1522    /// identifiers (`webMessageInfo` → `web_message_info`), matching
1523    /// prost-build's behavior for protos that use camelCase field names.
1524    ///
1525    /// Only the generated *Rust source names* change — struct fields, view
1526    /// accessors, `has_*`/`with_*` methods. Every name-keyed protocol surface
1527    /// keeps the descriptor's names: the wire format keys on field numbers,
1528    /// JSON uses `json_name` (with the original proto name still accepted on
1529    /// parse, per the proto3 JSON spec), text format and reflection lookups
1530    /// use the original proto name. Enum values and message/module names are
1531    /// not affected (see
1532    /// [`idiomatic_enum_aliases`](Self::idiomatic_enum_aliases) for enums),
1533    /// and extension accessors are `SHOUTY_SNAKE_CASE` constants derived
1534    /// independently of this option.
1535    ///
1536    /// Word boundaries match heck's (and therefore prost-build's)
1537    /// segmentation, including acronym handling (`XMLHttpRequest` →
1538    /// `xml_http_request`) and digit-transparent case boundaries
1539    /// (`v2Field` → `v2_field`). The one deliberate divergence from prost:
1540    /// the conversion is insertion-only and never deletes underscores the
1541    /// proto author wrote, so it is the identity on every name that is
1542    /// already a valid snake_case identifier (`_foo` stays `_foo`, where
1543    /// prost emits `foo`).
1544    ///
1545    /// The conversion is lossy, so two members of one message can collide
1546    /// (`userName` and `user_name`). Unlike enum aliases — which are additive
1547    /// `const`s and can simply be suppressed — a field rename replaces the
1548    /// canonical name, so collisions are resolved deterministically instead:
1549    /// a member whose name is already snake_case keeps it, a converted field
1550    /// that collides gets an `_f<field_number>` suffix (`userName = 12` →
1551    /// `user_name_f12`), and a converted oneof that collides keeps its
1552    /// verbatim proto name. If an adjusted field name still collides, the
1553    /// changed members in that collision group fall back to their verbatim
1554    /// proto names. Each adjustment is reported as a
1555    /// [`CodeGenWarning::IdiomaticFieldNamesAdjusted`]. protoc rejects the
1556    /// underlying name collisions for proto3 and editions files (conflicting
1557    /// `json_name`s), so adjustments are only reachable from proto2 inputs.
1558    ///
1559    /// Defaults to `false`: a rename is not backward-compatible for existing
1560    /// consumers of generated camelCase fields, and verbatim emission keeps
1561    /// the `.proto` file the source of truth. Opt in for prost parity.
1562    pub idiomatic_field_names: bool,
1563    /// Crate feature names used by the `#[cfg(feature = "...")]` gates that
1564    /// [`gate_impls_on_crate_features`](Self::gate_impls_on_crate_features)
1565    /// and
1566    /// [`gate_reflect_on_crate_feature`](Self::gate_reflect_on_crate_feature)
1567    /// emit.
1568    ///
1569    /// Defaults to `"json"` / `"views"` / `"text"` / `"reflect"`. Override a
1570    /// name when the consuming crate gates the same concern behind a
1571    /// different feature name (e.g. its JSON support behind a `serde`
1572    /// feature). Inert unless one of the gating flags is on.
1573    pub feature_gate_names: FeatureGateNames,
1574    /// Prefix prepended to every locally-generated Rust type name.
1575    ///
1576    /// With prefix `"Rpc"`, `message User {}` generates `struct RpcUser`,
1577    /// its view becomes `RpcUserView` / `RpcUserOwnedView`, and every
1578    /// cross-reference (fields, oneof variants, maps, extensions) uses the
1579    /// prefixed name. Useful in multi-protocol systems where generated
1580    /// types from different domains would otherwise collide with each
1581    /// other or with a canonical hand-written model.
1582    ///
1583    /// The prefix applies to **message structs and enum types** (top-level
1584    /// and nested, plus their derived view/owned-view types). It does not
1585    /// apply to:
1586    ///
1587    /// - module names (`message Outer` still nests under `pub mod outer` —
1588    ///   modules are namespaced by the package tree and never collide with
1589    ///   type names),
1590    /// - oneof enums (structurally namespaced under `__buffa::oneof::`,
1591    ///   named after the oneof declaration, not the message),
1592    /// - types mapped away via [`extern_paths`](Self::extern_paths) or the
1593    ///   automatic well-known-type mapping (their names are owned by the
1594    ///   external crate),
1595    /// - wire-format and JSON output (proto names, `TYPE_URL`s, and JSON
1596    ///   field names are unaffected — this is a pure Rust-identifier
1597    ///   rename).
1598    ///
1599    /// When another codegen run references these prefixed types via its own
1600    /// [`extern_paths`](Self::extern_paths) mapping, the mapped Rust path
1601    /// must spell out the prefixed name (e.g. `::crate_a::RpcUser`) — the
1602    /// proto name carries no prefix, so the mapping is not derived
1603    /// automatically. Prefix-induced name collisions (e.g. `message RpcUser`
1604    /// alongside `message User` with prefix `Rpc`) are not detected here;
1605    /// they surface as ordinary duplicate-definition errors when the
1606    /// generated code is compiled.
1607    ///
1608    /// Must be PascalCase (`[A-Z][A-Za-z0-9]*`) — an ASCII uppercase letter
1609    /// followed by ASCII letters and digits — so the prefixed names stay
1610    /// conventionally cased; generation fails with
1611    /// [`CodeGenError::InvalidTypeNamePrefix`] otherwise. Defaults to `""`
1612    /// (no prefix).
1613    pub type_name_prefix: String,
1614}
1615
1616impl Default for CodeGenConfig {
1617    fn default() -> Self {
1618        Self {
1619            generate_views: true,
1620            lazy_views: false,
1621            preserve_unknown_fields: true,
1622            generate_json: false,
1623            generate_arbitrary: false,
1624            extern_paths: Vec::new(),
1625            bytes_fields: Vec::new(),
1626            string_fields: Vec::new(),
1627            map_fields: Vec::new(),
1628            pointer_fields: Vec::new(),
1629            repeated_fields: Vec::new(),
1630            feature_overrides: Vec::new(),
1631            unboxed_oneof_fields: Vec::new(),
1632            strict_utf8_mapping: false,
1633            allow_message_set: false,
1634            generate_text: false,
1635            emit_register_fn: true,
1636            file_per_package: false,
1637            type_attributes: Vec::new(),
1638            field_attributes: Vec::new(),
1639            message_attributes: Vec::new(),
1640            enum_attributes: Vec::new(),
1641            oneof_attributes: Vec::new(),
1642            gate_impls_on_crate_features: false,
1643            generate_with_setters: true,
1644            generate_reflection: false,
1645            generate_reflection_vtable: false,
1646            gate_reflect_on_crate_feature: false,
1647            idiomatic_enum_aliases: true,
1648            idiomatic_imports: false,
1649            idiomatic_field_names: false,
1650            feature_gate_names: FeatureGateNames::default(),
1651            type_name_prefix: String::new(),
1652        }
1653    }
1654}
1655
1656impl CodeGenConfig {
1657    /// Whether any [`FeatureOverride::EnumType`] rule is configured — the
1658    /// gate for the open-enum declared-default machinery (which can only
1659    /// fire when a closed enum has been opened by such a rule).
1660    pub(crate) fn has_enum_type_overrides(&self) -> bool {
1661        self.feature_overrides
1662            .iter()
1663            .any(|(_, o)| matches!(o, FeatureOverride::EnumType(_)))
1664    }
1665
1666    /// Active [`feature_gates::FeatureGates`] for this config.
1667    ///
1668    /// Recomputed on each call (cheap — three boolean ANDs); call once at
1669    /// the top of a generation function and thread through, or call inline
1670    /// at each use site, whichever reads better.
1671    pub(crate) fn feature_gates(&self) -> feature_gates::FeatureGates<'_> {
1672        feature_gates::FeatureGates::for_config(self)
1673    }
1674
1675    /// Apply [`type_name_prefix`](Self::type_name_prefix) to a locally
1676    /// generated type's proto simple name, yielding the Rust identifier to
1677    /// declare (and register in the type map).
1678    pub(crate) fn prefixed_type_name(&self, proto_name: &str) -> String {
1679        format!("{}{proto_name}", self.type_name_prefix)
1680    }
1681
1682    /// Validate [`type_name_prefix`](Self::type_name_prefix): empty (no
1683    /// prefix) or PascalCase (`[A-Z][A-Za-z0-9]*`), so `{prefix}{TypeName}`
1684    /// is always a valid, conventionally-cased identifier that does not
1685    /// trip `non_camel_case_types` in consumer crates.
1686    pub(crate) fn validate_type_name_prefix(&self) -> Result<(), CodeGenError> {
1687        let prefix = &self.type_name_prefix;
1688        let valid = prefix.is_empty()
1689            || (prefix.starts_with(|c: char| c.is_ascii_uppercase())
1690                && prefix.chars().all(|c| c.is_ascii_alphanumeric()));
1691        if valid {
1692            Ok(())
1693        } else {
1694            Err(CodeGenError::InvalidTypeNamePrefix {
1695                prefix: prefix.clone(),
1696            })
1697        }
1698    }
1699}
1700
1701/// Compute the effective extern path list by starting with user-provided
1702/// mappings and adding the default WKT mapping if appropriate.
1703///
1704/// The default mapping `".google.protobuf" → "::buffa_types::google::protobuf"`
1705/// is added unless:
1706/// - The user already provided an extern_path covering `.google.protobuf`
1707/// - Any of the files being generated are in the `google.protobuf` package
1708///   (i.e., we're building `buffa-types` itself)
1709pub(crate) fn effective_extern_paths(
1710    file_descriptors: &[FileDescriptorProto],
1711    files_to_generate: &[String],
1712    config: &CodeGenConfig,
1713) -> Vec<(String, String)> {
1714    let mut paths = config.extern_paths.clone();
1715
1716    // Only an EXACT .google.protobuf mapping suppresses auto-injection.
1717    // A sub-package mapping like .google.protobuf.compiler does NOT cover
1718    // WKTs like Timestamp — resolve_extern_prefix's longest-prefix matching
1719    // lets both coexist, so we still inject the parent mapping.
1720    let has_wkt_mapping = paths.iter().any(|(proto, _)| proto == ".google.protobuf");
1721
1722    if !has_wkt_mapping {
1723        // Check if we're generating google.protobuf files ourselves
1724        // (e.g., building buffa-types). If so, don't auto-map.
1725        let generating_wkts = file_descriptors
1726            .iter()
1727            .filter(|fd| {
1728                fd.name
1729                    .as_deref()
1730                    .is_some_and(|n| files_to_generate.iter().any(|f| f == n))
1731            })
1732            .any(|fd| fd.package.as_deref() == Some("google.protobuf"));
1733
1734        if !generating_wkts {
1735            paths.push((
1736                ".google.protobuf".to_string(),
1737                "::buffa_types::google::protobuf".to_string(),
1738            ));
1739        }
1740    }
1741
1742    paths
1743}
1744
1745/// Compute the effective file-level extern path list.
1746///
1747/// File-level mappings route a specific `.proto` file to a Rust module root,
1748/// taking priority over the package-level mappings from
1749/// [`effective_extern_paths`]. They exist to resolve a structural problem:
1750/// `descriptor.proto` is in the same `google.protobuf` package as the
1751/// JSON-mappable WKTs (`Timestamp`, `Any`, …), but its types live in
1752/// `buffa-descriptor`, not `buffa-types`. A single package-keyed
1753/// `.google.protobuf` extern_path can route the package to one crate or the
1754/// other; it can't split it. The file-level mapping splits it.
1755///
1756/// Auto-injected mappings (when not suppressed):
1757///
1758/// | Proto file | Rust module |
1759/// |---|---|
1760/// | `google/protobuf/descriptor.proto` | `::buffa_descriptor::generated::descriptor` |
1761/// | `google/protobuf/compiler/plugin.proto` | `::buffa_descriptor::generated::compiler` |
1762///
1763/// Suppression conditions, evaluated **per file**:
1764///
1765/// - **A user-provided `extern_path` covers the file's package.** That
1766///   override has covered the file's types since the package mapping was
1767///   introduced; auto-injecting a higher-priority file-level mapping would
1768///   silently redirect them away from the user's crate. Matching is via
1769///   the same longest-prefix logic the package resolver uses, so both an
1770///   exact `.google.protobuf` mapping and a sub-package
1771///   `.google.protobuf.compiler` mapping suppress the entries they cover —
1772///   `.google.protobuf` suppresses both, `.google.protobuf.compiler`
1773///   suppresses only `plugin.proto`.
1774/// - **The proto file itself is in `files_to_generate`.** When building
1775///   `buffa-descriptor` (or any local copy of `descriptor.proto`), its types
1776///   must resolve to the local module, not externally.
1777///
1778/// Currently internal-only — there is no `CodeGenConfig` field for
1779/// user-provided *file-level* mappings. The user-facing `extern_path` API is
1780/// keyed by proto package *or* type FQN (per-type overrides, issue #111);
1781/// per-file overrides may be added later as a public feature if a concrete
1782/// need arises.
1783pub(crate) fn effective_file_extern_paths(
1784    files_to_generate: &[String],
1785    config: &CodeGenConfig,
1786) -> Vec<(String, String)> {
1787    // (proto file path, proto package, Rust module root). The package is
1788    // recorded alongside the file so the user-override suppression check
1789    // is per-file: a `.google.protobuf.compiler` extern_path covers only
1790    // `plugin.proto`, while `.google.protobuf` covers both.
1791    const DESCRIPTOR_FILES: [(&str, &str, &str); 2] = [
1792        (
1793            "google/protobuf/descriptor.proto",
1794            "google.protobuf",
1795            "::buffa_descriptor::generated::descriptor",
1796        ),
1797        (
1798            "google/protobuf/compiler/plugin.proto",
1799            "google.protobuf.compiler",
1800            "::buffa_descriptor::generated::compiler",
1801        ),
1802    ];
1803
1804    DESCRIPTOR_FILES
1805        .into_iter()
1806        .filter(|(proto_file, package, _)| {
1807            // Yield to a user package-level extern_path that already covers
1808            // this file's package: anyone who wrote
1809            // `extern_path(".google.protobuf", "::my_crate")` (or a
1810            // sub-package mapping) today routes these types to their crate;
1811            // the auto-injected file-level mapping must not silently
1812            // outrank it.
1813            if context::resolve_extern_prefix(package, &config.extern_paths).is_some() {
1814                return false;
1815            }
1816            // Don't externalize a file we're generating locally.
1817            !files_to_generate.iter().any(|f| f == proto_file)
1818        })
1819        .map(|(proto_file, _, rust_module)| (proto_file.to_string(), rust_module.to_string()))
1820        .collect()
1821}
1822
1823/// One CamelCase collision: a target identifier and the proto value names that
1824/// would all convert onto it.
1825///
1826/// Part of [`CodeGenWarning::IdiomaticAliasesSuppressed`].
1827#[derive(Debug, Clone, PartialEq, Eq)]
1828#[non_exhaustive]
1829pub struct AliasConflict {
1830    /// The `UpperCamelCase` identifier the colliding values map to.
1831    pub camel_target: String,
1832    /// The proto value names that convert onto `camel_target` (includes a
1833    /// literal variant name when an alias would shadow it).
1834    pub proto_values: Vec<String>,
1835}
1836
1837/// A non-fatal diagnostic produced during code generation.
1838///
1839/// Returned by [`generate_with_diagnostics`]. Render the human-readable form via
1840/// the [`Display`](core::fmt::Display) impl (e.g. `cargo:warning={warning}`), or
1841/// match on the variant for programmatic handling. The enum and its variants are
1842/// `#[non_exhaustive]` so new diagnostic kinds and fields can be added without a
1843/// breaking change.
1844#[derive(Debug, Clone, PartialEq, Eq)]
1845#[non_exhaustive]
1846pub enum CodeGenWarning {
1847    /// Idiomatic CamelCase aliases were suppressed for an enum because two or
1848    /// more proto values collide after conversion, or a value would convert to
1849    /// an invalid identifier. The enum's `SHOUTY_SNAKE_CASE` variants are
1850    /// unaffected.
1851    #[non_exhaustive]
1852    IdiomaticAliasesSuppressed {
1853        /// The Rust name of the affected enum.
1854        enum_name: String,
1855        /// Each collision, by target identifier. Empty if the only problem was
1856        /// invalid identifiers.
1857        conflicts: Vec<AliasConflict>,
1858        /// Proto values that would convert to an invalid Rust identifier.
1859        invalid: Vec<String>,
1860    },
1861    /// A field or oneof accessor on a generated `FooOwnedView` wrapper was
1862    /// suppressed because the proto name collides with one of the wrapper's
1863    /// reserved method names (`decode`, `view`, `bytes`, …). The field stays
1864    /// fully accessible through `view()` on the wrapper (or
1865    /// `OwnedView::reborrow`).
1866    #[non_exhaustive]
1867    OwnedViewAccessorSuppressed {
1868        /// The Rust name of the wrapper type (e.g. `FooOwnedView`).
1869        wrapper_name: String,
1870        /// The proto field or oneof name whose accessor was suppressed.
1871        field_name: String,
1872    },
1873    /// `lazy_views` was requested with `generate_views` disabled; the lazy
1874    /// family reuses the eager view-oneof enums and eager sub-view types, so
1875    /// no lazy views were generated. Emitted once per generation run.
1876    #[non_exhaustive]
1877    LazyViewsRequireViews,
1878    /// `idiomatic_field_names` found two or more members of one message whose
1879    /// snake_case conversions collide, and adjusted the affected Rust names
1880    /// deterministically (`_f<number>` suffix for fields, verbatim fallback
1881    /// for oneofs — see [`CodeGenConfig::idiomatic_field_names`]). Wire,
1882    /// JSON, and text-format names are unaffected.
1883    #[non_exhaustive]
1884    IdiomaticFieldNamesAdjusted {
1885        /// Fully-qualified proto name of the affected message.
1886        message_name: String,
1887        /// `(proto_name, final_rust_name)` for each adjusted member, sorted
1888        /// by proto name.
1889        assignments: Vec<(String, String)>,
1890    },
1891    /// A [`feature_overrides`](CodeGenConfig::feature_overrides) rule matched
1892    /// nothing the override targets in the compiled descriptor set, so it
1893    /// changed nothing. Usually a typo, a missing nested-message segment, or
1894    /// a stale path after a proto rename — the affected paths silently keep
1895    /// their default semantics.
1896    #[non_exhaustive]
1897    FeatureOverrideMatchedNothing {
1898        /// The rule's path as configured (post-normalization).
1899        rule: String,
1900        /// The overridden feature's name (e.g. `"enum_type"`).
1901        feature: &'static str,
1902        /// The override value (e.g. `"OPEN"`).
1903        value: &'static str,
1904    },
1905}
1906
1907impl core::fmt::Display for CodeGenWarning {
1908    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1909        match self {
1910            Self::IdiomaticAliasesSuppressed {
1911                enum_name,
1912                conflicts,
1913                invalid,
1914            } => {
1915                // Name the cause accurately: a collision, an invalid identifier,
1916                // or both.
1917                let cause = match (conflicts.is_empty(), invalid.is_empty()) {
1918                    (false, true) => "naming conflict",
1919                    (true, false) => "invalid identifier",
1920                    _ => "naming conflict / invalid identifier",
1921                };
1922                write!(
1923                    f,
1924                    "enum `{enum_name}`: idiomatic CamelCase aliases suppressed ({cause})"
1925                )?;
1926                let mut parts: Vec<String> = conflicts
1927                    .iter()
1928                    .map(|c| format!("{} → {}", c.proto_values.join(", "), c.camel_target))
1929                    .collect();
1930                parts.extend(invalid.iter().map(|n| format!("{n} → invalid identifier")));
1931                if !parts.is_empty() {
1932                    write!(f, ": {}", parts.join("; "))?;
1933                }
1934                Ok(())
1935            }
1936            Self::OwnedViewAccessorSuppressed {
1937                wrapper_name,
1938                field_name,
1939            } => {
1940                write!(
1941                    f,
1942                    "`{wrapper_name}`: accessor for field `{field_name}` suppressed \
1943                     (collides with a reserved wrapper method); use `.view().{field_name}` instead"
1944                )
1945            }
1946            Self::LazyViewsRequireViews => {
1947                write!(
1948                    f,
1949                    "lazy_views requires generate_views (the lazy family reuses the \
1950                     eager view-oneof enums and sub-view types); no lazy views were \
1951                     generated — enable generate_views (buffa-build: \
1952                     `.generate_views(true)`, the default; plugin: `views=true`)"
1953                )
1954            }
1955            Self::IdiomaticFieldNamesAdjusted {
1956                message_name,
1957                assignments,
1958            } => {
1959                let parts: Vec<String> = assignments
1960                    .iter()
1961                    .map(|(proto, rust)| format!("`{proto}` → `{rust}`"))
1962                    .collect();
1963                write!(
1964                    f,
1965                    "message `{message_name}`: idiomatic snake_case field names collide; \
1966                     adjusted: {} (wire/JSON/text names are unaffected)",
1967                    parts.join(", ")
1968                )
1969            }
1970            Self::FeatureOverrideMatchedNothing {
1971                rule,
1972                feature,
1973                value,
1974            } => {
1975                write!(
1976                    f,
1977                    "feature override '{rule}' ({feature} = {value}) matched nothing in \
1978                     the compiled set; the affected paths keep their default semantics — \
1979                     check the path against the fully-qualified proto names"
1980                )
1981            }
1982        }
1983    }
1984}
1985
1986/// Generate Rust source files from a set of file descriptors.
1987///
1988/// `files_to_generate` is the set of file names that were explicitly requested
1989/// (matching `CodeGeneratorRequest.file_to_generate`). Descriptors for
1990/// dependencies may be present in `file_descriptors` but won't produce output
1991/// files unless they appear in `files_to_generate`.
1992///
1993/// Each `.proto` emits up to five content files (kinds with no content
1994/// are omitted); each distinct package emits one `<pkg>.mod.rs`
1995/// stitcher. Packages are processed in sorted order for deterministic
1996/// output.
1997///
1998/// # Diagnostics
1999///
2000/// Non-fatal diagnostics produced during generation (e.g. an enum whose
2001/// idiomatic CamelCase aliases were suppressed by a naming conflict) are
2002/// **discarded** here. Use [`generate_with_diagnostics`] to receive them and
2003/// surface them as build warnings.
2004pub fn generate(
2005    file_descriptors: &[FileDescriptorProto],
2006    files_to_generate: &[String],
2007    config: &CodeGenConfig,
2008) -> Result<Vec<GeneratedFile>, CodeGenError> {
2009    Ok(generate_with_diagnostics(file_descriptors, files_to_generate, config)?.0)
2010}
2011
2012/// Like [`generate`], but also returns the non-fatal [`CodeGenWarning`]s
2013/// collected during generation (e.g. enums whose idiomatic CamelCase aliases
2014/// were suppressed by a naming conflict).
2015///
2016/// Surface each warning via its [`Display`](core::fmt::Display) impl — e.g. as a
2017/// `cargo:warning=...` from a `build.rs`, or on stderr from a standalone
2018/// generator — or match on it for programmatic handling. [`generate`] discards
2019/// them, so existing callers are unaffected.
2020///
2021/// Warnings are returned only on success. On error, any warnings already
2022/// collected are dropped along with the partial output — the [`CodeGenError`]
2023/// is the actionable signal.
2024///
2025/// # Errors
2026///
2027/// Returns [`CodeGenError::FileNotFound`] if a name in `files_to_generate` has
2028/// no matching descriptor, [`CodeGenError::InvalidTypeNamePrefix`] if
2029/// [`CodeGenConfig::type_name_prefix`] is not empty or PascalCase,
2030/// [`CodeGenError::Other`] if `generate_reflection_vtable`
2031/// is set without `generate_reflection` or if an active feature-gate name in
2032/// [`CodeGenConfig::feature_gate_names`] is not a valid Cargo feature name,
2033/// and other [`CodeGenError`] variants for malformed descriptors (e.g. a
2034/// missing required field) encountered while generating.
2035/// Whether a custom `repeated` element type holds proto `string` or `bytes` —
2036/// selects `ValueRef::String`/`ValueRef::Bytes` and the JSON delegate module.
2037#[derive(Clone, Copy, PartialEq, Eq)]
2038enum CustomElemKind {
2039    String,
2040    Bytes,
2041}
2042
2043/// The custom owned types collected generation-wide that need a codegen-emitted
2044/// reflection / JSON impl, split by the trait each needs.
2045#[derive(Default)]
2046struct CustomElements {
2047    /// Types needing `ReflectElement` (+ `ProtoElemJson` for bytes): custom
2048    /// `repeated` elements, custom `map` *values* (`string` or `bytes`).
2049    elements: std::collections::BTreeMap<String, CustomElemKind>,
2050    /// Custom `string` types used as a `map` *key*: need `ReflectMapKey` (vtable
2051    /// reflection only — the bridge path keys maps by the borrowed `&str` view).
2052    map_keys: std::collections::BTreeSet<String>,
2053}
2054
2055/// Collect the distinct custom owned types that need a codegen-emitted element
2056/// impl (`ReflectElement` / `ProtoElemJson`), keyed by Rust type path, across
2057/// the whole request. These are custom `string`/`bytes` types used as the
2058/// element of a `repeated` field, and custom `bytes` types used as a
2059/// `map<K, bytes>` value — both reflect via the element trait and (for bytes)
2060/// serialize JSON via `proto_map`/`proto_seq`. Singular / optional / oneof
2061/// custom fields reach JSON and reflection without an element-trait impl, and
2062/// `string`/`Vec<u8>`/`Bytes` map values are covered by the built-in impls.
2063fn collect_custom_elements(
2064    ctx: &context::CodeGenContext,
2065    file_descriptors: &[FileDescriptorProto],
2066    files_to_generate: &[String],
2067) -> CustomElements {
2068    use crate::generated::descriptor::field_descriptor_proto::{Label, Type};
2069
2070    fn walk(
2071        ctx: &context::CodeGenContext,
2072        messages: &[crate::generated::descriptor::DescriptorProto],
2073        scope: &str,
2074        parent_features: &crate::features::ResolvedFeatures,
2075        out: &mut CustomElements,
2076    ) {
2077        for msg in messages {
2078            let name = msg.name.as_deref().unwrap_or("");
2079            let fqn = if scope.is_empty() {
2080                name.to_string()
2081            } else {
2082                format!("{scope}.{name}")
2083            };
2084            let msg_features = crate::features::resolve_child(
2085                parent_features,
2086                crate::features::message_features(msg),
2087            );
2088            for field in &msg.field {
2089                if field.label.unwrap_or_default() != Label::LABEL_REPEATED {
2090                    continue;
2091                }
2092                let field_name = field.name.as_deref().unwrap_or("");
2093                let field_fqn = format!(".{fqn}.{field_name}");
2094
2095                // `map` slots: a custom value type needs the element impls
2096                // (reflected via ReflectMap → ReflectElement, JSON via
2097                // proto_map → ProtoElemJson for bytes), and a custom `string`
2098                // key needs ReflectMapKey. All keyed on the outer map field
2099                // path (the same `string_type` rule covers both slots), with the
2100                // `map<bytes, bytes>` value carve-out.
2101                if let Some(entry) = crate::message::find_map_entry(msg, field) {
2102                    let key_ty = crate::message::map_entry_key_type(ctx, entry, &msg_features);
2103                    let val_ty = crate::message::map_entry_value_type(ctx, entry, &msg_features);
2104                    if let crate::BytesRepr::Custom(path) =
2105                        crate::impl_message::map_value_bytes_repr(
2106                            ctx, key_ty, val_ty, &fqn, field_name,
2107                        )
2108                    {
2109                        out.elements.entry(path).or_insert(CustomElemKind::Bytes);
2110                    }
2111                    if let crate::StringRepr::Custom(path) = ctx.string_repr(&field_fqn) {
2112                        if key_ty == Some(Type::TYPE_STRING) {
2113                            out.map_keys.insert(path.clone());
2114                        }
2115                        if val_ty == Some(Type::TYPE_STRING) {
2116                            out.elements.entry(path).or_insert(CustomElemKind::String);
2117                        }
2118                    }
2119                    continue;
2120                }
2121
2122                let field_features = crate::features::resolve_field(ctx, field, &msg_features);
2123                let ty = crate::impl_message::effective_type(ctx, field, &field_features);
2124                match ty {
2125                    Type::TYPE_STRING => {
2126                        if let crate::StringRepr::Custom(path) = ctx.string_repr(&field_fqn) {
2127                            out.elements.entry(path).or_insert(CustomElemKind::String);
2128                        }
2129                    }
2130                    Type::TYPE_BYTES => {
2131                        if let crate::BytesRepr::Custom(path) = ctx.bytes_repr(&field_fqn) {
2132                            out.elements.entry(path).or_insert(CustomElemKind::Bytes);
2133                        }
2134                    }
2135                    _ => {}
2136                }
2137            }
2138            walk(ctx, &msg.nested_type, &fqn, &msg_features, out);
2139        }
2140    }
2141
2142    let mut out = CustomElements::default();
2143    for file_name in files_to_generate {
2144        let Some(file) = file_descriptors
2145            .iter()
2146            .find(|f| f.name.as_deref() == Some(file_name.as_str()))
2147        else {
2148            continue;
2149        };
2150        let pkg = file.package.as_deref().unwrap_or("");
2151        let file_features = crate::features::for_file(file);
2152        walk(ctx, &file.message_type, pkg, &file_features, &mut out);
2153    }
2154    out
2155}
2156
2157/// Render the deduped `ProtoElemJson` / `ReflectElement` impls for the collected
2158/// custom element types (repeated elements and `map<K, bytes>` values). Each
2159/// impl is feature-gated so a non-JSON /
2160/// non-reflect build never references an absent trait. These compile only when
2161/// the custom type is local to the generating crate (the orphan rule); that is
2162/// the documented limitation of a custom `repeated` element under JSON or vtable
2163/// reflection.
2164fn render_custom_elem_impls(
2165    ctx: &context::CodeGenContext,
2166    elems: &CustomElements,
2167) -> Result<TokenStream, CodeGenError> {
2168    let json_gate = ctx.config.feature_gates().json;
2169    let reflect_gate = ctx.config.feature_gates().reflect;
2170    let mut out = TokenStream::new();
2171    for (path, kind) in &elems.elements {
2172        let ty = parse_custom_type_path(path)?;
2173        // `ProtoElemJson` is only needed for the `bytes` element path (proto3
2174        // JSON base64). A repeated `string` element serializes through the
2175        // native `Vec<T>` serde derive, and custom `string` map keys/values go
2176        // through serde too (the derive / `string_key_map` / `proto_str_key_map`
2177        // paths), so a String-kind `ProtoElemJson` impl would be dead code.
2178        if ctx.config.generate_json && *kind == CustomElemKind::Bytes {
2179            out.extend(feature_gates::cfg_block(
2180                quote! {
2181                    impl ::buffa::json_helpers::ProtoElemJson for #ty {
2182                        fn serialize_proto_json<S: ::serde::Serializer>(
2183                            v: &Self,
2184                            s: S,
2185                        ) -> ::core::result::Result<S::Ok, S::Error> {
2186                            ::buffa::json_helpers::bytes::serialize(
2187                                ::core::convert::AsRef::<[u8]>::as_ref(v),
2188                                s,
2189                            )
2190                        }
2191                        fn deserialize_proto_json<'de, D: ::serde::Deserializer<'de>>(
2192                            d: D,
2193                        ) -> ::core::result::Result<Self, D::Error> {
2194                            ::buffa::json_helpers::bytes::deserialize(d)
2195                        }
2196                    }
2197                },
2198                json_gate,
2199            ));
2200        }
2201        if ctx.config.generate_reflection_vtable {
2202            let value_ref = match kind {
2203                CustomElemKind::String => quote! {
2204                    ::buffa_descriptor::reflect::ValueRef::String(
2205                        ::core::convert::AsRef::<str>::as_ref(self),
2206                    )
2207                },
2208                CustomElemKind::Bytes => quote! {
2209                    ::buffa_descriptor::reflect::ValueRef::Bytes(
2210                        ::core::convert::AsRef::<[u8]>::as_ref(self),
2211                    )
2212                },
2213            };
2214            out.extend(feature_gates::cfg_block(
2215                quote! {
2216                    impl ::buffa_descriptor::reflect::ReflectElement for #ty {
2217                        fn as_value_ref(&self) -> ::buffa_descriptor::reflect::ValueRef<'_> {
2218                            #value_ref
2219                        }
2220                    }
2221                },
2222                reflect_gate,
2223            ));
2224        }
2225    }
2226    // A custom `string` type used as a `map` key needs `ReflectMapKey` for
2227    // vtable reflection (the bridge path keys maps by the borrowed `&str` view,
2228    // which already implements it). Like the element impls above, this compiles
2229    // only when the type is local to the generating crate (the orphan rule).
2230    if ctx.config.generate_reflection_vtable {
2231        for path in &elems.map_keys {
2232            let ty = parse_custom_type_path(path)?;
2233            out.extend(feature_gates::cfg_block(
2234                quote! {
2235                    impl ::buffa_descriptor::reflect::ReflectMapKey for #ty {
2236                        fn as_map_key_ref(&self) -> ::buffa_descriptor::reflect::MapKeyRef<'_> {
2237                            ::buffa_descriptor::reflect::MapKeyRef::String(
2238                                ::core::convert::AsRef::<str>::as_ref(self),
2239                            )
2240                        }
2241                    }
2242                },
2243                reflect_gate,
2244            ));
2245        }
2246    }
2247    Ok(out)
2248}
2249
2250pub fn generate_with_diagnostics(
2251    file_descriptors: &[FileDescriptorProto],
2252    files_to_generate: &[String],
2253    config: &CodeGenConfig,
2254) -> Result<(Vec<GeneratedFile>, Vec<CodeGenWarning>), CodeGenError> {
2255    // Vtable reflection resolves against the per-package descriptor pool, which
2256    // is emitted by bridge-mode reflection — so it requires `generate_reflection`.
2257    // It does NOT require views: the owned `impl ReflectMessage` is self-contained,
2258    // so with views off, vtable mode still emits owned-message reflection (the
2259    // view impls are simply skipped along with the views).
2260    if config.generate_reflection_vtable && !config.generate_reflection {
2261        return Err(CodeGenError::Other(
2262            "generate_reflection_vtable requires generate_reflection to be enabled \
2263             (it provides the descriptor pool the reflect impls resolve against)"
2264                .into(),
2265        ));
2266    }
2267
2268    // Idiomatic imports place `use` directives in the package-root scope,
2269    // which is only single-writer (collision-free by construction) when the
2270    // whole package is one generated file.
2271    if config.idiomatic_imports && !config.file_per_package {
2272        return Err(CodeGenError::Other(
2273            "idiomatic_imports requires file_per_package to be enabled (the multi-file \
2274             layout include!-merges every proto's content into the shared package root, \
2275             where emitted `use` directives could collide across files)"
2276                .into(),
2277        ));
2278    }
2279
2280    // Active feature-gate names are emitted verbatim into
2281    // `#[cfg(feature = "...")]`; an invalid name fails open (the cfg is
2282    // permanently false and the gated impls silently compile away), so it
2283    // must be a hard error here rather than a debug assertion — build
2284    // scripts and protoc plugins typically run as release builds.
2285    if let Err((kind, name)) = config.feature_gates().validate() {
2286        return Err(CodeGenError::Other(format!(
2287            "invalid {kind} feature-gate name {name:?}: a Cargo feature name starts \
2288             with an ASCII alphanumeric or '_' and contains only alphanumerics, \
2289             '_', '-', '+', or '.'; an invalid name would leave the emitted \
2290             #[cfg(feature = ...)] permanently false, silently compiling the \
2291             gated impls away"
2292        )));
2293    }
2294
2295    config.validate_type_name_prefix()?;
2296
2297    // Feature overrides are applied by mutating the descriptor set up front,
2298    // so every downstream consumer — feature resolution, all generation
2299    // paths, and the embedded reflection descriptor pool — reads the same
2300    // overridden features. With no overrides configured this is a no-op
2301    // borrow.
2302    let overridden =
2303        feature_overrides::apply_feature_overrides(file_descriptors, &config.feature_overrides);
2304    let file_descriptors: &[FileDescriptorProto] =
2305        overridden.as_ref().map_or(file_descriptors, |o| &o.files);
2306
2307    let ctx = context::CodeGenContext::for_generate(file_descriptors, files_to_generate, config);
2308
2309    // An inert rule means the user opted a path out of its default semantics
2310    // and silently didn't get it — warn per rule so typos surface at build
2311    // time instead of as production behavior surprises.
2312    if let Some(o) = &overridden {
2313        for (rule, ovr) in &o.unmatched {
2314            ctx.warn(CodeGenWarning::FeatureOverrideMatchedNothing {
2315                rule: rule.clone(),
2316                feature: ovr.feature_name(),
2317                value: ovr.value_name(),
2318            });
2319        }
2320    }
2321
2322    // Lazy views need the eager view machinery; warn once per run.
2323    if config.lazy_views && !config.generate_views {
2324        ctx.warn(CodeGenWarning::LazyViewsRequireViews);
2325    }
2326
2327    // Group requested files by package. BTreeMap → deterministic output order.
2328    let mut by_package: std::collections::BTreeMap<String, Vec<&FileDescriptorProto>> =
2329        std::collections::BTreeMap::new();
2330    for file_name in files_to_generate {
2331        let file_desc = file_descriptors
2332            .iter()
2333            .find(|f| f.name.as_deref() == Some(file_name.as_str()))
2334            .ok_or_else(|| CodeGenError::FileNotFound(file_name.clone()))?;
2335        let pkg = file_desc.package.as_deref().unwrap_or("").to_string();
2336        by_package.entry(pkg).or_default().push(file_desc);
2337    }
2338
2339    // Reflection: serialize the FileDescriptorSet once, regardless of how
2340    // many packages are in the request. Each package embeds its own copy of
2341    // the bytes (binary-size dedup is a follow-up), but the build-time
2342    // re-encoding cost shouldn't scale with the package count.
2343    let fds_bytes = if config.generate_reflection {
2344        reflect::encode_fds_once(file_descriptors)
2345    } else {
2346        Vec::new()
2347    };
2348
2349    // Custom owned types used as elements of a `repeated` field need a
2350    // `ProtoElemJson` (JSON) and/or `ReflectElement` (vtable) impl, which buffa
2351    // cannot provide for a foreign type (orphan rule). Collect them once across
2352    // the whole request, render the impls, and hand them to the first package so
2353    // they are emitted exactly once (a per-package emit would collide, E0119).
2354    let custom_elems = collect_custom_elements(&ctx, file_descriptors, files_to_generate);
2355    let custom_elem_impls = render_custom_elem_impls(&ctx, &custom_elems)?;
2356
2357    let empty_impls = TokenStream::new();
2358    let mut output = Vec::new();
2359    let mut custom_emitted = false;
2360    for (package, files) in by_package {
2361        let impls = if custom_emitted {
2362            &empty_impls
2363        } else {
2364            custom_emitted = true;
2365            &custom_elem_impls
2366        };
2367        generate_package(&ctx, &package, &files, &fds_bytes, impls, &mut output)?;
2368    }
2369
2370    Ok((output, ctx.take_warnings()))
2371}
2372
2373/// Generate a module tree that assembles per-package `.mod.rs` files into
2374/// nested `pub mod` blocks matching the protobuf package hierarchy.
2375///
2376/// Each entry is a `(mod_file_name, package)` pair where `package` is the
2377/// dot-separated protobuf package name (e.g., `"google.api"`) and
2378/// `mod_file_name` is the corresponding `<pkg>.mod.rs` (only
2379/// [`GeneratedFileKind::PackageMod`] outputs need wiring; per-proto
2380/// content files are reached via `include!` from the stitcher).
2381///
2382/// `include_mode` controls how `include!` paths are emitted.
2383///
2384/// `emit_inner_allow` adds a `#![allow(...)]` inner attribute at the top —
2385/// valid when the output is used directly as a module file (`mod.rs`),
2386/// invalid when consumed via `include!`.
2387pub fn generate_module_tree<F: AsRef<str>, P: AsRef<str>>(
2388    entries: &[(F, P)],
2389    include_mode: IncludeMode<'_>,
2390    emit_inner_allow: bool,
2391) -> String {
2392    use std::collections::BTreeMap;
2393    use std::fmt::Write;
2394
2395    use crate::idents::escape_mod_ident;
2396
2397    #[derive(Default)]
2398    struct ModNode {
2399        files: Vec<String>,
2400        children: BTreeMap<String, Self>,
2401    }
2402
2403    let mut root = ModNode::default();
2404
2405    for (file_name, package) in entries {
2406        let package = package.as_ref();
2407        let pkg_parts: Vec<&str> = if package.is_empty() {
2408            vec![]
2409        } else {
2410            package.split('.').collect()
2411        };
2412
2413        let mut node = &mut root;
2414        for seg in &pkg_parts {
2415            node = node.children.entry(seg.to_string()).or_default();
2416        }
2417        node.files.push(file_name.as_ref().to_string());
2418    }
2419
2420    let lints = ALLOW_LINTS.join(", ");
2421    let mut out = String::new();
2422    let _ = writeln!(out, "// @generated by buffa-codegen. DO NOT EDIT.");
2423    if emit_inner_allow {
2424        let _ = writeln!(out, "#![allow({lints})]");
2425    }
2426    let _ = writeln!(out);
2427
2428    fn emit(out: &mut String, node: &ModNode, depth: usize, mode: IncludeMode<'_>, lints: &str) {
2429        let indent = "    ".repeat(depth);
2430
2431        for file in &node.files {
2432            match mode {
2433                IncludeMode::Relative(prefix) => {
2434                    let _ = writeln!(out, r#"{indent}include!("{prefix}{file}");"#);
2435                }
2436                IncludeMode::OutDir => {
2437                    let _ = writeln!(
2438                        out,
2439                        r#"{indent}include!(concat!(env!("OUT_DIR"), "/{file}"));"#
2440                    );
2441                }
2442            }
2443        }
2444
2445        for (name, child) in &node.children {
2446            let escaped = escape_mod_ident(name);
2447            let _ = writeln!(out, "{indent}#[allow({lints})]");
2448            let _ = writeln!(out, "{indent}pub mod {escaped} {{");
2449            let _ = writeln!(out, "{indent}    use super::*;");
2450            emit(out, child, depth + 1, mode, lints);
2451            let _ = writeln!(out, "{indent}}}");
2452        }
2453    }
2454
2455    emit(&mut out, &root, 0, include_mode, &lints);
2456    out
2457}
2458
2459/// How [`generate_module_tree`] emits `include!` paths.
2460#[derive(Debug, Clone, Copy)]
2461pub enum IncludeMode<'a> {
2462    /// `include!("<prefix><file>")` — relative to the including file.
2463    /// Prefix is typically `""` or `"gen/"`.
2464    Relative(&'a str),
2465    /// `include!(concat!(env!("OUT_DIR"), "/<file>"))` — for build.rs output.
2466    OutDir,
2467}
2468
2469/// Validate one input descriptor before generating code for it.
2470///
2471/// Checks, in one walk of the message tree:
2472///
2473/// - **Reserved field names**: no field starts with `__buffa_` (would clash
2474///   with generated `__buffa_unknown_fields` / `__buffa_cached_size`).
2475/// - **Module-name conflicts**: no two sibling messages snake_case to the
2476///   same module name (e.g. `HTTPRequest` vs `HttpRequest`).
2477/// - **Reserved sentinel**: no package segment, message-module name, or
2478///   file-level enum name equals [`SENTINEL_MOD`](context::SENTINEL_MOD).
2479///   Ancillary types live under `pkg::__buffa::…`; a proto element
2480///   emitting an item named `__buffa` at package root would produce
2481///   E0428 against `pub mod __buffa`. This is the only name buffa
2482///   reserves in user namespace.
2483fn validate_file(file: &FileDescriptorProto) -> Result<(), CodeGenError> {
2484    use std::collections::HashMap;
2485
2486    let sentinel = context::SENTINEL_MOD;
2487    let package = file.package.as_deref().unwrap_or("");
2488    if package.split('.').any(|seg| seg == sentinel) {
2489        return Err(CodeGenError::ReservedModuleName {
2490            name: sentinel.to_string(),
2491            location: format!("package '{package}'"),
2492        });
2493    }
2494    // File-level enums emit `pub enum <name>` at package root with the
2495    // proto name preserved verbatim (no PascalCase normalization), so a
2496    // proto `enum __buffa` would land beside `pub mod __buffa`. Nested
2497    // enums live inside their owner message's module and cannot collide
2498    // with the package-root sentinel, so only file-level is checked.
2499    for enum_type in &file.enum_type {
2500        let name = enum_type.name.as_deref().unwrap_or("");
2501        if name == sentinel {
2502            return Err(CodeGenError::ReservedModuleName {
2503                name: sentinel.to_string(),
2504                location: format!("enum '{package}.{name}'"),
2505            });
2506        }
2507    }
2508
2509    fn walk(
2510        messages: &[crate::generated::descriptor::DescriptorProto],
2511        scope: &str,
2512        sentinel: &str,
2513    ) -> Result<(), CodeGenError> {
2514        // snake_case module name → original proto name (for conflict diag).
2515        let mut seen: HashMap<String, &str> = HashMap::new();
2516
2517        for msg in messages {
2518            let name = msg.name.as_deref().unwrap_or("");
2519            let fqn = if scope.is_empty() {
2520                name.to_string()
2521            } else {
2522                format!("{scope}.{name}")
2523            };
2524
2525            for field in &msg.field {
2526                if let Some(fname) = &field.name {
2527                    if fname.starts_with("__buffa_") {
2528                        return Err(CodeGenError::ReservedFieldName {
2529                            message_name: fqn,
2530                            field_name: fname.clone(),
2531                        });
2532                    }
2533                }
2534            }
2535
2536            let module_name = crate::oneof::to_snake_case(name);
2537            if module_name == sentinel {
2538                return Err(CodeGenError::ReservedModuleName {
2539                    name: sentinel.to_string(),
2540                    location: format!("message '{fqn}'"),
2541                });
2542            }
2543            if let Some(existing) = seen.get(&module_name) {
2544                return Err(CodeGenError::ModuleNameConflict {
2545                    scope: scope.to_string(),
2546                    name_a: existing.to_string(),
2547                    name_b: name.to_string(),
2548                    module_name,
2549                });
2550            }
2551            seen.insert(module_name, name);
2552
2553            walk(&msg.nested_type, &fqn, sentinel)?;
2554        }
2555        Ok(())
2556    }
2557
2558    walk(&file.message_type, package, sentinel)
2559}
2560
2561/// Per-proto content streams plus the file stem, ready to be formatted.
2562struct ProtoContent {
2563    stem: String,
2564    owned: TokenStream,
2565    view: TokenStream,
2566    lazy_view: TokenStream,
2567    oneof: TokenStream,
2568    view_oneof: TokenStream,
2569    ext: TokenStream,
2570    /// Candidate `pub use` re-exports targeting the package root (top-level
2571    /// view structs, file-level extension consts). Filtered against the
2572    /// package-wide root namespace in [`generate_package_mod`] — the package
2573    /// can span multiple `.proto` files, so collisions are only knowable at
2574    /// the stitcher level.
2575    root_reexports: Vec<message::ReexportCandidate>,
2576}
2577
2578/// Generate the per-`.proto` content token streams for one input file.
2579/// Each ancillary kind that has no content yields an empty stream and
2580/// is dropped at the file-emission stage.
2581fn generate_proto_content(
2582    ctx: &context::CodeGenContext,
2583    current_package: &str,
2584    file: &FileDescriptorProto,
2585    reg: &mut message::RegistryPaths,
2586) -> Result<ProtoContent, CodeGenError> {
2587    use crate::idents::make_field_ident;
2588    use crate::message::MessageOutput;
2589
2590    validate_file(file)?;
2591
2592    let resolver = imports::ImportResolver::new();
2593    let features = crate::features::for_file(file);
2594
2595    let mut owned = TokenStream::new();
2596    let mut view = TokenStream::new();
2597    let mut lazy_view = TokenStream::new();
2598    let mut oneof = TokenStream::new();
2599    let mut view_oneof = TokenStream::new();
2600    let mut ext = TokenStream::new();
2601    let mut root_reexports: Vec<message::ReexportCandidate> = Vec::new();
2602    let sentinel = make_field_ident(context::SENTINEL_MOD);
2603
2604    for enum_type in &file.enum_type {
2605        let enum_proto_name = enum_type.name.as_deref().unwrap_or("");
2606        let enum_rust_name = ctx.config.prefixed_type_name(enum_proto_name);
2607        let enum_fqn = if current_package.is_empty() {
2608            enum_proto_name.to_string()
2609        } else {
2610            format!("{}.{}", current_package, enum_proto_name)
2611        };
2612        owned.extend(enumeration::generate_enum(
2613            ctx,
2614            enum_type,
2615            &enum_rust_name,
2616            &enum_fqn,
2617            &features,
2618            &resolver,
2619        )?);
2620    }
2621
2622    for message_type in &file.message_type {
2623        let top_level_name = message_type.name.as_deref().unwrap_or("");
2624        let rust_name = ctx.config.prefixed_type_name(top_level_name);
2625        let proto_fqn = if current_package.is_empty() {
2626            top_level_name.to_string()
2627        } else {
2628            format!("{}.{}", current_package, top_level_name)
2629        };
2630        let MessageOutput {
2631            owned_top,
2632            owned_mod,
2633            oneof_tree: msg_oneof,
2634            view_tree: msg_view,
2635            lazy_view_tree: msg_lazy_view,
2636            view_oneof_tree: msg_view_oneof,
2637            reg: msg_reg,
2638        } = message::generate_message(
2639            ctx,
2640            message_type,
2641            current_package,
2642            &rust_name,
2643            &proto_fqn,
2644            &features,
2645            &resolver,
2646        )?;
2647        owned.extend(owned_top);
2648        let mod_name = ctx.nested_module_name(current_package, top_level_name);
2649        let mod_ident = make_field_ident(&mod_name);
2650        // When the nested-types module was deconflicted from a sub-package
2651        // (issue #135), document why the name carries a trailing `_`.
2652        let mod_doc = if mod_name == crate::oneof::to_snake_case(top_level_name) {
2653            quote! {}
2654        } else {
2655            let doc = format!(
2656                "Nested items of `{top_level_name}`. The module name carries a \
2657                 trailing `_` to avoid a collision with another module in this \
2658                 scope (a sub-package or sibling message of the same name). See \
2659                 buffa#135."
2660            );
2661            quote! { #[doc = #doc] }
2662        };
2663        for p in msg_reg.json_ext {
2664            reg.json_ext.push(quote! { #mod_ident :: #p });
2665        }
2666        for p in msg_reg.text_ext {
2667            reg.text_ext.push(quote! { #mod_ident :: #p });
2668        }
2669        reg.json_any.extend(msg_reg.json_any);
2670        reg.text_any.extend(msg_reg.text_any);
2671
2672        if !owned_mod.is_empty() {
2673            owned.extend(quote! {
2674                #mod_doc
2675                pub mod #mod_ident {
2676                    #[allow(unused_imports)]
2677                    use super::*;
2678                    #owned_mod
2679                }
2680            });
2681        }
2682        oneof.extend(msg_oneof);
2683        view.extend(msg_view);
2684        lazy_view.extend(msg_lazy_view);
2685        view_oneof.extend(msg_view_oneof);
2686
2687        // Top-level message view → re-export at package root. The leading
2688        // `self::` is load-bearing: when consumers nest packages with
2689        // `pub mod a { use super::*; pub mod a_b { use super::*; … } }`
2690        // (`buffa-build`'s `_include.rs` does this), a parent package's
2691        // `__buffa` is in scope via the glob, and Rust's import-resolution
2692        // pass treats a glob-imported name as ambiguous against a
2693        // **macro-expanded** local one (the `pub mod __buffa` block arrives
2694        // via `include!()`), even though a non-macro local definition would
2695        // shadow the glob — see rustc E0659. `self::` resolves it
2696        // deterministically. `#[doc(inline)]` makes rustdoc render the type's
2697        // full page at the natural path instead of a "Re-export of …" stub.
2698        if ctx.config.generate_views {
2699            let view_ident = format_ident!("{rust_name}View");
2700            root_reexports.push(message::ReexportCandidate {
2701                name: view_ident.to_string(),
2702                tokens: feature_gates::cfg_block(
2703                    quote! {
2704                        #[doc(inline)]
2705                        pub use self :: #sentinel :: view :: #view_ident;
2706                    },
2707                    ctx.config.feature_gates().views,
2708                ),
2709            });
2710            // The owned-view wrapper gets the same natural-path treatment as
2711            // the view struct, so `pkg::FooOwnedView` works out of the box.
2712            let owned_view_ident = format_ident!("{rust_name}OwnedView");
2713            root_reexports.push(message::ReexportCandidate {
2714                name: owned_view_ident.to_string(),
2715                tokens: feature_gates::cfg_block(
2716                    quote! {
2717                        #[doc(inline)]
2718                        pub use self :: #sentinel :: view :: #owned_view_ident;
2719                    },
2720                    ctx.config.feature_gates().views,
2721                ),
2722            });
2723            if ctx.config.lazy_views {
2724                let lazy_ident = format_ident!("{rust_name}LazyView");
2725                root_reexports.push(message::ReexportCandidate {
2726                    name: lazy_ident.to_string(),
2727                    tokens: feature_gates::cfg_block(
2728                        quote! {
2729                            #[doc(inline)]
2730                            pub use self :: #sentinel :: lazy_view :: #lazy_ident;
2731                        },
2732                        ctx.config.feature_gates().views,
2733                    ),
2734                });
2735            }
2736        }
2737    }
2738
2739    // File-level `extend` declarations → `__buffa::ext::` (depth 2).
2740    let (file_ext_tokens, file_ext_json, file_ext_text) = extension::generate_extensions(
2741        ctx,
2742        &file.extension,
2743        current_package,
2744        2,
2745        &features,
2746        current_package,
2747    )?;
2748    ext.extend(file_ext_tokens);
2749    for id in file_ext_json {
2750        reg.json_ext.push(quote! { #sentinel :: ext :: #id });
2751    }
2752    for id in file_ext_text {
2753        reg.text_ext.push(quote! { #sentinel :: ext :: #id });
2754    }
2755    // File-level extension consts → re-export at package root. `self::` and
2756    // `#[doc(inline)]` for the same reasons as the view re-exports above.
2757    for ext_field in &file.extension {
2758        let const_ident = extension::extension_const_ident(ext_field.name.as_deref().unwrap_or(""));
2759        root_reexports.push(message::ReexportCandidate {
2760            name: const_ident.to_string(),
2761            tokens: quote! {
2762                #[doc(inline)]
2763                pub use self :: #sentinel :: ext :: #const_ident;
2764            },
2765        });
2766    }
2767
2768    Ok(ProtoContent {
2769        stem: proto_path_to_stem(file.name.as_deref().unwrap_or("")),
2770        owned,
2771        view,
2772        lazy_view,
2773        oneof,
2774        view_oneof,
2775        ext,
2776        root_reexports,
2777    })
2778}
2779
2780/// Per-section token streams for one package, ready for the stitcher.
2781///
2782/// In per-file mode each section holds `include!("<stem>...rs")` calls; in
2783/// `file_per_package` mode each holds the actual generated items.
2784#[derive(Default)]
2785struct PackageSections {
2786    owned: Vec<TokenStream>,
2787    view: Vec<TokenStream>,
2788    lazy_view: Vec<TokenStream>,
2789    oneof: Vec<TokenStream>,
2790    view_oneof: Vec<TokenStream>,
2791    ext: Vec<TokenStream>,
2792}
2793
2794impl PackageSections {
2795    /// Append one proto file's generated items in-line.
2796    ///
2797    /// Empty streams are skipped so each section's emptiness reflects
2798    /// "the package has no content of this kind" — symmetric with the
2799    /// per-file branch that filters at file-emission time.
2800    fn push_inline(&mut self, pc: ProtoContent) {
2801        let push_if_nonempty = |dst: &mut Vec<TokenStream>, ts: TokenStream| {
2802            if !ts.is_empty() {
2803                dst.push(ts);
2804            }
2805        };
2806        push_if_nonempty(&mut self.owned, pc.owned);
2807        push_if_nonempty(&mut self.view, pc.view);
2808        push_if_nonempty(&mut self.lazy_view, pc.lazy_view);
2809        push_if_nonempty(&mut self.oneof, pc.oneof);
2810        push_if_nonempty(&mut self.view_oneof, pc.view_oneof);
2811        push_if_nonempty(&mut self.ext, pc.ext);
2812    }
2813}
2814
2815/// Generate all output files for one proto package: up to five content
2816/// files per `.proto` (empty ancillary kinds are skipped) plus one
2817/// `<pkg>.mod.rs` stitcher, or a single `<pkg>.rs` when
2818/// [`CodeGenConfig::file_per_package`] is set.
2819fn generate_package(
2820    ctx: &context::CodeGenContext,
2821    current_package: &str,
2822    files: &[&FileDescriptorProto],
2823    fds_bytes: &[u8],
2824    // Deduped `ProtoElemJson` / `ReflectElement` impls for custom repeated
2825    // element types, collected generation-wide and emitted into exactly one
2826    // package's `__buffa` module (empty for every package but the first).
2827    custom_elem_impls: &TokenStream,
2828    out: &mut Vec<GeneratedFile>,
2829) -> Result<(), CodeGenError> {
2830    // Registry paths are package-root-relative; `register_types` lives at
2831    // `__buffa::register_types` (one level deep), so each path gets a
2832    // single `super::` prefix when emitted into the fn body.
2833    let mut reg = message::RegistryPaths::default();
2834    let mut root_reexports: Vec<message::ReexportCandidate> = Vec::new();
2835
2836    // Idiomatic imports: dry-run the package's generation once with the
2837    // registry collecting, so the set of package-root path references is
2838    // known — by construction, exactly the set the real pass will emit —
2839    // then assign short names and generate for real with the registry
2840    // resolving. Generation is deterministic, so the two passes see the
2841    // same references; assignment sorts the collected set, so the result
2842    // is also stable under `.proto` file reordering. The dry run's other
2843    // outputs (tokens, registry paths, re-export candidates, warnings) are
2844    // discarded; only the candidate *names* feed the occupied set, since a
2845    // surviving re-export occupies a root name a `use` must not claim.
2846    if ctx.config.idiomatic_imports && ctx.config.file_per_package {
2847        ctx.imports_begin_collecting();
2848        let warn_mark = ctx.warnings_len();
2849        let mut scratch_reg = message::RegistryPaths::default();
2850        let mut occupied = root_occupied_names(ctx, files);
2851        for file in files {
2852            let pc = generate_proto_content(ctx, current_package, file, &mut scratch_reg)?;
2853            occupied.extend(pc.root_reexports.into_iter().map(|c| c.name));
2854        }
2855        ctx.truncate_warnings(warn_mark);
2856        occupied.insert("register_types".to_string());
2857        // The reflect re-export names (`descriptor_pool`,
2858        // `FILE_DESCRIPTOR_SET_BYTES`) are reserved inside
2859        // `root_occupied_names` itself.
2860        let collected = ctx.imports_take_collected();
2861        ctx.imports_set_resolving(imports::RootImports::assign(&collected, &occupied));
2862    }
2863
2864    let sections = if ctx.config.file_per_package {
2865        let mut sections = PackageSections::default();
2866        for file in files {
2867            let mut pc = generate_proto_content(ctx, current_package, file, &mut reg)?;
2868            root_reexports.append(&mut pc.root_reexports);
2869            sections.push_inline(pc);
2870        }
2871        sections
2872    } else {
2873        let mut sections = PackageSections::default();
2874        for file in files {
2875            let mut pc = generate_proto_content(ctx, current_package, file, &mut reg)?;
2876            root_reexports.append(&mut pc.root_reexports);
2877            let source = file.name.as_deref().unwrap_or("");
2878            let stem = pc.stem;
2879
2880            // Empty ancillary token streams are skipped — neither the
2881            // content file nor the stitcher's `include!` is emitted.
2882            let emit = |suffix: &str,
2883                        kind: GeneratedFileKind,
2884                        tokens: TokenStream,
2885                        section: &mut Vec<TokenStream>,
2886                        out: &mut Vec<GeneratedFile>|
2887             -> Result<(), CodeGenError> {
2888                if tokens.is_empty() {
2889                    return Ok(());
2890                }
2891                let name = format!("{stem}{suffix}.rs");
2892                section.push(quote! { include!(#name); });
2893                out.push(GeneratedFile {
2894                    name,
2895                    package: current_package.to_string(),
2896                    kind,
2897                    content: format_tokens(tokens, source)?,
2898                });
2899                Ok(())
2900            };
2901            emit(
2902                "",
2903                GeneratedFileKind::Owned,
2904                pc.owned,
2905                &mut sections.owned,
2906                out,
2907            )?;
2908            emit(
2909                ".__view",
2910                GeneratedFileKind::View,
2911                pc.view,
2912                &mut sections.view,
2913                out,
2914            )?;
2915            emit(
2916                ".__lazy_view",
2917                GeneratedFileKind::LazyView,
2918                pc.lazy_view,
2919                &mut sections.lazy_view,
2920                out,
2921            )?;
2922            emit(
2923                ".__oneof",
2924                GeneratedFileKind::Oneof,
2925                pc.oneof,
2926                &mut sections.oneof,
2927                out,
2928            )?;
2929            emit(
2930                ".__view_oneof",
2931                GeneratedFileKind::ViewOneof,
2932                pc.view_oneof,
2933                &mut sections.view_oneof,
2934                out,
2935            )?;
2936            emit(
2937                ".__ext",
2938                GeneratedFileKind::Ext,
2939                pc.ext,
2940                &mut sections.ext,
2941                out,
2942            )?;
2943        }
2944        sections
2945    };
2946
2947    let reexport_block = surviving_root_reexports(ctx, files, &reg, root_reexports);
2948
2949    out.push(GeneratedFile {
2950        name: if ctx.config.file_per_package {
2951            package_to_filename(current_package)
2952        } else {
2953            package_to_mod_filename(current_package)
2954        },
2955        package: current_package.to_string(),
2956        kind: GeneratedFileKind::PackageMod,
2957        content: generate_package_mod(
2958            ctx,
2959            &sections,
2960            &reg,
2961            &reexport_block,
2962            fds_bytes,
2963            custom_elem_impls,
2964        )?,
2965    });
2966
2967    // Drop the import registry so its bindings can't leak into the next
2968    // package's generation.
2969    ctx.imports_reset();
2970
2971    Ok(())
2972}
2973
2974/// Names occupied at a package's root by real items: top-level messages,
2975/// enums, message nested-types modules (deconflicted name, #135), and the
2976/// `__buffa` sentinel itself.
2977///
2978/// The package root is shared across every `.proto` file in the package, so
2979/// the set is built from *all* of them. File-level extension consts live in
2980/// `__buffa::ext::`, not at the root, so they are re-export *candidates*
2981/// (added by `generate_proto_content`) rather than occupants. Used both to
2982/// filter root re-exports and as the base reserved set for
2983/// `idiomatic_imports` short-name assignment.
2984fn root_occupied_names(
2985    ctx: &context::CodeGenContext,
2986    files: &[&FileDescriptorProto],
2987) -> std::collections::BTreeSet<String> {
2988    let mut occupied = std::collections::BTreeSet::new();
2989    occupied.insert(context::SENTINEL_MOD.to_string());
2990    for file in files {
2991        let package = file.package.as_deref().unwrap_or("");
2992        for m in &file.message_type {
2993            let name = m.name.as_deref().unwrap_or("");
2994            // The declared struct name carries the configured prefix; the
2995            // module name stays proto-derived.
2996            occupied.insert(ctx.config.prefixed_type_name(name));
2997            // The actual module name (deconflicted from sub-packages, #135).
2998            occupied.insert(ctx.nested_module_name(package, name));
2999        }
3000        for e in &file.enum_type {
3001            occupied.insert(
3002                ctx.config
3003                    .prefixed_type_name(e.name.as_deref().unwrap_or("")),
3004            );
3005        }
3006    }
3007    // The reflect surface is re-exported at the package root directly by
3008    // `generate_package_mod` (not via a `ReexportCandidate`), so candidates
3009    // that could share its names — an extension const named
3010    // `file_descriptor_set_bytes` becomes `FILE_DESCRIPTOR_SET_BYTES` —
3011    // must be filtered against it here or the two `pub use`s collide
3012    // (E0252) in the generated package root.
3013    if ctx.config.generate_reflection {
3014        occupied.insert("descriptor_pool".to_string());
3015        occupied.insert("FILE_DESCRIPTOR_SET_BYTES".to_string());
3016    }
3017    occupied
3018}
3019
3020/// Filter the candidate package-root re-exports against the package's
3021/// existing root namespace and against each other, returning the surviving
3022/// `pub use` lines.
3023///
3024/// The package root is shared across every `.proto` file in the package, so
3025/// the occupied-name set must be built from *all* of them — a top-level
3026/// message named `FooView` declared in `a.proto` would shadow `Foo`'s view
3027/// re-export from `b.proto`.
3028fn surviving_root_reexports(
3029    ctx: &context::CodeGenContext,
3030    files: &[&FileDescriptorProto],
3031    reg: &message::RegistryPaths,
3032    mut candidates: Vec<message::ReexportCandidate>,
3033) -> TokenStream {
3034    use crate::idents::make_field_ident;
3035
3036    let occupied = root_occupied_names(ctx, files);
3037
3038    // `register_types`, when emitted, lives at `__buffa::register_types`.
3039    // `self::` and `#[doc(inline)]` for the same reasons as the view
3040    // re-exports above. Same `any(json, text)` gate as the fn itself.
3041    if ctx.config.emit_register_fn && !reg.is_empty() {
3042        let sentinel = make_field_ident(context::SENTINEL_MOD);
3043        let json_or_text = ctx.config.feature_gates().json_or_text();
3044        candidates.push(message::ReexportCandidate {
3045            name: "register_types".to_string(),
3046            tokens: feature_gates::cfg_block_any(
3047                quote! {
3048                    #[doc(inline)]
3049                    pub use self :: #sentinel :: register_types;
3050                },
3051                &json_or_text,
3052            ),
3053        });
3054    }
3055
3056    message::emit_surviving_reexports(candidates, &occupied)
3057}
3058
3059/// Render the per-package stitcher: owned items at root plus the
3060/// `__buffa::{view,oneof,ext,...}` module wrappers, followed by the
3061/// surviving package-root `pub use` re-exports.
3062fn generate_package_mod(
3063    ctx: &context::CodeGenContext,
3064    sections: &PackageSections,
3065    reg: &message::RegistryPaths,
3066    root_reexports: &TokenStream,
3067    fds_bytes: &[u8],
3068    custom_elem_impls: &TokenStream,
3069) -> Result<String, CodeGenError> {
3070    use crate::idents::make_field_ident;
3071
3072    let owned = &sections.owned;
3073    let view = &sections.view;
3074    let lazy_view = &sections.lazy_view;
3075    let view_oneof = &sections.view_oneof;
3076    let oneof = &sections.oneof;
3077    let ext = &sections.ext;
3078
3079    // Each ancillary module is emitted only when its section has
3080    // content. The natural-path re-exports outside `__buffa` target
3081    // these modules — they are emitted only when their target items
3082    // exist, so the conditions align and re-exports never reference
3083    // a missing module.
3084    let view_oneof_mod = if !view_oneof.is_empty() {
3085        quote! {
3086            pub mod oneof {
3087                #[allow(unused_imports)]
3088                use super::*;
3089                #(#view_oneof)*
3090            }
3091        }
3092    } else {
3093        TokenStream::new()
3094    };
3095
3096    // `view_oneof` is only populated for messages that have oneofs, and
3097    // every message also contributes to `view`, so `!view.is_empty()` is
3098    // sufficient — `view_oneof` non-empty implies `view` non-empty.
3099    debug_assert!(view_oneof.is_empty() || !view.is_empty());
3100    let view_mod = if ctx.config.generate_views && !view.is_empty() {
3101        feature_gates::cfg_block(
3102            quote! {
3103                pub mod view {
3104                    #[allow(unused_imports)]
3105                    use super::*;
3106                    #(#view)*
3107                    #view_oneof_mod
3108                }
3109            },
3110            ctx.config.feature_gates().views,
3111        )
3112    } else {
3113        TokenStream::new()
3114    };
3115
3116    // `lazy_view` is only populated when `view` is (the lazy family is
3117    // generated per-message alongside the eager view).
3118    debug_assert!(lazy_view.is_empty() || !view.is_empty());
3119    let lazy_view_mod = if !lazy_view.is_empty() {
3120        feature_gates::cfg_block(
3121            quote! {
3122                pub mod lazy_view {
3123                    #[allow(unused_imports)]
3124                    use super::*;
3125                    #(#lazy_view)*
3126                }
3127            },
3128            ctx.config.feature_gates().views,
3129        )
3130    } else {
3131        TokenStream::new()
3132    };
3133
3134    let oneof_mod = if !oneof.is_empty() {
3135        quote! {
3136            pub mod oneof {
3137                #[allow(unused_imports)]
3138                use super::*;
3139                #(#oneof)*
3140            }
3141        }
3142    } else {
3143        TokenStream::new()
3144    };
3145
3146    let ext_mod = if !ext.is_empty() {
3147        quote! {
3148            pub mod ext {
3149                #[allow(unused_imports)]
3150                use super::*;
3151                #(#ext)*
3152            }
3153        }
3154    } else {
3155        TokenStream::new()
3156    };
3157
3158    let register_fn = if ctx.config.emit_register_fn && !reg.is_empty() {
3159        let gates = ctx.config.feature_gates();
3160        // When the gated consts (`__*_JSON_ANY` / `__*_TEXT_ANY`) are
3161        // `#[cfg(feature = "...")]`, each registration statement that
3162        // references them gets the same gate. `#[cfg]` on a statement is
3163        // allowed; the call disappears with the const.
3164        let json_regs = reg
3165            .json_any
3166            .iter()
3167            .map(|p| {
3168                feature_gates::cfg_block(quote! { reg.register_json_any(super::#p); }, gates.json)
3169            })
3170            .chain(reg.json_ext.iter().map(|p| {
3171                feature_gates::cfg_block(quote! { reg.register_json_ext(super::#p); }, gates.json)
3172            }));
3173        let text_regs = reg
3174            .text_any
3175            .iter()
3176            .map(|p| {
3177                feature_gates::cfg_block(quote! { reg.register_text_any(super::#p); }, gates.text)
3178            })
3179            .chain(reg.text_ext.iter().map(|p| {
3180                feature_gates::cfg_block(quote! { reg.register_text_ext(super::#p); }, gates.text)
3181            }));
3182        // When gating, a feature subset may leave one bucket of statements
3183        // cfg'd out while the other survives — `reg` is still used. But if
3184        // `register_types` itself is gated on `any(json, text)` (below),
3185        // the only reachable bodies have at least one statement, so `reg`
3186        // can't be unused. Keep `#[allow(unused_variables)]` defensively
3187        // anyway: it's harmless, and the alternative — proving the
3188        // invariant holds across future statement-shape changes — is
3189        // brittle.
3190        let allow_unused = if ctx.config.gate_impls_on_crate_features {
3191            quote! { #[allow(unused_variables)] }
3192        } else {
3193            quote! {}
3194        };
3195        // The fn is useless without at least one of the gated modes that
3196        // populate it — and `::buffa::type_registry::TypeRegistry` may
3197        // become feature-gated in the runtime in a future release. Gate the
3198        // fn on `any(...)` of whichever modes are active so it disappears
3199        // alongside the last entry.
3200        feature_gates::cfg_block_any(
3201            quote! {
3202                /// Register this package's `Any` type entries and extension entries.
3203                #allow_unused
3204                pub fn register_types(reg: &mut ::buffa::type_registry::TypeRegistry) {
3205                    #(#json_regs)*
3206                    #(#text_regs)*
3207                }
3208            },
3209            &gates.json_or_text(),
3210        )
3211    } else {
3212        TokenStream::new()
3213    };
3214
3215    // Reflection: embed the FileDescriptorSet bytes and a lazy pool
3216    // accessor so per-message `Reflectable` impls have a descriptor pool to
3217    // resolve against. Lives inside `__buffa` so the impls can reach it via
3218    // a relative `__buffa::reflect::descriptor_pool()` path. Package-root
3219    // `pub use`s re-export `descriptor_pool` and `FILE_DESCRIPTOR_SET_BYTES`
3220    // so consumers don't have to route through the reserved `__buffa`
3221    // sentinel.
3222    let (reflect_mod, reflect_reexport) = if ctx.config.generate_reflection {
3223        let gate = ctx.config.feature_gates().reflect;
3224        (
3225            feature_gates::cfg_block(reflect::reflect_pool_module(fds_bytes), gate),
3226            reflect::reflect_reexports(&quote! { __buffa }, gate),
3227        )
3228    } else {
3229        (TokenStream::new(), TokenStream::new())
3230    };
3231
3232    let sentinel = make_field_ident(context::SENTINEL_MOD);
3233    // The whole `pub mod __buffa { ... }` wrapper is itself omitted
3234    // when none of its inner modules or `register_types` exist.
3235    let buffa_mod = if view_mod.is_empty()
3236        && lazy_view_mod.is_empty()
3237        && oneof_mod.is_empty()
3238        && ext_mod.is_empty()
3239        && register_fn.is_empty()
3240        && reflect_mod.is_empty()
3241        && custom_elem_impls.is_empty()
3242    {
3243        TokenStream::new()
3244    } else {
3245        let allow = allow_lints_attr();
3246        quote! {
3247            #allow
3248            pub mod #sentinel {
3249                #[allow(unused_imports)]
3250                use super::*;
3251                #view_mod
3252                #lazy_view_mod
3253                #oneof_mod
3254                #ext_mod
3255                #register_fn
3256                #reflect_mod
3257                #custom_elem_impls
3258            }
3259        }
3260    };
3261
3262    // Idiomatic imports: the `use` block backing the package-root short
3263    // names (empty unless the registry is in its resolution phase). Only
3264    // ever non-empty in file_per_package mode, where this output is the
3265    // whole single-writer package file.
3266    //
3267    // Load-bearing lint coupling: impl bodies still write fully-qualified
3268    // paths (e.g. `::buffa::MessageField<…>`) for types this block also
3269    // imports — exactly what `unused_qualifications` flags. That lint is
3270    // suppressed by the `ALLOW_LINTS` attr the module-tree wrapper carries,
3271    // so generated files must keep their `#[allow]` wrapper when consumed.
3272    let use_block = ctx.imports_use_block();
3273
3274    let tokens = quote! {
3275        #use_block
3276        #(#owned)*
3277        #buffa_mod
3278        #reflect_reexport
3279        #root_reexports
3280    };
3281
3282    format_tokens(tokens, "")
3283}
3284
3285/// Format a token stream into a generated-file string with the standard
3286/// header comment.
3287fn format_tokens(tokens: TokenStream, source: &str) -> Result<String, CodeGenError> {
3288    let syntax_tree =
3289        syn::parse2::<syn::File>(tokens).map_err(|e| CodeGenError::InvalidSyntax(e.to_string()))?;
3290    let formatted = prettyplease::unparse(&syntax_tree);
3291    let source_line = if source.is_empty() {
3292        String::new()
3293    } else {
3294        format!("// source: {source}\n")
3295    };
3296    Ok(format!(
3297        "// @generated by buffa-codegen. DO NOT EDIT.\n{source_line}\n{formatted}"
3298    ))
3299}
3300
3301/// Convert a proto package name to its `.mod.rs` stitcher filename.
3302///
3303/// e.g., `"google.protobuf"` → `"google.protobuf.mod.rs"`. The unnamed
3304/// package uses the [`SENTINEL_MOD`](context::SENTINEL_MOD) name as its
3305/// filename stem — `package __buffa;` is already rejected by
3306/// `validate_file`, so the unnamed-package stitcher cannot
3307/// collide with any real package's.
3308pub fn package_to_mod_filename(package: &str) -> String {
3309    if package.is_empty() {
3310        format!("{}.mod.rs", context::SENTINEL_MOD)
3311    } else {
3312        format!("{package}.mod.rs")
3313    }
3314}
3315
3316/// Returns `true` if `package` is covered by any entry in `excludes`.
3317///
3318/// Intended for packages that `include_imports` pulls into
3319/// `file_to_generate` but that a caller does not want emitted — typically
3320/// option-only imports such as `buf.validate` or `gnostic.openapi.v3`, whose
3321/// types are referenced only from custom options and never appear as message
3322/// fields.
3323///
3324/// An exclusion matches a package exactly, or as a dotted-path prefix on a
3325/// component boundary: `"buf.validate"` covers `buf.validate` and
3326/// `buf.validate.foo`, but not `buf.validatex`. Entries are proto package
3327/// paths without a leading dot (`buf.validate`, not `.buf.validate`); an
3328/// empty entry matches only the unnamed package.
3329///
3330/// Both `protoc-gen-buffa` (which filters `file_to_generate` before codegen)
3331/// and `protoc-gen-buffa-packaging` (which filters the packages it stitches
3332/// into `mod.rs`) route their exclusion through this one predicate, so the
3333/// two plugins are guaranteed to drop exactly the same set — the invariant
3334/// the packaging plugin's "Matching a codegen plugin's output set" note
3335/// depends on.
3336pub fn package_is_excluded(package: &str, excludes: &[String]) -> bool {
3337    excludes.iter().any(|ex| {
3338        package == ex
3339            || (package.len() > ex.len()
3340                && package.starts_with(ex.as_str())
3341                && package.as_bytes()[ex.len()] == b'.')
3342    })
3343}
3344
3345/// Normalize and validate one `exclude_package` option value: trim
3346/// whitespace, strip the optional leading dot, reject an empty result or a
3347/// value with empty components (`buf.validate.`, `buf..validate`) — those
3348/// could never match a real package, so a typo would otherwise be a silent
3349/// no-op.
3350///
3351/// Both protoc plugins parse their `exclude_package` options through this
3352/// one function so their normalization cannot drift — the same reason they
3353/// share [`package_is_excluded`].
3354///
3355/// # Errors
3356///
3357/// Returns the user-facing message for a malformed value. The error is a
3358/// plain `String` (not [`CodeGenError`]) deliberately: this is plugin
3359/// option-string parsing, and both plugins' parse layers are
3360/// `Result<_, String>` end to end, surfaced verbatim by protoc.
3361pub fn normalize_exclude_package(value: &str) -> Result<String, String> {
3362    let pkg = value.trim();
3363    let pkg = pkg.strip_prefix('.').unwrap_or(pkg);
3364    if pkg.is_empty() || pkg.split('.').any(str::is_empty) {
3365        return Err(
3366            "'exclude_package' requires a non-empty proto package with no \
3367             empty components, e.g. exclude_package=.buf.validate"
3368                .to_string(),
3369        );
3370    }
3371    Ok(pkg.to_string())
3372}
3373
3374#[cfg(test)]
3375mod package_exclusion_tests {
3376    use super::package_is_excluded;
3377
3378    fn ex(list: &[&str]) -> Vec<String> {
3379        list.iter().map(|s| s.to_string()).collect()
3380    }
3381
3382    #[test]
3383    fn exact_match_is_excluded() {
3384        assert!(package_is_excluded("buf.validate", &ex(&["buf.validate"])));
3385    }
3386
3387    #[test]
3388    fn subpackage_matches_on_component_boundary() {
3389        assert!(package_is_excluded("gnostic.openapi.v3", &ex(&["gnostic"])));
3390        assert!(package_is_excluded(
3391            "buf.validate.priv",
3392            &ex(&["buf.validate"])
3393        ));
3394    }
3395
3396    #[test]
3397    fn prefix_without_boundary_does_not_match() {
3398        assert!(!package_is_excluded(
3399            "buf.validatex",
3400            &ex(&["buf.validate"])
3401        ));
3402        assert!(!package_is_excluded("gnostics", &ex(&["gnostic"])));
3403    }
3404
3405    #[test]
3406    fn unrelated_package_is_kept() {
3407        assert!(!package_is_excluded(
3408            "example.user.v1",
3409            &ex(&["buf.validate", "gnostic"])
3410        ));
3411    }
3412
3413    #[test]
3414    fn empty_exclude_list_keeps_everything() {
3415        assert!(!package_is_excluded("buf.validate", &ex(&[])));
3416    }
3417
3418    // An empty exclude entry is unreachable through either plugin
3419    // (`normalize_exclude_package` rejects it); this pins the raw
3420    // predicate's documented behavior for direct callers.
3421    #[test]
3422    fn empty_entry_matches_only_the_unnamed_package() {
3423        assert!(package_is_excluded("", &ex(&[""])));
3424        assert!(!package_is_excluded("foo", &ex(&[""])));
3425    }
3426
3427    #[test]
3428    fn normalize_strips_dot_and_rejects_malformed() {
3429        use super::normalize_exclude_package;
3430        assert_eq!(
3431            normalize_exclude_package(".buf.validate").as_deref(),
3432            Ok("buf.validate")
3433        );
3434        assert_eq!(
3435            normalize_exclude_package("gnostic").as_deref(),
3436            Ok("gnostic")
3437        );
3438        assert!(normalize_exclude_package("").is_err());
3439        assert!(normalize_exclude_package(".").is_err());
3440        assert!(normalize_exclude_package("  ").is_err());
3441        // Entries that could never match a real package are rejected, not
3442        // silently accepted as no-ops.
3443        assert!(normalize_exclude_package("buf.validate.").is_err());
3444        assert!(normalize_exclude_package("buf..validate").is_err());
3445    }
3446}
3447
3448/// Convert a proto package name to its [`file_per_package`] output filename.
3449///
3450/// e.g., `"google.protobuf"` → `"google.protobuf.rs"`. The unnamed
3451/// package uses [`SENTINEL_MOD`](context::SENTINEL_MOD) — same
3452/// collision-avoidance as [`package_to_mod_filename`].
3453///
3454/// [`file_per_package`]: CodeGenConfig::file_per_package
3455pub fn package_to_filename(package: &str) -> String {
3456    if package.is_empty() {
3457        format!("{}.rs", context::SENTINEL_MOD)
3458    } else {
3459        format!("{package}.rs")
3460    }
3461}
3462
3463/// Convert a `.proto` file path to its content-file stem.
3464///
3465/// e.g., `"google/protobuf/timestamp.proto"` → `"google.protobuf.timestamp"`.
3466/// Content files append `""`, `".__view"`, `".__oneof"`,
3467/// `".__view_oneof"`, or `".__ext"` plus `".rs"` — emitted only for
3468/// kinds with non-empty content.
3469pub fn proto_path_to_stem(proto_path: &str) -> String {
3470    let without_ext = proto_path.strip_suffix(".proto").unwrap_or(proto_path);
3471    without_ext.replace('/', ".")
3472}
3473
3474/// Merge downstream [`Companion`](GeneratedFileKind::Companion) files into
3475/// the per-package stitcher produced by [`generate`].
3476///
3477/// For each companion file this function locates the
3478/// [`PackageMod`](GeneratedFileKind::PackageMod) entry in `files` with a
3479/// matching package and appends `include!("<name>");` at file scope after
3480/// buffa's own output — at package root, alongside the owned message types,
3481/// not under `__buffa::`. The companion files themselves are appended to
3482/// `files` so that build integrations can write everything to disk in one
3483/// pass.
3484///
3485/// **Call this once per build**; it does not deduplicate, so a second call
3486/// with the same companions emits a second `include!` for each, which fails
3487/// to compile downstream with a duplicate-definition error.
3488///
3489/// `name` must be a bare-sibling filename — the same convention buffa uses
3490/// for its own `include!` calls, so it resolves relative to the stitcher
3491/// without any `OUT_DIR` prefix. Names must not contain `"`, `\`, `/`, or
3492/// newlines (the function `debug_assert!`s this in debug builds), and must
3493/// not collide with any of buffa's own generated filenames for the same
3494/// package (`<stem>.rs`, `<stem>.__view.rs`, etc.) — pick an unused suffix
3495/// such as `<stem>.__myplugin.rs`.
3496///
3497/// Companion files with no matching `PackageMod` (e.g. for a package buffa
3498/// did not generate any output for) are still appended to `files` but no
3499/// `include!` is emitted; the caller is responsible for wiring them up. If
3500/// you don't expect orphans, check that every companion's `package` appears
3501/// in `files` as a `PackageMod` after calling.
3502pub fn apply_companions(files: &mut Vec<GeneratedFile>, companions: Vec<GeneratedFile>) {
3503    for comp in &companions {
3504        debug_assert!(
3505            !comp.name.contains(['"', '\\', '/', '\n']),
3506            "companion file name {:?} contains a character that would break \
3507             the generated include!() literal or its bare-sibling resolution",
3508            comp.name
3509        );
3510        if let Some(pkg_mod) = files
3511            .iter_mut()
3512            .find(|f| f.kind == GeneratedFileKind::PackageMod && f.package == comp.package)
3513        {
3514            pkg_mod
3515                .content
3516                .push_str(&format!("include!(\"{}\");\n", comp.name));
3517        }
3518    }
3519    files.extend(companions);
3520}
3521
3522/// Code generation error.
3523#[derive(Debug, Clone, thiserror::Error)]
3524#[non_exhaustive]
3525pub enum CodeGenError {
3526    /// A required field was absent in a descriptor.
3527    ///
3528    /// The `&'static str` names the missing field for diagnostics.
3529    #[error("missing required descriptor field: {0}")]
3530    MissingField(&'static str),
3531    /// A resolved type path string could not be parsed as a Rust type.
3532    #[error("invalid Rust type path: '{0}'")]
3533    InvalidTypePath(String),
3534    /// A `box_type_custom` pointer template did not contain the `*` placeholder.
3535    ///
3536    /// The custom pointer wraps the message type, so the template must mark where
3537    /// it goes with `*`, e.g. `"::smallbox::SmallBox<*, smallbox::space::S4>"`.
3538    #[error("box_type template must contain a `*` placeholder for the message type: '{0}'")]
3539    MissingWildcard(String),
3540    /// A `repeated_type_custom` collection template did not contain the `*`
3541    /// element placeholder.
3542    ///
3543    /// Unlike the scalar `string_type_custom` / `bytes_type_custom` knobs (which
3544    /// take a complete type path), a collection template wraps the element type
3545    /// and must mark where it goes with `*`, e.g. `"::my_crate::SmallList<*>"`.
3546    #[error("repeated_type template must contain a `*` element placeholder: '{0}'")]
3547    MissingListPlaceholder(String),
3548    /// The accumulated `TokenStream` failed to parse as valid Rust syntax.
3549    #[error("generated code failed to parse as Rust: {0}")]
3550    InvalidSyntax(String),
3551    /// A requested file was not present in the descriptor set.
3552    #[error("file_to_generate '{0}' not found in descriptor set")]
3553    FileNotFound(String),
3554    /// Unexpected descriptor state (e.g. a map entry or oneof that cannot be
3555    /// resolved to a known descriptor field).
3556    #[error("codegen error: {0}")]
3557    Other(String),
3558    /// A proto field name uses the `__buffa_` reserved prefix, which would
3559    /// conflict with buffa's internal generated fields.
3560    #[error(
3561        "reserved field name '{field_name}' in message '{message_name}': \
3562             proto field names starting with '__buffa_' conflict with buffa's \
3563             internal fields"
3564    )]
3565    ReservedFieldName {
3566        message_name: String,
3567        field_name: String,
3568    },
3569    /// Two sibling messages produce the same Rust module name after
3570    /// snake_case conversion (e.g., `HTTPRequest` and `HttpRequest` both
3571    /// become `pub mod http_request`).
3572    #[error(
3573        "module name conflict in '{scope}': messages '{name_a}' and '{name_b}' \
3574         both produce module '{module_name}'"
3575    )]
3576    ModuleNameConflict {
3577        scope: String,
3578        name_a: String,
3579        name_b: String,
3580        module_name: String,
3581    },
3582    /// A proto package segment, message name, or file-level enum name
3583    /// would emit a Rust item matching the reserved sentinel `__buffa`.
3584    ///
3585    /// This is the only name buffa reserves in user namespace. Resolve by
3586    /// renaming the proto element.
3587    #[error(
3588        "reserved name '{name}' at {location}: this name is reserved for \
3589         buffa's generated ancillary types (views, oneof enums, \
3590         extensions). Rename the proto element."
3591    )]
3592    ReservedModuleName { name: String, location: String },
3593    /// The input contains a message with `option message_set_wire_format = true`
3594    /// but [`CodeGenConfig::allow_message_set`] was not set.
3595    #[error(
3596        "message '{message_name}' uses `option message_set_wire_format = true` \
3597         but CodeGenConfig::allow_message_set is false; MessageSet is a legacy \
3598         wire format — set allow_message_set(true) if this is intentional"
3599    )]
3600    MessageSetNotSupported { message_name: String },
3601    /// A custom attribute string configured via [`CodeGenConfig::type_attributes`],
3602    /// [`CodeGenConfig::field_attributes`], [`CodeGenConfig::message_attributes`],
3603    /// [`CodeGenConfig::enum_attributes`], or [`CodeGenConfig::oneof_attributes`]
3604    /// could not be parsed as a Rust attribute.
3605    #[error(
3606        "invalid custom attribute for path '{path}': '{attribute}' is not a valid \
3607         Rust attribute ({detail})"
3608    )]
3609    InvalidCustomAttribute {
3610        path: String,
3611        attribute: String,
3612        detail: String,
3613    },
3614    /// [`CodeGenConfig::type_name_prefix`] is not PascalCase
3615    /// (`[A-Z][A-Za-z0-9]*`), so prepending it to a type name would produce
3616    /// an invalid or unconventionally-cased Rust identifier.
3617    #[error(
3618        "invalid type_name_prefix '{prefix}': must be empty or PascalCase \
3619         (start with an ASCII uppercase letter, followed by ASCII letters \
3620         and digits only)"
3621    )]
3622    InvalidTypeNamePrefix { prefix: String },
3623}
3624
3625#[cfg(test)]
3626mod tests;