pub struct Config { /* private fields */ }Expand description
Builder for configuring and running protobuf compilation.
Implementations§
Source§impl Config
impl Config
Sourcepub fn includes(self, includes: &[impl AsRef<Path>]) -> Self
pub fn includes(self, includes: &[impl AsRef<Path>]) -> Self
Add include directories for protoc to search for imports.
Sourcepub fn out_dir(self, dir: impl Into<PathBuf>) -> Self
pub fn out_dir(self, dir: impl Into<PathBuf>) -> Self
Set the output directory for generated files.
Defaults to $OUT_DIR if not set.
Sourcepub fn generate_views(self, enabled: bool) -> Self
pub fn generate_views(self, enabled: bool) -> Self
Enable or disable view type generation (default: true).
Sourcepub fn lazy_views(self, enabled: bool) -> Self
pub fn lazy_views(self, enabled: bool) -> Self
Additionally generate the lazy view family (FooLazyView<'a>)
alongside the unchanged eager views (default: false).
Lazy views decode in a single non-recursive pass, recording nested and
repeated message fields as undecoded byte ranges that decode on access
via fallible, by-value accessors (.get() / iteration) — untouched
sub-trees cost nothing. Validation of deferred bytes happens on
access (and in the fallible to_owned_message), not at decode.
Groups, oneof message variants, and map message values stay eager;
lazy views have no ReflectMessage/OwnedView/text surface. Eager
codegen output is byte-identical with or without the flag. Requires
generate_views. See
CodeGenConfig::lazy_views for full semantics.
Sourcepub fn generate_json(self, enabled: bool) -> Self
pub fn generate_json(self, enabled: bool) -> Self
Enable or disable serde JSON generation (default: false).
When enabled:
-
Generated message structs get
Serialize/Deserializederives. -
Generated enum types get
Serialize/Deserializederives. -
Generated view types (when
generate_viewsis also enabled) get a manualimpl Serializefor zero-copy JSON serialization, soserde_json::to_string(&view)works directly:ⓘlet view = MyMsgView::decode_view(&bytes)?; let json = serde_json::to_string(&view)?;
The downstream crate must depend on serde and enable the buffa/json
feature for the runtime helpers. When views are enabled, the crate must
also enable buffa-types/json so the well-known type views implement
Serialize; without it, references to e.g. TimestampView<'_> in the
generated Serialize impl will fail with
the trait bound 'TimestampView<'_>: Serialize' is not satisfied.
Limitations of the view Serialize impl:
- Extension fields are not included in view JSON output; serialize the
owned form (
view.to_owned_message()) to include extensions. - The impl uses
serialize_map(None)(unknown length) because the number of emitted fields depends on default-omission rules. Most self-describing serializers (notablyserde_json) accept this, but length-prefixed formats (e.g.bincode,postcard) will return a runtime error. The owned types’ derivedSerializedoes not have this restriction.
Sourcepub fn generate_text(self, enabled: bool) -> Self
pub fn generate_text(self, enabled: bool) -> Self
Enable or disable impl buffa::text::TextFormat on generated message
structs (default: false).
When enabled, the downstream crate must enable the buffa/text
feature for the runtime textproto encoder/decoder.
Sourcepub fn generate_arbitrary(self, enabled: bool) -> Self
pub fn generate_arbitrary(self, enabled: bool) -> Self
Enable or disable #[derive(arbitrary::Arbitrary)] on generated
types (default: false).
The derive is gated behind #[cfg_attr(feature = "arbitrary", ...)]
so the downstream crate compiles with or without the feature enabled.
Your crate’s Cargo feature must be named exactly "arbitrary" —
the generated cfg_attr uses that literal string and cannot be
customised — and it must forward to buffa/arbitrary:
[features]
arbitrary = ["dep:arbitrary", "buffa/arbitrary"]Forgetting "buffa/arbitrary" produces a confusing
cannot find function 'arbitrary_bytes' in module '__private' error
in generated code when use_bytes_type or
use_bytes_type_in is also enabled,
because the helper that backs #[arbitrary(with = ...)] for
bytes::Bytes fields lives in buffa under that feature gate.
Sourcepub fn gate_impls_on_crate_features(self, enabled: bool) -> Self
pub fn gate_impls_on_crate_features(self, enabled: bool) -> Self
Wrap generated impls in #[cfg(feature = "...")] instead of
emitting them unconditionally (default: false).
When enabled, the impls controlled by generate_json,
generate_views, and generate_text are wrapped in
#[cfg(feature = "json" | "views" | "text")] (or
#[cfg_attr(feature = ..., ...)] for derives and field attributes)
rather than emitted unconditionally. The crate consuming the
generated code must define matching Cargo features that enable the
corresponding runtime support:
[features]
json = ["buffa/json", "dep:serde", "dep:serde_json"]
views = []
text = ["buffa/text"]The generate_* flags still control whether an impl kind is
emitted at all — this flag only controls whether it is cfg-gated.
generate_arbitrary is always cfg_attr-gated on
feature = "arbitrary" regardless of this flag, because arbitrary
is an optional dependency by design.
Reach for this when generated code is the public interface of a
library crate consumed by downstream projects with different
feature needs — exactly the shape of buffa-descriptor and
buffa-types, which ship every impl while letting the codegen
toolchain (buffa-codegen/buffa-build/protoc-gen-buffa) depend
on them with default-features = false and stay free of
serde/serde_json/base64. Most consumers of buffa-build are
not in this position: a build.rs that decides at build-script
time whether to generate JSON wants impl Serialize to just exist.
Default false.
Sourcepub fn json_feature_name(self, name: impl Into<String>) -> Self
pub fn json_feature_name(self, name: impl Into<String>) -> Self
Set the crate feature name the gated JSON impls are conditioned on
(default: "json").
Only meaningful together with
gate_impls_on_crate_features;
inert otherwise. Use when the consuming crate gates its JSON support
behind a differently-named feature:
[features]
serde = ["buffa/json", "dep:serde", "dep:serde_json"]buffa_build::Config::new()
.generate_json(true)
.gate_impls_on_crate_features(true)
.json_feature_name("serde")The name is emitted verbatim into #[cfg(feature = "...")]
attributes and must be a valid Cargo feature name declared in the
consuming crate’s [features] table. A misspelled or undeclared
name fails open: the #[cfg] is permanently false, so the gated
impls silently compile away (on Rust ≥ 1.80 an undeclared name at
least triggers the unexpected_cfgs warning). A name that is not a
valid Cargo feature name at all (empty, or containing characters
outside alphanumerics and _/-/+/.) makes compile
fail with an error when the gate is active.
Sourcepub fn views_feature_name(self, name: impl Into<String>) -> Self
pub fn views_feature_name(self, name: impl Into<String>) -> Self
Set the crate feature name the gated view impls are conditioned on
(default: "views").
Only meaningful together with
gate_impls_on_crate_features;
inert otherwise. See json_feature_name.
Sourcepub fn text_feature_name(self, name: impl Into<String>) -> Self
pub fn text_feature_name(self, name: impl Into<String>) -> Self
Set the crate feature name the gated textproto impls are conditioned
on (default: "text").
Only meaningful together with
gate_impls_on_crate_features;
inert otherwise. See json_feature_name.
Sourcepub fn reflect_feature_name(self, name: impl Into<String>) -> Self
pub fn reflect_feature_name(self, name: impl Into<String>) -> Self
Set the crate feature name the gated reflection impls are conditioned
on (default: "reflect").
Only meaningful together with
gate_impls_on_crate_features
(or the experimental, hidden gate_reflect_on_crate_feature, which
gates reflection alone); inert otherwise. See
json_feature_name.
Sourcepub fn type_name_prefix(self, prefix: impl Into<String>) -> Self
pub fn type_name_prefix(self, prefix: impl Into<String>) -> Self
Prepend a prefix to every generated Rust type name (default: none).
With prefix "Rpc", message User {} generates struct RpcUser
(and RpcUserView / RpcUserOwnedView); every cross-reference uses
the prefixed name. Useful in multi-protocol systems where generated
types from different domains would otherwise collide with each other
or with a canonical hand-written model.
Applies to message structs and enum types (top-level and nested).
Module names, oneof enums, extern_path-mapped
types (including well-known types), and the wire/JSON format are
unaffected.
When another crate references these prefixed types via its own
extern_path mapping, the mapped Rust path must
spell out the prefixed name (e.g. ::crate_a::RpcUser) — the proto
name carries no prefix, so the mapping is not derived automatically.
The prefix must be PascalCase ([A-Z][A-Za-z0-9]*) — an ASCII
uppercase letter followed by ASCII letters and digits — so the
prefixed names stay conventionally cased; compile
fails otherwise.
Sourcepub fn generate_with_setters(self, enabled: bool) -> Self
pub fn generate_with_setters(self, enabled: bool) -> Self
Enable or disable with_* builder-style setter methods for
explicit-presence fields (default: true).
Each explicit-presence scalar, bytes, or enum field gets a
pub fn with_<name>(mut self, value: T) -> Self method that wraps the
value in Some(...) and returns self, enabling chained construction
without the Some(...) boilerplate:
let req = MyRequest::default()
.with_name("alice")
.with_timeout_ms(30_000);String, bytes, and enum setters take impl Into<T> (so &str,
b"..." literals, and bare enum variants work directly); other
scalars take T to keep integer-literal inference unambiguous.
Setters are pure inherent methods with no runtime dependency — they
don’t interact with the json/views/text feature gates. Disable
only if you want to keep generated code minimal or have a competing
with_* convention in your own crate.
Sourcepub fn generate_reflection(self, enabled: bool) -> Self
pub fn generate_reflection(self, enabled: bool) -> Self
Enable reflection on generated types (default: off).
generate_reflection(true) selects ReflectMode::VTable — the fast
path: foo.reflect() borrows foo directly (no encode/decode
round-trip), and owned and view types implement ReflectMessage. For
the smaller bridge implementation (reflect() round-trips through a
DynamicMessage), use reflect_mode(ReflectMode::Bridge)
instead. generate_reflection(false) is ReflectMode::Off.
Either mode embeds a lazily-built DescriptorPool (as
FileDescriptorSet bytes) reachable as
your_crate::your_pkg::descriptor_pool().
§Cargo.toml setup
The consuming crate must depend on buffa-descriptor with the
reflect feature and on std:
[dependencies]
buffa = { version = "0.7", features = ["std"] }
buffa-descriptor = { version = "0.7", features = ["reflect", "std"] }When gate_impls_on_crate_features
is also on, the impls are wrapped in #[cfg(feature = "reflect")],
so the consuming crate must declare a forwarding feature:
[features]
reflect = ["buffa-descriptor/reflect"]Without the feature declared, the generated Reflectable impls
silently disappear — cfg(feature = "reflect") is permanently
false in a crate that doesn’t declare it. The first call to
.reflect() fails to compile with “trait Reflectable not
implemented”, which is a misleading diagnostic. Most consumers
should leave gate_impls_on_crate_features off.
Reflecting message-typed fields also requires every crate that field
types resolve to via an extern path — notably buffa-types for
well-known types — to enable its own reflection feature; see
reflect_mode (“Extern-path types”) for the
Cargo.toml requirement and mixed-mode behavior.
§Performance
In the default vtable mode, reflect() borrows self — no round-trip,
no allocation; reflective accessors read fields in place. (Bridge mode
instead pays one encode/decode round-trip plus a heap allocation per
call.) Either way the first call pays a one-time pool build cost.
§Build time and binary size
Each generated package embeds its own copy of the full
FileDescriptorSet (transitive closure). For a single-package
crate this is one copy. For a multi-package codegen run the bytes
duplicate per package — measurable for large proto trees. The
serialization happens once per compile() call (not per package),
so build-time CPU does not scale with package count. Vtable mode also
emits an impl ReflectMessage per type, so it produces more code than
bridge mode.
Sourcepub fn reflect_mode(self, mode: ReflectMode) -> Self
pub fn reflect_mode(self, mode: ReflectMode) -> Self
Select the reflection mode (the fuller form of
generate_reflection).
ReflectMode::Off— no reflection (the default); equivalent togenerate_reflection(false).ReflectMode::Bridge—reflect()round-trips throughDynamicMessage; smaller generated code, slower reflective access.ReflectMode::VTable—impl ReflectMessageon owned and view types, andreflect()borrowsselfwith no round-trip; equivalent togenerate_reflection(true). Does not require view generation — with views off, only the owned impls are emitted.
All non-Off modes require the consuming crate to depend on
buffa-descriptor with its reflect feature and on std. The call
site (foo.reflect().get(fd)) is identical across modes.
§Extern-path types
Reflection on a message reaches into its message-typed fields, so
every crate that field types resolve to via an extern path must have
its own reflection enabled. In particular, well-known types resolve
to buffa-types by default, and its impls are behind a cargo
feature: depend on buffa-types = { ..., features = ["reflect"] }
or the build fails with unsatisfied Reflectable /
ReflectMessage bounds on the WKT.
§Mixed modes
A vtable-mode message may embed owned message types generated in
bridge mode (e.g. a dependency crate that chose the smaller output):
reflective access degrades to an owned DynamicMessage snapshot at
that boundary instead of failing. For a bridge-grade repeated or
map field the snapshot is taken per element on every access, so
reflecting a large mixed-mode collection scales the encode/decode
cost by the element count. The view reflection surface cannot
degrade — every view type embedded in a vtable-mode view must itself
be vtable-grade, and a bridge-grade view field is a compile error.
Sourcepub fn idiomatic_enum_aliases(self, enabled: bool) -> Self
pub fn idiomatic_enum_aliases(self, enabled: bool) -> Self
Enable or disable idiomatic UpperCamelCase enum aliases (matches the
CodeGenConfig default, currently on).
Protobuf enum values are SHOUTY_SNAKE_CASE and stay the definitive Rust
variants. When enabled, codegen additionally emits associated consts
with the enum-name prefix stripped and the name converted to
UpperCamelCase (RULE_LEVEL_HIGH → RuleLevel::High), purely
additively — existing references and Debug output are unchanged.
Aliases are suppressed per enum (with a build warning and a doc note) if
any two values would collide after conversion, so a match is never forced
to mix conventions. See CodeGenConfig::idiomatic_enum_aliases.
Sourcepub fn file_per_package(self, enabled: bool) -> Self
pub fn file_per_package(self, enabled: bool) -> Self
Emit one <dotted.package>.rs file per proto package instead of the
per-proto-file content set plus <pkg>.mod.rs stitcher. Default:
false.
The single file inlines what the stitcher would otherwise include!,
producing the same module structure. Required by
idiomatic_imports. See
CodeGenConfig::file_per_package for caveats about packages that
span multiple directories.
Sourcepub fn idiomatic_imports(self, enabled: bool) -> Self
pub fn idiomatic_imports(self, enabled: bool) -> Self
Experimental. Emit use-backed short type names at the package
root instead of fully-qualified paths, so struct fields read
MessageField<Timestamp> instead of
::buffa::MessageField<::buffa_types::google::protobuf::Timestamp>.
Default: false (output is byte-for-byte identical to previous
releases).
Requires file_per_package — the build
fails otherwise. Short names that would collide with another item at
the package root (or a name referenced bare by sibling emissions)
fall back to parent-module qualification, then to the
fully-qualified path.
Only package-root type declarations are shortened; impl bodies,
nested-message modules, and __buffa internals keep fully-qualified
paths. “Experimental” means the output shape may change between
releases and the option may be renamed or removed outside semver
guarantees. See CodeGenConfig::idiomatic_imports for details.
Sourcepub fn preserve_unknown_fields(self, enabled: bool) -> Self
pub fn preserve_unknown_fields(self, enabled: bool) -> Self
Enable or disable unknown field preservation (default: true).
When enabled (the default), unrecognized fields encountered during decode are stored and re-emitted on encode — essential for proxy / middleware services and round-trip fidelity across schema versions.
Disabling is primarily a memory optimization (24 bytes/message for
the UnknownFields Vec header), not a throughput one. When no unknown
fields appear on the wire — the common case for schema-aligned
services — decode and encode costs are effectively identical in
either mode. Consider disabling for embedded / no_std targets or
large in-memory collections of small messages.
Sourcepub fn strict_utf8_mapping(self, enabled: bool) -> Self
pub fn strict_utf8_mapping(self, enabled: bool) -> Self
Honor features.utf8_validation = NONE by emitting Vec<u8> / &[u8]
for such string fields instead of String / &str (default: false).
When disabled (the default), all string fields map to String and
UTF-8 is validated on decode — stricter than proto2 requires, but
ergonomic and safe.
When enabled, string fields with utf8_validation = NONE become
Vec<u8> / &[u8]. Decode skips validation; the caller chooses
whether to std::str::from_utf8 (checked) or from_utf8_unchecked
(trusted-input fast path). This is the only sound Rust mapping when
strings may actually contain non-UTF-8 bytes.
Note for proto2 users: proto2’s default is utf8_validation = NONE,
so enabling this turns ALL proto2 string fields into Vec<u8>. Use
only for new code or when profiling identifies UTF-8 validation as a
bottleneck (it can be 10%+ of decode CPU for string-heavy messages).
JSON note: fields normalized to bytes serialize as base64 in JSON
(the proto3 JSON encoding for bytes). Keep strict mapping disabled
for fields that need JSON string interop with other implementations.
Interaction with use_bytes_type: when both are enabled,
map<bytes, bytes> values stay Vec<u8> (the bytes-keyed JSON helper
is concrete HashMap<Vec<u8>, Vec<u8>>). All other bytes shapes —
singular / optional / repeated / oneof / map<non-bytes, bytes> —
still become bytes::Bytes. The asymmetry is documented; if you hit
it, see issue #76.
Sourcepub fn allow_message_set(self, enabled: bool) -> Self
pub fn allow_message_set(self, enabled: bool) -> Self
Permit option message_set_wire_format = true on input messages.
MessageSet is a legacy Google-internal wire format. Default: false
(such messages produce a codegen error). Set to true only when
compiling protos that interoperate with old Google-internal services.
Sourcepub fn extern_path(
self,
proto_path: impl Into<String>,
rust_path: impl Into<String>,
) -> Self
pub fn extern_path( self, proto_path: impl Into<String>, rust_path: impl Into<String>, ) -> Self
Declare an external type path mapping.
The matched types reference the specified Rust path instead of being generated. This allows shared proto packages to be compiled once in a dedicated crate and referenced from others.
proto_path is a fully-qualified protobuf path — either a package
(".my.common", mapping every type under it to a Rust module root) or a
single type FQN (".google.protobuf.Timestamp", mapping just that
type, the prost/tonic idiom). The leading dot is optional and is added
automatically. As in prost, the most specific entry wins: an exact type
FQN beats a covering package prefix, which in turn beats a shorter
prefix.
rust_path is where the type(s) are accessible — a module root for a
package mapping (e.g. "::common_protos") or a full type path for a
per-type mapping (e.g. "::pbjson_types::Timestamp"). It must be an
absolute path (starting with :: or crate::); any other value is
emitted into the generated code verbatim and will fail to resolve there.
Nested types inherit an enclosing message’s per-type override:
mapping .my.pkg.Outer to ::ext::Outer resolves .my.pkg.Outer.Inner
to ::ext::outer::Inner — the override’s parent module plus buffa’s
usual snake_case(MessageName) nested-types module (snake case of the
proto message name, regardless of the override’s final segment). This
matches the layout of another buffa-generated crate; for a target crate
laid out differently, add explicit per-type entries for the nested types
as well.
§Limitations
An extern type that is referenced by a generated view must map to
another buffa-generated crate — the view path is composed as
<rust_path_root>::__buffa::view::…, which a non-buffa crate (e.g.
pbjson_types) does not provide. Map per-type to a buffa crate, or
disable views (generate_views(false)), for
such types.
A misconfigured mapping (a typo’d FQN target, a non-absolute
rust_path, or a view-referenced type mapped to a non-buffa crate) is
not diagnosed at generation time; it surfaces as an unresolved-path
error when the generated code is compiled.
§Example
buffa_build::Config::new()
// Whole-package mapping.
.extern_path(".my.common", "::common_protos")
// Per-type mapping (issue #111) — overrides the package prefix for
// just this type.
.extern_path(".google.protobuf.Timestamp", "::common_protos::well_known::Timestamp")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn use_bytes_type_in(self, paths: &[impl AsRef<str>]) -> Self
pub fn use_bytes_type_in(self, paths: &[impl AsRef<str>]) -> Self
Configure bytes fields to use bytes::Bytes instead of Vec<u8>.
Each path is a fully-qualified proto path prefix. Use "." to apply
to all bytes fields, or specify individual field paths like
".my.pkg.MyMessage.data".
Applies uniformly to singular, optional, repeated, oneof, and
map<K, bytes> values — the map case lets view → owned
conversion participate in the to_owned_from_source zero-copy
slice_ref path. One carve-out: an effective map<bytes, bytes> keeps
Vec<u8> values (the JSON helper for that combination is concrete
HashMap<Vec<u8>, Vec<u8>>); every other shape becomes Bytes. A
bytes map key is only reachable when strict_utf8_mapping is enabled
and the map<string, bytes> field carries
[features.utf8_validation = NONE] on its key, which normalizes the
string key to bytes — strict_utf8_mapping alone does not trigger it.
A custom bytes representation
(bytes_type_custom) is honored for
map<K, bytes> values too, the same as the built-in Bytes — but a
custom map value (like a custom repeated element) must be a crate-local
type, since codegen emits its ReflectElement / ProtoElemJson impls
(the orphan rule forbids them for a foreign type).
§Example
buffa_build::Config::new()
.use_bytes_type_in(&["."]) // all bytes fields use Bytes
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn use_bytes_type(self) -> Self
pub fn use_bytes_type(self) -> Self
Use bytes::Bytes for all bytes fields in all messages.
This is a convenience for .use_bytes_type_in(&["."]). Use
use_bytes_type_in with specific proto paths if you only want Bytes
for certain fields. See that method for the path-matching semantics, the
map<K, bytes> rule, and the map<bytes, bytes> carve-out under
strict_utf8_mapping.
Sourcepub fn bytes_type_in(self, repr: BytesRepr, paths: &[impl AsRef<str>]) -> Self
pub fn bytes_type_in(self, repr: BytesRepr, paths: &[impl AsRef<str>]) -> Self
Map bytes fields to a BytesRepr other than Vec<u8> for the given
proto path prefixes. The bytes counterpart to
string_type_in.
Rules accumulate and the last matching rule wins, so call the broad
bytes_type first, then bytes_type_in for
narrower overrides. For BytesRepr::Custom, the downstream crate must
depend on the crate providing the type (buffa does not re-export it).
Only the owned Rust type changes — the wire format is unchanged and view
types still borrow &[u8].
Sourcepub fn bytes_type(self, repr: BytesRepr) -> Self
pub fn bytes_type(self, repr: BytesRepr) -> Self
Map every bytes field in all messages to the given BytesRepr.
Convenience for .bytes_type_in(repr, &["."]); call before any
bytes_type_in overrides (last matching rule
wins).
Sourcepub fn bytes_type_custom_in(self, path: &str, paths: &[impl AsRef<str>]) -> Self
pub fn bytes_type_custom_in(self, path: &str, paths: &[impl AsRef<str>]) -> Self
Map the matching bytes fields to a custom type named by its
fully-qualified Rust path (e.g. "::my_crate::MyBytes"). The type must
satisfy buffa::ProtoBytes, and the downstream crate must depend on the
crate providing it. Shorthand for
bytes_type_in(BytesRepr::Custom(path), paths).
§Limitations
- A foreign custom type used as a
repeatedelement — or amap<K, bytes>value — fails to compile: codegen emitsReflectElement/ProtoElemJsonimpls for it, which the orphan rule forbids for a foreign type. Wrap it in a crate-local newtype for those cases; singular / optional / oneof uses work directly. - A
Customrule does apply tomap<K, bytes>values (honored like the built-inBytesRepr::Bytes); only themap<bytes, bytes>carve-out keepsVec<u8>values. - A
paththat does not parse as a Rust type is reported as a codegen error fromcompile. - A custom bytes type needs no native
arbitrary::Arbitraryimpl (a generic builder handles it undergenerate_arbitrary).
Sourcepub fn bytes_type_custom(self, path: &str) -> Self
pub fn bytes_type_custom(self, path: &str) -> Self
Map every bytes field to the given custom type path. Convenience for
.bytes_type_custom_in(path, &["."]); see it for the limitations
(foreign repeated elements, map values, path parsing).
Sourcepub fn unbox_oneof_in(self, paths: &[impl AsRef<str>]) -> Self
pub fn unbox_oneof_in(self, paths: &[impl AsRef<str>]) -> Self
Store the matching message-typed oneof variants inline instead of
wrapping them in Box<T>.
By default every message/group oneof variant is boxed so that recursive
types compile. For non-recursive variants the Box is pure overhead (an
allocation per construction); this opts the matching variants out.
This affects the owned message enum only — view oneof variants remain
boxed.
Each path is a fully-qualified proto variant path prefix, e.g.
".my.pkg.MyMessage.body.small" for one variant or ".my.pkg" for a
package (same matching as use_bytes_type_in).
A leading dot is added if missing, mirroring
extern_path.
Recursive variants cannot be stored inline (the type would be
unsized). A rule that names a recursive variant exactly is rejected
at codegen time; a broader prefix rule silently keeps recursive
variants boxed and inlines the rest. For example, with
unbox_oneof_in(&[".my.pkg.Node"]), a self-referential
Node.kind.child variant stays boxed while Node’s other message
variants become inline.
Sourcepub fn unbox_oneof(self) -> Self
pub fn unbox_oneof(self) -> Self
Store every non-recursive message-typed oneof variant inline instead of
boxing it. Convenience for .unbox_oneof_in(&["."]); recursive
variants stay boxed.
Sourcepub fn string_type_in(self, repr: StringRepr, paths: &[impl AsRef<str>]) -> Self
pub fn string_type_in(self, repr: StringRepr, paths: &[impl AsRef<str>]) -> Self
Map string fields to a StringRepr other than String for the
given proto path prefixes. The string counterpart to
use_bytes_type_in.
Each path is a fully-qualified proto path prefix (e.g.
".my.pkg.MyMessage.name" for one field, ".my.pkg" for a package).
Rules accumulate and the last matching rule wins. Order therefore
matters: call string_type (the broad default)
first, then string_type_in for narrower overrides — a broad rule
added after a specific one will shadow it.
For StringRepr::Custom, the type must implement buffa::ProtoString,
and the downstream crate must depend on the crate providing it (buffa does
not re-export it). A foreign type cannot implement ProtoString directly
(orphan rule) — point at a local newtype, or the buffa-smolstr crate for
smol_str::SmolStr.
Only the owned Rust type changes: the wire format is unchanged, view
types still borrow &str, and map<_, string> keys and values stay
String.
§Example
buffa_build::Config::new()
.string_type_custom("::buffa_smolstr::SmolStr") // broad default first
.string_type_custom_in("::my_crate::CompactStr", &[".my.pkg.Msg.body"]) // narrow override
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn string_type(self, repr: StringRepr) -> Self
pub fn string_type(self, repr: StringRepr) -> Self
Map every string field in all messages to the given StringRepr.
Convenience for .string_type_in(repr, &["."]). Call this before any
string_type_in overrides, since the last
matching rule wins (a "." rule added later shadows earlier specific
rules). map<_, string> keys and values stay String.
Sourcepub fn string_type_custom_in(
self,
path: &str,
paths: &[impl AsRef<str>],
) -> Self
pub fn string_type_custom_in( self, path: &str, paths: &[impl AsRef<str>], ) -> Self
Map the matching string fields to a custom type that implements
buffa::ProtoString, named by its fully-qualified Rust path (e.g.
"::buffa_smolstr::SmolStr", or a local newtype — a foreign type cannot
implement the trait directly). The downstream crate must depend on the
crate providing it. Shorthand for
string_type_in(StringRepr::Custom(path), paths).
§Limitations
- A foreign custom type used as a
repeatedelement fails to compile: codegen emits aReflectElementimpl for it, which the orphan rule forbids for a foreign type. Wrap it in a crate-local newtype for the repeated case; singular / optional / oneof uses work directly. - JSON of a
repeatedcustom string serializes elements through their nativeserde, so such a type must deriveSerialize/Deserialize(and an external type must enable itsserdefeature). Singular / optional / oneof custom strings use theproto_stringwith-module and need noserdeimpl. - A
paththat does not parse as a Rust type is reported as a codegen error fromcompile. - A custom string type needs no native
arbitrary::Arbitraryimpl (a generic builder handles it undergenerate_arbitrary).
Sourcepub fn string_type_custom(self, path: &str) -> Self
pub fn string_type_custom(self, path: &str) -> Self
Map every string field to the given custom type path. Convenience for
.string_type_custom_in(path, &["."]); see it for the limitations
(foreign repeated elements, the repeated JSON serde requirement,
path parsing).
Sourcepub fn map_type_in(self, repr: MapRepr, paths: &[impl AsRef<str>]) -> Self
pub fn map_type_in(self, repr: MapRepr, paths: &[impl AsRef<str>]) -> Self
Map the matching map fields to a MapRepr other than the default
HashMap. Rules are matched with proto-segment-aware prefix logic; the
last matching rule wins, so add a broad rule first and narrower
overrides after.
Use MapRepr::BTreeMap for the buffa-provided BTreeMap (deterministic
key order, no extra dependency, no consumer code), or
MapRepr::Custom for a crate-local newtype that implements
buffa::map_codec::MapStorage.
Only the owned collection changes: the wire format is unchanged and view types are unaffected.
§Example
buffa_build::Config::new()
.map_type(buffa_build::MapRepr::BTreeMap) // broad default
.map_type_in(buffa_build::MapRepr::HashMap, &[".my.pkg.Msg.cache"]) // narrow override
.compile()
.unwrap();Sourcepub fn map_type(self, repr: MapRepr) -> Self
pub fn map_type(self, repr: MapRepr) -> Self
Map every map field in all messages to the given MapRepr.
Convenience for .map_type_in(repr, &["."]). Call this before any
map_type_in overrides, since the last matching
rule wins.
Sourcepub fn map_type_custom_in(self, path: &str, paths: &[impl AsRef<str>]) -> Self
pub fn map_type_custom_in(self, path: &str, paths: &[impl AsRef<str>]) -> Self
Map the matching map fields to a custom collection implementing
buffa::map_codec::MapStorage, named by its fully-qualified Rust path
(e.g. "::my_crate::OrderedMap"). The path must not include the
<K, V> parameters — they are applied positionally. Shorthand for
map_type_in(MapRepr::Custom(path), paths).
§Limitations
- The path must name a crate-local newtype — a foreign map cannot
implement the buffa-owned reflection / serde traits (orphan rule).
Prefer the built-in
MapRepr::BTreeMapunless you need a specific foreign map. - The newtype must implement
buffa::MapStorageplus the derive /FromIterator/ReflectMap/ serde /arbitrarybounds documented onbuffa::map_codec::MapStorage(the canonical list). JSON andarbitrarywork for every proto map key/value type regardless of the container. - A path that does not parse as a Rust type is reported as a codegen
error from
compile.
Sourcepub fn map_type_custom(self, path: &str) -> Self
pub fn map_type_custom(self, path: &str) -> Self
Map every map field to the given custom collection path. Convenience
for .map_type_custom_in(path, &["."]); see it for the limitations (the
crate-local newtype requirement, the trait bounds, path parsing).
Sourcepub fn box_type_in(self, repr: PointerRepr, paths: &[impl AsRef<str>]) -> Self
pub fn box_type_in(self, repr: PointerRepr, paths: &[impl AsRef<str>]) -> Self
Map the matching message fields to a PointerRepr other than the
default Box. Rules are matched with proto-segment-aware prefix logic;
the last matching rule wins, so add a broad rule first and narrower
overrides after.
Applies to singular (and proto2 optional/required) message fields and to
boxed oneof message/group variants (matched by the variant’s path).
A oneof variant opted into inline storage via unbox_oneof_in
takes precedence and gets no pointer; recursive variants stay boxed and so
accept a custom pointer. Repeated message fields use a collection, not a
pointer. For PointerRepr::Custom, the pointer must implement
buffa::ProtoBox<T> and be a crate-local newtype; the path is a
template with a * placeholder for the message type (e.g.
"::my_crate::SmallBox<*>").
Only the in-memory pointer changes: the wire format is unchanged and view types are unaffected.
Sourcepub fn box_type(self, repr: PointerRepr) -> Self
pub fn box_type(self, repr: PointerRepr) -> Self
Map every message field (and boxed oneof variant) to the given PointerRepr.
Convenience for .box_type_in(repr, &["."]). Call before any
box_type_in overrides, since the last matching rule
wins. An inline pointer inflates each parent struct, so prefer narrow
rules over a blanket default.
Sourcepub fn box_type_custom_in(
self,
template: &str,
paths: &[impl AsRef<str>],
) -> Self
pub fn box_type_custom_in( self, template: &str, paths: &[impl AsRef<str>], ) -> Self
Map the matching singular message fields to a custom pointer implementing
buffa::ProtoBox<T>, named by a Rust type-path template with a *
placeholder for the message type (e.g. "::my_crate::SmallBox<*>").
Shorthand for
box_type_in(PointerRepr::Custom(template), paths).
§Limitations
- The template must contain at least one
*; a template that omits it, or whose substitution does not parse as a Rust type, is reported as a codegen error fromcompile. - The pointer must be exclusively owned (
Rc/Arcare unusable — the decoder needsDerefMut) and a crate-local newtype (a foreign pointer cannot implement the buffa-ownedProtoBox).
Sourcepub fn box_type_custom(self, template: &str) -> Self
pub fn box_type_custom(self, template: &str) -> Self
Map every message field (and boxed oneof variant) to the given custom pointer template.
Convenience for .box_type_custom_in(template, &["."]); see it for the
limitations (the * placeholder, Rc/Arc exclusion, newtype rule).
Sourcepub fn repeated_type_in(
self,
repr: RepeatedRepr,
paths: &[impl AsRef<str>],
) -> Self
pub fn repeated_type_in( self, repr: RepeatedRepr, paths: &[impl AsRef<str>], ) -> Self
Map the matching repeated fields to a RepeatedRepr other than the
default Vec<T>. Rules are matched with proto-segment-aware prefix
logic; the last matching rule wins, so add a broad rule first and
narrower overrides after. Applies only to repeated fields (not map).
For RepeatedRepr::Custom, the collection must implement
buffa::ProtoList<T>. Unlike the scalar string_type_custom /
bytes_type_custom knobs (which take a complete type path), this path
is a template with a * placeholder for the element type, and it must
name a crate-local newtype (a foreign collection cannot implement the
buffa-owned ProtoList).
Only the owned collection changes: the wire format is unchanged and view
types still borrow &[T].
§Example
// `SmallList<T>` is a crate-local newtype over smallvec::SmallVec that
// implements buffa::ProtoList (see the ProtoList docs for the template).
buffa_build::Config::new()
.repeated_type_custom("::my_crate::SmallList<*>") // broad default
.repeated_type_custom_in("::my_crate::SmallList8<*>", &[".my.pkg.Msg.tags"])
.compile()
.unwrap();Sourcepub fn repeated_type(self, repr: RepeatedRepr) -> Self
pub fn repeated_type(self, repr: RepeatedRepr) -> Self
Map every repeated field in all messages to the given
RepeatedRepr. Convenience for .repeated_type_in(repr, &["."]).
Call this before any repeated_type_in
overrides, since the last matching rule wins.
Sourcepub fn repeated_type_custom_in(
self,
template: &str,
paths: &[impl AsRef<str>],
) -> Self
pub fn repeated_type_custom_in( self, template: &str, paths: &[impl AsRef<str>], ) -> Self
Map the matching repeated fields to a custom collection implementing
buffa::ProtoList<T>, named by a Rust type-path template with a *
placeholder for the element type (e.g. "::my_crate::SmallList<*>").
Note the asymmetry with the scalar string_type_custom /
bytes_type_custom knobs: those take a complete path, this takes a
*-template that wraps the element. Shorthand for
repeated_type_in(RepeatedRepr::Custom(template), paths).
§Limitations
- The template must contain at least one
*; a template that omits it, or whose substitution does not parse as a Rust type, is reported as a codegen error fromcompile. - The template must name a crate-local newtype — a foreign collection
cannot implement the buffa-owned
ProtoList(orphan rule). This applies to every build, not just reflection: the generated decode and clear code requireField: ProtoList. - Under reflection / vtable the newtype must also implement
buffa_descriptor’sReflectList(not derivable, but aVec-backed newtype can delegate to the innerVec<T>). Under JSON it must implementserde::Serialize/Deserialize; undergenerate_arbitrary,arbitrary::Arbitrary(derivable on a newtype). Seebuffa::ProtoListfor a worked newtype example.
Sourcepub fn repeated_type_custom(self, template: &str) -> Self
pub fn repeated_type_custom(self, template: &str) -> Self
Map every repeated field to the given custom collection template.
Convenience for .repeated_type_custom_in(template, &["."]); see it for
the limitations (the * placeholder, foreign reflection, the JSON /
arbitrary requirements).
Sourcepub fn type_attribute(
self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self
pub fn type_attribute( self, path: impl Into<String>, attribute: impl Into<String>, ) -> Self
Add a custom attribute to generated types (messages and enums) matching a proto path prefix.
path is a fully-qualified proto path prefix: "." applies to all
types, ".my.pkg" to types in that package, ".my.pkg.MyMessage"
to a specific type. A leading . is auto-prepended if omitted; a
trailing . is trimmed. Prefix matching respects proto-segment
boundaries, so ".my.pk" does not match ".my.pkg.Msg".
attribute is a raw Rust attribute string
(e.g., "#[derive(serde::Serialize)]"). A malformed attribute
produces CodeGenError::InvalidCustomAttribute
at compile time rather than being silently dropped.
Multiple calls accumulate in insertion order — all matching attributes are emitted, and ordering is preserved in generated code.
Also applies to generated oneof enums when path matches
".pkg.Msg.my_oneof" (the oneof’s fully-qualified path).
§Pitfalls
buffa already emits #[derive(Clone, PartialEq)] on messages and
#[derive(Clone, PartialEq, Debug)] on oneofs (oneofs with a
[debug_redact = true] variant get a generated Debug impl instead
of the Debug derive); adding a duplicate derive via
type_attribute(".", "#[derive(Clone)]") produces a compile error in
the generated code.
§Example
buffa_build::Config::new()
.type_attribute(".", "#[derive(serde::Serialize)]")
.type_attribute(".my.pkg.MyEnum", "#[derive(strum::EnumIter)]")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn field_attribute(
self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self
pub fn field_attribute( self, path: impl Into<String>, attribute: impl Into<String>, ) -> Self
Add a custom attribute to generated struct fields matching a proto path prefix.
path is a fully-qualified proto field path (e.g.,
".my.pkg.MyMessage.my_field"). "." applies to all fields. A
leading . is auto-prepended if omitted; a trailing . is trimmed.
Prefix matching respects proto-segment boundaries.
Also applies to oneof variants when path matches
".pkg.Msg.my_oneof.variant_name".
§Example
buffa_build::Config::new()
.field_attribute(".my.pkg.MyMessage.secret_key", "#[serde(skip)]")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn message_attribute(
self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self
pub fn message_attribute( self, path: impl Into<String>, attribute: impl Into<String>, ) -> Self
Add a custom attribute to generated message structs only (not enums,
not oneof enums — those are reached by
enum_attribute and
oneof_attribute respectively) matching a
proto path prefix.
Same path-matching semantics as type_attribute —
leading . auto-prepended, trailing . trimmed, proto-segment-aware
prefix matching, accumulation in insertion order. A malformed attribute
produces a compile-time error. Useful for struct-only attributes like
#[serde(default)].
§Example
buffa_build::Config::new()
.message_attribute(".", "#[serde(default)]")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn enum_attribute(
self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self
pub fn enum_attribute( self, path: impl Into<String>, attribute: impl Into<String>, ) -> Self
Add a custom attribute to generated enum types only (not message
structs, not oneof enums — those are reached by
type_attribute on the oneof’s path or by
oneof_attribute) matching a proto path
prefix.
Same path-matching semantics as type_attribute —
leading . auto-prepended, trailing . trimmed, proto-segment-aware
prefix matching, accumulation in insertion order. A malformed attribute
produces a compile-time error. Useful when you want to inject an
attribute on every enum in a package without also matching the
(often more numerous) messages that share the path prefix — e.g.
#[derive(strum::EnumIter)], which only makes sense on enums.
§Example
buffa_build::Config::new()
.enum_attribute(".my.pkg", "#[derive(strum::EnumIter)]")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn oneof_attribute(
self,
path: impl Into<String>,
attribute: impl Into<String>,
) -> Self
pub fn oneof_attribute( self, path: impl Into<String>, attribute: impl Into<String>, ) -> Self
Add a custom attribute to generated oneof enums only (not message structs, not regular enums) matching a proto path prefix.
Same path-matching semantics as type_attribute:
a leading . is auto-prepended, a trailing . is trimmed, prefixes
match on proto-path segments, and attributes accumulate in insertion
order. The match key is the oneof’s fully-qualified path
(.my.pkg.MyMessage.my_oneof) — the whole-enum path has no variant
segment; to target a single variant’s field, append .variant_name
and use field_attribute instead. A
malformed attribute produces a compile-time error in the generated
code. Useful when a oneof needs a different attribute set than the
surrounding types — for example to keep #[derive(serde::Serialize)]
on messages and oneofs while
enum_attribute gives the regular enums a
different serde derive.
Applies to the owned oneof enum only; the zero-copy view-of-oneof
enum receives no custom attributes (true of the whole attribute
family). For JSON serialization of both owned types and views, use
generate_json(true), which emits canonical
protobuf-JSON impls rather than derived ones.
§Pitfalls
Generated oneof enums already derive Clone, PartialEq, and
Debug (oneofs containing [debug_redact = true] fields replace the
Debug derive with a manual impl). Re-deriving any of these via
oneof_attribute produces a conflicting-implementation compile error
inside the generated code.
§Example
buffa_build::Config::new()
// one specific oneof; ".my.pkg" would match every oneof in the package
.oneof_attribute(".my.pkg.MyMessage.my_oneof", "#[derive(serde::Serialize)]")
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.compile()
.unwrap();Sourcepub fn use_buf(self) -> Self
pub fn use_buf(self) -> Self
Use buf build instead of protoc for descriptor generation.
buf is often easier to install and keep current than protoc
(which many distros pin to old versions). This mode is intended for
the single-crate case: a buf.yaml at the crate root defining
the module layout.
Requires buf on PATH and a buf.yaml at the crate root. The
includes() setting is ignored — buf resolves
imports via its own module configuration.
Each path given to files() must be relative to its
owning module’s directory (the path: value inside buf.yaml), not
the crate root where buf.yaml itself lives. buf strips the module
path when producing FileDescriptorProto.name, so for
modules: [{path: proto}] and a file on disk at
proto/api/v1/service.proto, the descriptor name is
api/v1/service.proto — that is what .files() must contain.
Multiple modules in one buf.yaml work fine; buf enforces that
module-relative names are unique across the workspace.
§Monorepo / multi-module setups
For a workspace-root buf.yaml with many modules, this mode is a
poor fit. Prefer running buf generate with the protoc-gen-buffa
plugin and checking in the generated code, or use
descriptor_set() with the output of
buf build --as-file-descriptor-set -o fds.binpb <module-path>
run as a pre-build step.
§Example
// buf.yaml (at crate root):
// version: v2
// modules:
// - path: proto
//
// build.rs:
buffa_build::Config::new()
.use_buf()
.files(&["api/v1/service.proto"]) // relative to module root
.compile()
.unwrap();Sourcepub fn descriptor_set(self, path: impl Into<PathBuf>) -> Self
pub fn descriptor_set(self, path: impl Into<PathBuf>) -> Self
Use a pre-compiled FileDescriptorSet binary file as input.
Skips invoking protoc or buf entirely. The file must contain a
serialized google.protobuf.FileDescriptorSet (as produced by
protoc --descriptor_set_out or buf build --as-file-descriptor-set).
When using this, .files() specifies which proto files in the
descriptor set to generate code for (matching by proto file name).
Sourcepub fn include_file(self, name: impl Into<String>) -> Self
pub fn include_file(self, name: impl Into<String>) -> Self
Generate a module-tree include file alongside the per-package .rs
files.
The include file contains nested pub mod declarations with
include!() directives that assemble the generated code into a
module hierarchy matching the protobuf package structure. Users can
then include this single file instead of manually creating the
module tree.
The form of the emitted include! directives depends on whether
out_dir was set:
- Default (
$OUT_DIR): emitsinclude!(concat!(env!("OUT_DIR"), "/foo.rs")), for use frombuild.rsviainclude!(concat!(env!("OUT_DIR"), "/<name>")). - Explicit
out_dir: emits sibling-relativeinclude!("foo.rs"), for checking the generated code into the source tree and referencing it as a module (e.g.mod gen;).
§Example — build.rs / $OUT_DIR
// build.rs
buffa_build::Config::new()
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.include_file("_include.rs")
.compile()
.unwrap();
// lib.rs
include!(concat!(env!("OUT_DIR"), "/_include.rs"));§Example — checked-in source
// codegen.rs (run manually, not from build.rs)
buffa_build::Config::new()
.files(&["proto/my_service.proto"])
.includes(&["proto/"])
.out_dir("src/gen")
.include_file("mod.rs")
.compile()
.unwrap();
// lib.rs
mod gen;Sourcepub fn compile(self) -> Result<(), Box<dyn Error>>
pub fn compile(self) -> Result<(), Box<dyn Error>>
Compile proto files and generate Rust source.
§Errors
Returns an error if:
OUT_DIRis not set and noout_dirwas configuredprotocorbufcannot be found onPATH(when using those sources)- the proto compiler exits with a non-zero status (syntax errors, missing imports, etc.)
- a precompiled descriptor set file cannot be read
- the descriptor set bytes cannot be decoded as a
FileDescriptorSet - code generation fails (e.g. unsupported proto feature)
- the output directory cannot be created or written to