Skip to main content

Config

Struct Config 

Source
pub struct Config { /* private fields */ }
Expand description

Builder for configuring and running protobuf compilation.

Implementations§

Source§

impl Config

Source

pub fn new() -> Self

Create a new configuration with defaults.

Source

pub fn files(self, files: &[impl AsRef<Path>]) -> Self

Add .proto files to compile.

Source

pub fn includes(self, includes: &[impl AsRef<Path>]) -> Self

Add include directories for protoc to search for imports.

Source

pub fn out_dir(self, dir: impl Into<PathBuf>) -> Self

Set the output directory for generated files. Defaults to $OUT_DIR if not set.

Source

pub fn generate_views(self, enabled: bool) -> Self

Enable or disable view type generation (default: true).

Source

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.

Source

pub fn generate_json(self, enabled: bool) -> Self

Enable or disable serde JSON generation (default: false).

When enabled:

  • Generated message structs get Serialize/Deserialize derives.

  • Generated enum types get Serialize/Deserialize derives.

  • Generated view types (when generate_views is also enabled) get a manual impl Serialize for zero-copy JSON serialization, so serde_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 (notably serde_json) accept this, but length-prefixed formats (e.g. bincode, postcard) will return a runtime error. The owned types’ derived Serialize does not have this restriction.
Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 disappearcfg(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.

Source

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 to generate_reflection(false).
  • ReflectMode::Bridgereflect() round-trips through DynamicMessage; smaller generated code, slower reflective access.
  • ReflectMode::VTableimpl ReflectMessage on owned and view types, and reflect() borrows self with no round-trip; equivalent to generate_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.

Source

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_HIGHRuleLevel::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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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();
Source

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 bytesstrict_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();
Source

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.

Source

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].

Source

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).

Source

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 repeated element — or a map<K, bytes> value — fails to compile: codegen emits ReflectElement / ProtoElemJson impls 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 Custom rule does apply to map<K, bytes> values (honored like the built-in BytesRepr::Bytes); only the map<bytes, bytes> carve-out keeps Vec<u8> values.
  • A path that does not parse as a Rust type is reported as a codegen error from compile.
  • A custom bytes type needs no native arbitrary::Arbitrary impl (a generic builder handles it under generate_arbitrary).
Source

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).

Source

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.

Source

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.

Source

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();
Source

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.

Source

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 repeated element fails to compile: codegen emits a ReflectElement impl 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 repeated custom string serializes elements through their native serde, so such a type must derive Serialize / Deserialize (and an external type must enable its serde feature). Singular / optional / oneof custom strings use the proto_string with-module and need no serde impl.
  • A path that does not parse as a Rust type is reported as a codegen error from compile.
  • A custom string type needs no native arbitrary::Arbitrary impl (a generic builder handles it under generate_arbitrary).
Source

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).

Source

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();
Source

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.

Source

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::BTreeMap unless you need a specific foreign map.
  • The newtype must implement buffa::MapStorage plus the derive / FromIterator / ReflectMap / serde / arbitrary bounds documented on buffa::map_codec::MapStorage (the canonical list). JSON and arbitrary work 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.
Source

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).

Source

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.

Source

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.

Source

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 from compile.
  • The pointer must be exclusively owned (Rc/Arc are unusable — the decoder needs DerefMut) and a crate-local newtype (a foreign pointer cannot implement the buffa-owned ProtoBox).
Source

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).

Source

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();
Source

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.

Source

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 from compile.
  • 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 require Field: ProtoList.
  • Under reflection / vtable the newtype must also implement buffa_descriptor’s ReflectList (not derivable, but a Vec-backed newtype can delegate to the inner Vec<T>). Under JSON it must implement serde::Serialize / Deserialize; under generate_arbitrary, arbitrary::Arbitrary (derivable on a newtype). See buffa::ProtoList for a worked newtype example.
Source

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).

Source

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();
Source

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();
Source

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();
Source

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();
Source

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();
Source

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();
Source

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).

Source

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): emits include!(concat!(env!("OUT_DIR"), "/foo.rs")), for use from build.rs via include!(concat!(env!("OUT_DIR"), "/<name>")).
  • Explicit out_dir: emits sibling-relative include!("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;
Source

pub fn compile(self) -> Result<(), Box<dyn Error>>

Compile proto files and generate Rust source.

§Errors

Returns an error if:

  • OUT_DIR is not set and no out_dir was configured
  • protoc or buf cannot be found on PATH (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

Trait Implementations§

Source§

impl Default for Config

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.