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

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

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

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

The downstream crate must enable the selected type’s buffa feature (smol_str, ecow, or compact_str); otherwise the generated ::buffa::<crate>::<Type> references fail to resolve.

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
use buffa_build::StringRepr;
buffa_build::Config::new()
    .string_type(StringRepr::SmolStr)                          // broad default first
    .string_type_in(StringRepr::CompactString, &[".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, and the downstream crate must enable the selected type’s buffa feature.

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