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 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 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 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.
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.
Types under the given protobuf path prefix will reference the specified Rust module 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 package path, e.g.,
".my.common" or "my.common" (the leading dot is optional and will
be added automatically). rust_path is the Rust module path where
those types are accessible (e.g., "::common_protos").
§Example
buffa_build::Config::new()
.extern_path(".my.common", "::common_protos")
.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".
§Example
buffa_build::Config::new()
.bytes(&["."]) // 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.
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; 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) 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) 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 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