Skip to main content

micropb_gen/
lib.rs

1#![warn(missing_docs)]
2//! `micropb-gen` compiles `.proto` files into Rust code. It is intended to be used inside
3//! `build.rs` for build-time code generation.
4//!
5//! Unlike other Protobuf code generators in the Rust ecosystem, `micropb` is aimed for constrained
6//! environments without an allocator.
7//!
8//! The entry point of this crate is the [`Generator`] type.
9//!
10//! For info on the "library layer" of `micropb-gen`, see [`micropb`].
11//!
12//! # Getting Started
13//!
14//! Add `micropb` crates to your `Cargo.toml`:
15//! ```toml
16//! [dependencies]
17//! # Allow types from `heapless` to be used for container fields
18//! micropb = { version = "0.3.0", features = ["container-heapless-0-9"] }
19//! heapless = "0.9"
20//!
21//! [build-dependencies]
22//! micropb-gen = "0.3.0"
23//! ```
24//!
25//! Then, place your `.proto` file into the project's root directory:
26//! ```proto
27//! // example.proto
28//! message Example {
29//!     int32 field1 = 1;
30//!     bool field2 = 2;
31//!     double field3 = 3;
32//! }
33//! ```
34//!
35//! `micropb-gen` requires `protoc` to build `.proto` files, so [install
36//! `protoc`](https://grpc.io/docs/protoc-installation) and add it to your PATH, then invoke the
37//! code generator in `build.rs`:
38//!
39//! ```rust,no_run
40//! let mut generator = micropb_gen::Generator::new();
41//! // Compile example.proto into a Rust module
42//! generator.compile_protos(&["example.proto"], std::env::var("OUT_DIR").unwrap() + "/example.rs").unwrap();
43//! ```
44//!
45//! Finally, include the generated file in your code:
46//! ```rust,ignore
47//! // main.rs
48//! use micropb::{MessageDecode, MessageEncode, PbEncoder};
49//!
50//! mod example {
51//!     #![allow(clippy::all)]
52//!     #![allow(nonstandard_style, unused, irrefutable_let_patterns)]
53//!     // Let's assume that Example is the only message define in the .proto file that has been
54//!     // converted into a Rust struct
55//!     include!(concat!(env!("OUT_DIR"), "/example.rs"));
56//! }
57//!
58//! let example = example::Example {
59//!     field1: 12,
60//!     field2: true,
61//!     field3: 0.234,
62//! };
63//!
64//! // Maximum size of the message type on the wire, scaled to the next power of 2 for heapless::Vec
65//! const CAPACITY: usize = example::Example::MAX_SIZE.unwrap().next_power_of_two();
66//! // For the example message above we can use a smaller capacity
67//! // const CAPACITY: usize = 32;
68//!
69//! // Use heapless::Vec as the output stream and build an encoder around it
70//! let mut encoder = PbEncoder::new(heapless::Vec::<u8, CAPACITY>::new());
71//!
72//! // Compute the size of the `Example` on the wire
73//! let _size = example.compute_size();
74//! // Encode the `Example` to the data stream
75//! example.encode(&mut encoder).expect("Vec over capacity");
76//!
77//! // Decode a new instance of `Example` into a new struct
78//! let mut new = example::Example::default();
79//! let data = encoder.as_writer().as_slice();
80//! new.decode_from_bytes(data).expect("decoding failed");
81//! assert_eq!(example, new);
82//! ```
83//!
84//! # Messages
85//!
86//! Protobuf messages are translated directly into Rust structs, and each message field translates into a Rust field.
87//!
88//! Given the following Protobuf definition:
89//! ```proto
90//! syntax = "proto3";
91//!
92//! package example;
93//!
94//! message Example {
95//!     int32 f_int32 = 1;
96//!     int64 f_int64 = 2;
97//!     uint32 f_uint32 = 3;
98//!     uint64 f_uint64 = 4;
99//!     sint32 f_sint32 = 5;
100//!     sint64 f_sint64 = 6;
101//!     bool f_bool = 7;
102//!     fixed32 f_fixed32 = 8;
103//!     fixed64 f_fixed64 = 9;
104//!     sfixed32 f_sfixed32 = 10;
105//!     sfixed64 f_sfixed64 = 11;
106//!     float f_float = 12;
107//!     double f_double = 13;
108//! }
109//! ```
110//!
111//! `micropb-gen` will generate the following Rust structs and APIs:
112//! ```rust,ignore
113//! pub mod example_ {
114//!     #[derive(Debug, Clone, Copy)]
115//!     pub struct Example {
116//!         pub f_int32: i32,
117//!         pub f_int64: i64,
118//!         pub f_uint32: u32,
119//!         pub f_uint64: u64,
120//!         pub f_sint32: i32,
121//!         pub f_sint64: i64,
122//!         pub f_bool: bool,
123//!         pub f_fixed32: u32,
124//!         pub f_fixed64: u64,
125//!         pub f_sfixed32: u32,
126//!         pub f_sfixed64: u64,
127//!         pub f_float: f32,
128//!         pub f_double: f64,
129//!     }
130//!
131//!     impl Example {
132//!         /// Return reference to f_int32
133//!         pub fn f_int32(&self) -> &i32;
134//!         /// Return mutable reference to f_int32
135//!         pub fn mut_f_int32(&mut self) -> &mut i32;
136//!         /// Set value of f_int32
137//!         pub fn set_f_int32(&mut self, val: i32) -> &mut Self;
138//!         /// Builder method that sets f_int32. Useful for initializing the message.
139//!         pub fn init_f_int32(mut self, val: i32) -> Self;
140//!
141//!         // Same APIs for the other singular fields
142//!     }
143//!
144//!     impl Default for Example { /* ... */ }
145//!
146//!     impl PartialEq for Example { /* ... */ }
147//!
148//!     impl micropb::MessageEncode for Example { /* ... */ }
149//!
150//!     impl micropb::MessageDecode for Example { /* ... */ }
151//! }
152//! ```
153//!
154//! The generated [`MessageDecode`](micropb::MessageEncode) and
155//! [`MessageEncode`](micropb::MessageDecode) implementations provide APIs for decoding, encoding,
156//! and computing the size of `Example`.
157//!
158//! Implementations or derives for `Default`, `Clone`, `PartialEq`, and `Debug` are also provided.
159//! `Copy` derives are generated for messages consisting entirely of copyable fields.
160//!
161//! ## Optional Fields
162//!
163//! While the obvious choice for representing optional fields is [`Option`], this is not actually
164//! ideal in embedded systems because `Option<T>` actually takes up twice as much space as `T` for
165//! many types, such as `u32` and `i32`. Instead, **`micropb` tracks the presence of all optional
166//! fields of a message in a separate bitfield called a _hazzer_**, which is usually small enough to
167//! fit into the padding. Field presence can either be queried directly from the hazzer or from
168//! message APIs that return `Option`.
169//!
170//! For example, given the following Protobuf message:
171//! ```proto
172//! message Example {
173//!     optional int32 f_int32 = 1;
174//!     optional int64 f_int64 = 2;
175//!     optional bool f_bool = 3;
176//! }
177//! ```
178//!
179//! `micropb-gen` generates the following Rust struct and APIs:
180//! ```rust,ignore
181//! pub struct Example {
182//!     pub f_int32: i32,
183//!     pub f_int64: i64,
184//!     pub f_bool: bool,
185//!
186//!     pub _has: Example_::_Hazzer,
187//! }
188//!
189//! impl Example {
190//!     /// Return reference to f_int32 as an Option
191//!     pub fn f_int32(&self) -> Option<&i32>;
192//!     /// Return mutable reference to f_int32 as an Option
193//!     pub fn mut_f_int32(&mut self) -> Option<&mut i32>;
194//!     /// Set value and presence of f_int32
195//!     pub fn set_f_int32(&mut self, val: i32) -> &mut Self;
196//!     /// Clear presence of f_int32
197//!     pub fn clear_f_int32(&mut self) -> &mut Self;
198//!     /// Take f_int32 and return it
199//!     pub fn take_f_int32(&mut self) -> Option<i32>;
200//!     /// Builder method that sets f_int32. Useful for initializing the message.
201//!     pub fn init_f_int32(mut self, val: i32) -> Self;
202//!
203//!     // Same APIs for other optional fields
204//! }
205//!
206//! pub mod Example_ {
207//!     /// Tracks whether the optional fields are present
208//!     #[derive(Debug, Default, Clone, PartialEq, Copy)]
209//!     pub struct _Hazzer([u8; 1]);
210//!
211//!     impl _Hazzer {
212//!         /// Create an empty Hazzer with all fields cleared
213//!         pub const fn _new() -> Self;
214//!
215//!         /// Query presence of f_int32
216//!         pub const fn f_int32(&self) -> bool;
217//!         /// Set presence of f_int32
218//!         pub const fn set_f_int32(&mut self) -> &mut Self;
219//!         /// Clear presence of f_int32
220//!         pub const fn clear_f_int32(&mut self) -> &mut Self;
221//!         /// Builder method that toggles on the presence of f_int32. Useful for initializing the Hazzer.
222//!         pub const fn init_f_int32(mut self) -> Self;
223//!
224//!         // Same APIs for other optional fields
225//!     }
226//! }
227//!
228//! // trait impls, decode/encode logic, etc
229//! ```
230//!
231//! ### Note on Initialization
232//!
233//! **A field will be considered empty (and ignored by the encoder) if its bit in the hazzer is not
234//! set, _even if the field itself has been written_.** The following is an easy way to initialize a
235//! message with all optional fields set:
236//! ```rust,ignore
237//! Example::default().init_f_int32(4).init_f_int64(-5).init_f_bool(true)
238//! ```
239//!
240//! Alternatively, we can initialize the message using the constructor:
241//! ```rust,ignore
242//! Example {
243//!     f_int32: 4,
244//!     f_int64: -5,
245//!     f_bool: true,
246//!     // initialize the hazzer with all fields set to true
247//!     // without initializing the hazzer, all fields in Example will be considered unset
248//!     _has: Example_::_Hazzer::default()
249//!             .init_f_int32()
250//!             .init_f_int64()
251//!             .init_f_bool()
252//! }
253//! ```
254//!
255//! ### Fallback to [`Option`]
256//!
257//! By default, optional fields are represented by bitfields, as shown above. If an optional field
258//! is configured to be boxed via [`Config::boxed`], it will instead be represented as an `Option`,
259//! because `Option<Box<T>>` doesn't take up extra space compared to `Box<T>`. To override these default
260//! behaviours, see [`Config::optional_repr`].
261//!
262//! ### Required fields
263//!
264//! The generator treats required fields exactly the same way it treats optional fields.
265//!
266//! ## Message fields
267//!
268//! Message fields are generated as the corresponding Rust struct. If the message field has no
269//! modifier in `proto3`, it will be treated as an optional field. Cyclical references between
270//! parent message types and field types will be broken by automatically boxing the field to
271//! prevent infinite-sized structs.
272//!
273//! ## Oneof Fields
274//!
275//! Protobuf oneofs are translated into Rust enums. The enum type is defined in an internal
276//! module under the message, and its type name is the same as the name of the oneof field.
277//!
278//! For example, given this Protobuf definition:
279//! ```proto
280//! message Example {
281//!     oneof number {
282//!         int32 int = 1;
283//!         float decimal = 2;
284//!     }
285//! }
286//! ```
287//!
288//! `micropb-gen` generates the following definition:
289//! ```rust,no_run
290//! #[derive(Debug, Clone, PartialEq, Copy)]
291//! pub struct Example {
292//!     pub number: Option<Example_::Number>,
293//! }
294//!
295//! pub mod Example_ {
296//!     #[derive(Debug, Clone, PartialEq, Copy)]
297//!     pub enum Number {
298//!         Int(i32),
299//!         Decimal(f32),
300//!     }
301//! }
302//! ```
303//!
304//! ## Repeated, `map`, `string`, and `bytes` Fields
305//!
306//! Repeated, `map`, `string`, and `bytes` fields need to be represented as Rust "container" types,
307//! since they contain multiple elements or bytes. Normally standard types like `String` and `Vec`
308//! are used, but they aren't available in no-alloc environments. Instead, we need stack-allocated
309//! containers with fixed capacity. Since there is no defacto standard for such containers in Rust,
310//! **users are expected to configure the code generator with their own container types** (see
311//! [`Config`] for more details).
312//!
313//! For example, given the following Protobuf definition:
314//! ```proto
315//! message Containers {
316//!     string f_string = 1;
317//!     bytes f_bytes = 2;
318//!     repeated int32 f_repeated = 3;
319//!     map<int32, int64> f_map = 4;
320//! }
321//! ```
322//!
323//! and the following configuration in `build.rs`:
324//! ```rust,no_run
325//! let mut generator = micropb_gen::Generator::new();
326//! // Configure our own container types
327//! generator.configure(".",
328//!     micropb_gen::Config::new()
329//!         .string_type("crate::MyString<$N>")
330//!         .bytes_type("crate::MyVec<u8, $N>")
331//!         .vec_type("crate::MyVec<$T, $N>")
332//!         .map_type("crate::MyMap<$K, $V, $N>")
333//! );
334//!
335//! // We can also use container types from `heapless`, which have fixed capacity
336//! generator.use_container_heapless();
337//!
338//! // Same shorthand exists for containers from `arrayvec` or `alloc`
339//! // generator.use_container_arrayvec();
340//! // generator.use_container_alloc();
341//!
342//!
343//! // Since we're using fixed containers, we need to specify the max capacity of each field.
344//! // For simplicity, configure capacity of all repeated/map fields to 4 and string/bytes to 8.
345//! generator.configure(".", micropb_gen::Config::new().max_len(4).max_bytes(8));
346//! ```
347//!
348//! The following Rust struct will be generated:
349//! ```rust,no_run
350//! pub struct Containers {
351//!     f_string: heapless::String<8>,
352//!     f_bytes: heapless::Vec<u8, 8>,
353//!     f_repeated: heapless::Vec<i32, 4>,
354//!     f_map: heapless::index_map::FnvIndexMap<i32, i64, 4>,
355//! }
356//! ```
357//!
358//! For **decoding**, container types should implement [`PbVec`](micropb::PbVec) (repeated fields),
359//! [`PbString`](micropb::PbString), [`PbBytes`](micropb::PbBytes), or [`PbMap`](micropb::PbMap)
360//! For convenience, [`micropb`] comes with built-in implementations of the container traits for
361//! types from [`heapless`](https://docs.rs/heapless/latest/heapless),
362//! [`arrayvec`](https://docs.rs/arrayvec/latest/arrayvec), and
363//! [`alloc`](https://doc.rust-lang.org/alloc), as well as implementations on `[u8; N]` arrays and
364//! [`FixedLenString`](micropb::FixedLenString).
365//!
366//! For **encoding**, container types need to dereference into `&[T]` (repeated fields), `&str`, or
367//! `&[u8]`. Maps just need to iterate through key-value pairs.
368//!
369//! ## Message Lifetime
370//!
371//! A message struct may have up to one lifetime parameter. `micropb-gen` automatically generates
372//! the lifetime parameter for each message by checking if there's a lifetime in any of the fields.
373//!
374//! For example, given the Protobuf file from the previous section and the following `build.rs`
375//! config:
376//! ```rust,no_run
377//! # use micropb_gen::{Generator, Config, config::CustomField};
378//! # let mut generator = Generator::new();
379//! // Use `Cow` as container type with lifetime of 'a
380//! generator.configure(".",
381//!     Config::new()
382//!         .string_type("alloc::borrow::Cow<'a, str>")
383//!         .bytes_type("alloc::borrow::Cow<'a, [u8]>")
384//!         .vec_type("alloc::borrow::Cow<'a, [$T]>")
385//! );
386//! // Use a custom type for the `f_map` field, also with lifetime of 'a
387//! generator.configure(".Containers.f_map",
388//!     Config::new().custom_field(CustomField::from_type("MyField<'a>"))
389//! );
390//! ```
391//!
392//! `micropb-gen` generates the following struct:
393//! ```rust,no_run
394//! # extern crate alloc;
395//! # struct MyField<'a>(&'a u8);
396//! pub struct Containers<'a> {
397//!     f_string: alloc::borrow::Cow<'a, str>,
398//!     f_bytes: alloc::borrow::Cow<'a, [u8]>,
399//!     f_repeated: alloc::borrow::Cow<'a, [i32]>,
400//!     f_map: MyField<'a>,
401//! }
402//! ```
403//!
404//! Note that message types can only have a single lifetime, so don't mix multiple lifetime
405//! identifiers in your configuration.
406//!
407//! # Enums
408//!
409//! Protobuf enums are translated into "open" enums in Rust, rather than normal Rust enums. This is
410//! because proto3 requires enums to store unrecognized values, which is only possible with open
411//! enums.
412//!
413//! For example, given this Protobuf enum:
414//! ```proto
415//! enum Language {
416//!     RUST = 0,
417//!     C = 1,
418//!     CPP = 2,
419//! }
420//! ```
421//!
422//! `micropb-gen` generates the following Rust definition:
423//! ```rust,ignore
424//! #[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)]
425//! #[repr(transparent)]
426//! pub struct Language(pub i32);
427//!
428//! impl Language {
429//!     // Default value
430//!     pub const Rust: Self = Self(0);
431//!     pub const C: Self = Self(1);
432//!     pub const Cpp: Self = Self(2);
433//! }
434//!
435//! impl From<i32> for Language { /* .. */ }
436//! ```
437//!
438//! # Packages and Modules
439//!
440//! `micropb-gen` translates Protobuf package names into Rust modules by appending an underscore.
441//!
442//! For example, given the following Protobuf file:
443//! ```proto
444//! package foo.bar;
445//!
446//! // Protobuf contents
447//! ```
448//!
449//! The generated Rust file will look like:
450//! ```rust,ignore
451//! pub mod foo_ {
452//!     pub mod bar_ {
453//!         // Generated code lives here
454//!     }
455//! }
456//! ```
457//!
458//! If a Protobuf file does not have a package specifier, the generated code will instead live in
459//! the root module
460//!
461//! Message names are also translated into Rust modules by appending an underscore. For example,
462//! code generated from oneofs and nested messages within the `Name` message will live in the
463//! `Name_` module.
464//!
465//! # Configuring the Generator
466//!
467//! One of `micropb-gen`'s main features is its granular configuration system, which allows users
468//! to control how code is generated at the level of the module, message, or even individual
469//! fields. See [`Generator::configure`] and [`Config`] for more info on the configuration system.
470//!
471//! ## Notable Configurations
472//!
473//! - **Integer size**: Controls the width of the integer types used to represent [integer
474//!   fields](Config::int_size). This can also be done for [enums](Config::enum_int_size).
475//!
476//! - **Attributes**: Apply custom attributes to [fields](Config::field_attributes) and
477//!   [messages](Config::type_attributes).
478//!
479//! - **Custom fields**: Substitute your own type into the generated code, allowing complete
480//!   control over the encode and decode behaviour. Can be applied to [normal
481//!   fields](Config::custom_field) or [unknown fields](Config::unknown_handler).
482//!
483//! - **Max container size**: Specify the max capacity of [`string`/`bytes`
484//!   fields](Config::max_bytes) as well as [repeated fields](Config::max_len), which is necessary
485//!   when using fixed-capacity containers like `ArrayVec`.
486//!
487//! ## Configuration Files
488//!
489//! Configurations can be stored in TOML files rather than in `build.rs`. See
490//! [`Generator::parse_config_file`] for more info.
491
492pub mod config;
493pub(crate) mod error;
494mod generator;
495mod pathtree;
496mod utils;
497
498// This module was generated from example/file-descriptor-proto
499mod descriptor {
500    #![allow(clippy::all)]
501    #![allow(nonstandard_style, dead_code, unused_imports)]
502    include!("descriptor.rs");
503
504    pub use google_::protobuf_::*;
505}
506
507use std::{
508    collections::BTreeMap,
509    env,
510    ffi::{OsStr, OsString},
511    fmt, fs,
512    io::Write,
513    path::{Path, PathBuf},
514    process::Command,
515};
516
517pub use config::Config;
518pub use error::{Error, Result};
519use micropb::{MessageDecode, PbDecoder};
520use pathtree::PathTree;
521use proc_macro2::TokenStream;
522
523use crate::generator::Context;
524
525#[derive(Debug, Clone, Copy, Default)]
526/// Whether to include encode and decode logic
527pub enum EncodeDecode {
528    /// Only include encode logic
529    EncodeOnly,
530    /// Only include decode logic
531    DecodeOnly,
532    #[default]
533    /// Include both encode and decode logic
534    Both,
535}
536
537impl EncodeDecode {
538    fn is_encode(self) -> bool {
539        matches!(self, Self::EncodeOnly | Self::Both)
540    }
541
542    fn is_decode(self) -> bool {
543        matches!(self, Self::DecodeOnly | Self::Both)
544    }
545}
546
547type WarningCb = fn(fmt::Arguments);
548
549fn warn_cargo_build(args: fmt::Arguments) {
550    println!("cargo::warning={args}");
551}
552
553/// Protobuf code generator
554///
555/// Use this in `build.rs` to compile `.proto` files into a Rust module.
556///
557/// The main way to control the compilation process is to call [`configure`](Generator::configure),
558/// which allows the user to customize how code is generated from Protobuf types and fields of
559/// their choosing.
560///
561/// # Note
562/// It's recommended to call one of [`use_container_alloc`](Self::use_container_alloc),
563/// [`use_container_heapless`](Self::use_container_heapless), or
564/// [`use_container_alloc`](Self::use_container_alloc) to ensure that container types are
565/// configured for `string`, `bytes`, repeated, and `map` fields. The generator will throw an
566/// error if it reaches any such field that doesn't have a container configured.
567///
568/// # Example
569/// ```no_run
570/// use micropb_gen::{Generator, Config};
571///
572/// let mut generator = Generator::new();
573/// // Use container types from `heapless`
574/// generator.use_container_heapless()
575///     // Set max length of repeated fields in .test.Data to 4
576///     .configure(".test.Data", Config::new().max_len(4))
577///     // Wrap .test.Data.value inside a Box
578///     .configure(".test.Data.value", Config::new().boxed(true));
579/// // Compile test.proto into a Rust module
580/// generator.compile_protos(
581///     &["test.proto"],
582///     std::env::var("OUT_DIR").unwrap() + "/test_proto.rs",
583/// )
584/// .unwrap();
585/// ```
586pub struct Generator {
587    pub(crate) config_tree: PathTree<Box<Config>>,
588
589    pub(crate) warning_cb: WarningCb,
590    pub(crate) extern_paths: BTreeMap<String, TokenStream>,
591    pub(crate) encode_decode: EncodeDecode,
592    pub(crate) calculate_max_size: bool,
593    pub(crate) retain_enum_prefix: bool,
594    pub(crate) format: bool,
595    pub(crate) fdset_path: Option<PathBuf>,
596    pub(crate) protoc_args: Vec<OsString>,
597    pub(crate) suffixed_package_names: bool,
598    pub(crate) single_oneof_msg_as_enum: bool,
599    pub(crate) comments_to_docs: bool,
600    pub(crate) encode_cache: bool,
601    pub(crate) cache_extern_types: bool,
602}
603
604#[allow(clippy::new_without_default)]
605impl Generator {
606    /// Create new generator with default settings
607    ///
608    /// By default, the generator assumes it's running inside a Cargo build script, so all warnings
609    /// will be emitted as compiler warnings. If the generator is not running inside a build
610    /// script, use [`with_warning_callback`](Self::with_warning_callback).
611    pub fn new() -> Self {
612        Self::with_warning_callback(warn_cargo_build)
613    }
614
615    /// Create a generator with a custom callback for emitting warnings
616    pub fn with_warning_callback(warning_cb: WarningCb) -> Self {
617        let config_tree = PathTree::new(Box::new(Config::default()));
618
619        Self {
620            config_tree,
621
622            warning_cb,
623            extern_paths: Default::default(),
624            encode_decode: Default::default(),
625            retain_enum_prefix: Default::default(),
626            format: true,
627            calculate_max_size: true,
628            fdset_path: Default::default(),
629            protoc_args: Default::default(),
630            suffixed_package_names: true,
631            single_oneof_msg_as_enum: false,
632            comments_to_docs: true,
633            encode_cache: false,
634            cache_extern_types: true,
635        }
636    }
637
638    fn configure_with_path<'a>(&mut self, path: impl Iterator<Item = &'a str>, config: Config) {
639        let config_slot = self.config_tree.root.add_path(path).value_mut();
640        match config_slot {
641            Some(existing) => existing.merge(&config),
642            None => *config_slot = Some(Box::new(config)),
643        }
644    }
645
646    /// Apply code generator configurations to Protobuf types and fields. See
647    /// [`Config`] for possible configuration options.
648    ///
649    /// The `proto_path` argument is a fully-qualified Protobuf path that points to a package,
650    /// type, or field in the compiled `.proto` files. The configurations are applied to the
651    /// element specified by `proto_path`, as well as its children.
652    ///
653    /// # Example
654    /// ```
655    /// # use micropb_gen::{Generator, Config, config::IntSize};
656    /// # let mut generator = micropb_gen::Generator::new();
657    /// // Configure field attributes on a specific field of a message type
658    /// generator.configure(".pkg.Message.int_field", Config::new().field_attributes("#[serde(skip)]"));
659    ///
660    /// // Configure field attributes on all fields of a message type
661    /// generator.configure(".pkg.Message", Config::new().field_attributes("#[serde(skip)]"));
662    ///
663    /// // Configure field attributes on all fields in a package
664    /// generator.configure(".pkg", Config::new().field_attributes("#[serde(skip)]"));
665    ///
666    /// // Configure field attributes on all fields
667    /// generator.configure(".", Config::new().field_attributes("#[serde(skip)]"));
668    ///
669    /// // Configure types attributes on a specific message type
670    /// generator.configure(".pkg.Message", Config::new().type_attributes("#[derive(Serialize)]"));
671    ///
672    /// // Configure boxing behaviour on an oneof in a message type
673    /// generator.configure(".pkg.Message.my_oneof", Config::new().boxed(true));
674    ///
675    /// // Configure the int size on a variant of an oneof
676    /// generator.configure(".pkg.Message.my_oneof_variant", Config::new().int_size(IntSize::S8));
677    ///
678    /// // Configure the int size of an enum
679    /// // Note that enum variants cannot be configured
680    /// generator.configure(".pkg.Enum", Config::new().enum_int_size(IntSize::S8));
681    /// ```
682    ///
683    /// # Special paths
684    /// `configure` also supports special path suffixes for configuring fields in the generated
685    /// code that don't have a corresponding Protobuf path.
686    /// ```no_run
687    /// # use micropb_gen::{Generator, Config, config::IntSize};
688    /// # let mut generator = micropb_gen::Generator::new();
689    /// // Configure the int size of the elements in a repeated field via ".elem"
690    /// generator.configure(".pkg.Message.repeated_field.elem", Config::new().int_size(IntSize::S8));
691    ///
692    /// // Configure the int size of the keys in a map field via ".key"
693    /// generator.configure(".pkg.Message.map_field.key", Config::new().int_size(IntSize::S8));
694    /// // Configure the int size of the values in a map field via ".value"
695    /// generator.configure(".pkg.Message.map_field.value", Config::new().int_size(IntSize::S16));
696    ///
697    /// // Configure the field attributes of hazzer field and the type attributes of
698    /// // the hazzer struct in the message via "._has"
699    /// generator.configure(".pkg.Message._has",
700    ///     Config::new().field_attributes("#[serde(skip)]").type_attributes("#[derive(Serialize)]"));
701    ///
702    /// // Configure the field attributes for the unknown handler field of the message via "._unknown"
703    /// generator.configure(".pkg.Message._unknown", Config::new().field_attributes("#[serde(skip)]"));
704    ///
705    /// ```
706    pub fn configure(&mut self, proto_path: &str, config: Config) -> &mut Self {
707        self.configure_with_path(split_dot_prefixed_pkg_name(proto_path), config);
708        self
709    }
710
711    /// Apply one set of configurations to all provided Protobuf paths.
712    ///
713    /// See [`configure`](Self::configure) for how configurations are applied.
714    pub fn configure_many(&mut self, proto_paths: &[&str], config: Config) -> &mut Self {
715        for path in proto_paths {
716            self.configure(path, config.clone());
717        }
718        self
719    }
720
721    #[cfg(feature = "config-file")]
722    fn parse_config_bytes(
723        &mut self,
724        bytes: &[u8],
725        prefix: &str,
726    ) -> std::result::Result<(), toml::de::Error> {
727        let configs: std::collections::HashMap<String, Config> = toml::from_slice(bytes)?;
728        for (path, config) in configs.into_iter() {
729            let prefix_path = split_dot_prefixed_pkg_name(prefix);
730            let path = split_dot_prefixed_pkg_name(&path);
731            let full_path = prefix_path.chain(path);
732
733            self.configure_with_path(full_path, config);
734        }
735        Ok(())
736    }
737
738    /// Parse configurations from a TOML file and apply them to the specified Protobuf pacakge.
739    ///
740    /// # Example
741    ///
742    /// For example, if we have the following configuration in `build.rs`:
743    ///
744    /// ```
745    /// # use micropb_gen::{Config, config::{IntSize, OptionalRepr}};
746    /// let mut generator = micropb_gen::Generator::new();
747    /// generator.configure(
748    ///     ".my.pkg.Message.int_field",
749    ///     Config::new().int_size(IntSize::S16).optional_repr(OptionalRepr::Option)
750    /// );
751    /// generator.configure("my.pkg.Message.bad_field", Config::new().skip(true));
752    /// ```
753    ///
754    /// We can instead load the configuration for `.my.pkg` from a TOML file:
755    /// ```no_run
756    /// # use std::path::Path;
757    /// # let mut generator = micropb_gen::Generator::new();
758    /// generator.parse_config_file(Path::new("my.pkg.toml"), ".my.pkg")?;
759    /// # Ok::<_, micropb_gen::Error>(())
760    /// ```
761    ///
762    /// `my.pkg.toml`
763    /// ```toml
764    /// # Each Config is represented as a table in the TOML document, keyed by the Protobuf path
765    /// ["Message.int_field"]
766    /// int_size = "S16"
767    /// optional_repr = "Option"
768    ///
769    /// ["Message.bad_field"]
770    /// skip = true
771    /// ```
772    ///
773    /// <div class="warning">Dot-separated Protobuf paths in config files MUST be wrapped in quotes
774    /// for TOML parsing to work correctly.</div>
775    #[cfg(feature = "config-file")]
776    pub fn parse_config_file(&mut self, file_path: &Path, package: &str) -> Result<()> {
777        let file_bytes = fs::read(file_path)?;
778        self.parse_config_bytes(&file_bytes, package)
779            .map_err(|err| Error::ConfigFile {
780                file_name: file_path.to_path_buf(),
781                err,
782            })?;
783        Ok(())
784    }
785
786    /// Configure the generator to generate `heapless` containers for Protobuf `string`, `bytes`,
787    /// repeated, and `map` fields.
788    ///
789    /// If using this option, `micropb` should have one of the `container-heapless-*` features
790    /// enabled, and the crate should depend on the corresponding version of `heapless`.
791    ///
792    /// Specifically, `heapless::String<N>` is generated for `string` fields, `heapless::Vec<u8,
793    /// N>` for `bytes` fields, `heapless::Vec<T, N>` for repeated fields, and
794    /// `heapless::index_map::FnvIndexMap<K, V, N>` for `map` fields. This uses
795    /// [`configure`](Self::configure) under the hood, so configurations set by this call can all
796    /// be overriden.
797    ///
798    /// # Note
799    /// This method assumes you're using `heapless` 0.9. The path to `FnvIndexMap` is different in
800    /// other versions of `heapless`, so you may need to override manually with [`Config::map_type`].
801    ///
802    /// Since `heapless` containers are fixed size, [`max_len`](Config::max_len) or
803    /// [`max_bytes`](Config::max_bytes) must be set for all fields that generate these containers.
804    pub fn use_container_heapless(&mut self) -> &mut Self {
805        self.configure(
806            ".",
807            Config::new()
808                .vec_type("::heapless::Vec<$T, $N>")
809                .string_type("::heapless::String<$N>")
810                .bytes_type("::heapless::Vec<u8, $N>")
811                .map_type("::heapless::index_map::FnvIndexMap<$K, $V, $N>"),
812        );
813        self
814    }
815
816    /// Same as [`use_container_heapless`](Self::use_container_heapless), but for `heapless` 0.8
817    /// and earlier.
818    pub fn use_container_heapless_v0_8(&mut self) -> &mut Self {
819        self.configure(
820            ".",
821            Config::new()
822                .vec_type("::heapless::Vec<$T, $N>")
823                .string_type("::heapless::String<$N>")
824                .bytes_type("::heapless::Vec<u8, $N>")
825                .map_type("::heapless::FnvIndexMap<$K, $V, $N>"),
826        );
827        self
828    }
829
830    /// Configure the generator to generate `arrayvec` containers for Protobuf `string`, `bytes`,
831    /// and repeated fields.
832    ///
833    /// If using this option, `micropb` should have one of the `container-arrayvec-*` feature
834    /// enabled, and the crate should depend on the corresponding version of `arrayvec`.
835    ///
836    /// Specifically, `arrayvec::ArrayString<N>` is generated for `string` fields,
837    /// `arrayvec::ArrayVec<u8, N>` for `bytes` fields, and `arrayvec::ArrayVec<T, N>` for repeated
838    /// fields. This uses [`configure`](Self::configure) under the hood, so configurations set by
839    /// this call can all be overriden.
840    ///
841    /// # Note
842    /// No container is configured for `map` fields, since `arrayvec` doesn't have a suitable map
843    /// type. If the .proto files contain `map` fields, [`map_type`](Config::map_type) will need to
844    /// be configured separately.
845    ///
846    /// Since `arrayvec` containers are fixed size, [`max_len`](Config::max_len) or
847    /// [`max_bytes`](Config::max_bytes) must be set for all fields that generate these containers.
848    pub fn use_container_arrayvec(&mut self) -> &mut Self {
849        self.configure(
850            ".",
851            Config::new()
852                .vec_type("::arrayvec::ArrayVec<$T, $N>")
853                .bytes_type("::arrayvec::ArrayVec<u8, $N>")
854                .string_type("::arrayvec::ArrayString<$N>"),
855        );
856        self
857    }
858
859    /// Configure the generator to generate `alloc` containers for Protobuf `string`, `bytes`,
860    /// repeated, and `map` fields.
861    ///
862    /// If using this option, `micropb` should have the `alloc` feature enabled.
863    ///
864    /// Specifically, `alloc::string::String` is generated for `string` fields,
865    /// `alloc::vec::Vec<u8>` is for `bytes` fields, `alloc::vec::Vec<T>` for repeated fields, and
866    /// `alloc::collections::BTreeMap<K, V>` for `map` fields. This uses
867    /// [`configure`](Self::configure) under the hood, so configurations set by this call can all
868    /// be overriden by future configurations.
869    pub fn use_container_alloc(&mut self) -> &mut Self {
870        self.configure(
871            ".",
872            Config::new()
873                .vec_type("::alloc::vec::Vec<$T>")
874                .bytes_type("::alloc::vec::Vec::<u8>")
875                .string_type("::alloc::string::String")
876                .map_type("::alloc::collections::BTreeMap<$K, $V>"),
877        );
878        self
879    }
880
881    /// Configure the generator to generate `std` containers for Protobuf `string`, `bytes`,
882    /// repeated, and `map` fields.
883    ///
884    /// If using this option, `micropb` should have the `std` feature enabled.
885    ///
886    /// Specifically, `std::string::String` is generated for `string` fields, `std::vec::Vec<u8>`
887    /// for `bytes` fields, `std::vec::Vec<T>` for repeated fields, and
888    /// `std::collections::HashMap<K, V>` for `map` fields. This uses
889    /// [`configure`](Self::configure) under the hood, so configurations set by this call can all
890    /// be overriden by future configurations.
891    pub fn use_container_std(&mut self) -> &mut Self {
892        self.configure(
893            ".",
894            Config::new()
895                .vec_type("::std::vec::Vec<$T>")
896                .bytes_type("::std::vec::Vec::<u8>")
897                .string_type("::std::string::String")
898                .map_type("::std::collections::HashMap<$K, $V>"),
899        );
900        self
901    }
902
903    #[cfg(feature = "config-file")]
904    pub(crate) fn parse_config_from_proto(
905        &mut self,
906        proto_path: &Path,
907        pkg: Option<&String>,
908    ) -> Result<()> {
909        let toml_path = proto_path.with_extension("toml");
910        let pkg_path = match pkg {
911            Some(pkg) => format!(".{pkg}"),
912            None => ".".to_owned(),
913        };
914        match self.parse_config_file(&toml_path, &pkg_path) {
915            // If config file doesn't exist then just assume there's no configs
916            Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
917            Err(err) => Err(err),
918            Ok(()) => Ok(()),
919        }
920    }
921
922    /// Compile `.proto` files and configuration files into a single Rust file.
923    ///
924    /// Configuration files are derived from the proto files by replacing the file extension with
925    /// `.toml`. For example, if `server.proto` is passed in, the generator will look for the
926    /// `server.toml` config file and apply it to the package specified in `server.proto`.
927    #[cfg(feature = "config-file")]
928    pub fn compile_protos_with_config_files(
929        self,
930        protos: &[impl AsRef<Path>],
931        out_filename: impl AsRef<Path>,
932    ) -> Result<()> {
933        self.compile_protos_inner(protos, out_filename.as_ref(), true)
934    }
935
936    /// Compile `.proto` files into a single Rust file.
937    ///
938    /// # Example
939    /// ```no_run
940    /// // build.rs
941    /// let mut generator = micropb_gen::Generator::new();
942    /// generator.compile_protos(&["server.proto", "client.proto"],
943    ///                     std::env::var("OUT_DIR").unwrap() + "/output.rs").unwrap();
944    /// ```
945    ///
946    /// To apply TOML config files along with the proto files, see [`compile_protos_with_config_files`](Self::compile_protos_with_config_files).
947    pub fn compile_protos(
948        self,
949        protos: &[impl AsRef<Path>],
950        out_filename: impl AsRef<Path>,
951    ) -> Result<()> {
952        self.compile_protos_inner(protos, out_filename.as_ref(), false)
953    }
954
955    fn compile_protos_inner(
956        self,
957        protos: &[impl AsRef<Path>],
958        out_filename: &Path,
959        find_config_files: bool,
960    ) -> Result<()> {
961        let tmp;
962        let fdset_file = if let Some(fdset_path) = &self.fdset_path {
963            fdset_path.to_owned()
964        } else {
965            tmp = tempfile::tempdir()?;
966            tmp.path().join("micropb-fdset")
967        };
968
969        // Get protoc command from PROTOC env-var, otherwise just use "protoc"
970        let mut cmd = Command::new(env::var("PROTOC").as_deref().unwrap_or("protoc"));
971        cmd.arg("-o").arg(fdset_file.as_os_str());
972        if self.comments_to_docs {
973            cmd.arg("--include_source_info");
974        }
975        cmd.args(&self.protoc_args);
976
977        for proto in protos {
978            cmd.arg(proto.as_ref());
979        }
980
981        let output = cmd.output().map_err(|e| match e.kind() {
982            std::io::ErrorKind::NotFound => {
983                std::io::Error::new(e.kind(), "`protoc` was not found. Check your PATH.")
984            }
985            _ => e,
986        })?;
987        if !output.status.success() {
988            return Err(Error::Protoc(
989                String::from_utf8_lossy(&output.stderr).into(),
990            ));
991        }
992
993        self.compile_fdset_file_inner(fdset_file.as_path(), out_filename, find_config_files)
994    }
995
996    /// Compile a Protobuf file descriptor set into a Rust file.
997    ///
998    /// Similar to [`compile_protos`](Self::compile_protos), but it does not invoke `protoc` and
999    /// instead takes a file descriptor set.
1000    pub fn compile_fdset_file(
1001        self,
1002        fdset_file: impl AsRef<Path>,
1003        out_filename: impl AsRef<Path>,
1004    ) -> crate::Result<()> {
1005        self.compile_fdset_file_inner(fdset_file.as_ref(), out_filename.as_ref(), false)
1006    }
1007
1008    fn compile_fdset_file_inner(
1009        self,
1010        fdset_file: &Path,
1011        out_filename: &Path,
1012        find_config_files: bool,
1013    ) -> crate::Result<()> {
1014        #[allow(unused)]
1015        let format = self.format;
1016
1017        let bytes = fs::read(fdset_file)?;
1018        let mut decoder = PbDecoder::new(bytes.as_slice());
1019        let mut fdset = descriptor::FileDescriptorSet::default();
1020        fdset
1021            .decode(&mut decoder, bytes.len())
1022            .expect("file descriptor set decode failed");
1023        let code = Context::generate_fdset(self, &fdset, find_config_files)?;
1024
1025        #[cfg(feature = "format")]
1026        let output = if format {
1027            prettyplease::unparse(
1028                &syn::parse2(code).expect("output code should be parseable as a file"),
1029            )
1030        } else {
1031            code.to_string()
1032        };
1033        #[cfg(not(feature = "format"))]
1034        let output = code.to_string();
1035
1036        let mut file = fs::File::create(out_filename)?;
1037        file.write_all(output.as_bytes())?;
1038
1039        Ok(())
1040    }
1041
1042    /// Determine whether the generator strips enum names from variant names.
1043    ///
1044    /// Protobuf enums commonly include the enum name as a prefix of variant names. `micropb`
1045    /// strips this enum name prefix by default. Setting this to `true` prevents the prefix from
1046    /// being stripped.
1047    pub fn retain_enum_prefix(&mut self, retain_enum_prefix: bool) -> &mut Self {
1048        self.retain_enum_prefix = retain_enum_prefix;
1049        self
1050    }
1051
1052    /// Determine whether the generator formats the output code.
1053    ///
1054    /// If the `format` feature isn't enabled, this does nothing.
1055    pub fn format(&mut self, format: bool) -> &mut Self {
1056        self.format = format;
1057        self
1058    }
1059
1060    /// Determine whether to generate logic for encoding and decoding Protobuf messages.
1061    ///
1062    /// Some applications don't need to support both encoding and decoding. This setting allows
1063    /// either the encoding or decoding logic to be omitted from the output. By default, both
1064    /// encoding and decoding are included.
1065    ///
1066    /// This setting allows omitting the `encode` or `decode` feature flag from `micropb`.
1067    pub fn encode_decode(&mut self, encode_decode: EncodeDecode) -> &mut Self {
1068        self.encode_decode = encode_decode;
1069        self
1070    }
1071
1072    /// When set, the file descriptor set generated by `protoc` is written to the provided path,
1073    /// instead of a temporary directory.
1074    pub fn file_descriptor_set_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
1075        self.fdset_path = Some(path.into());
1076        self
1077    }
1078
1079    /// Add an argument to the `protoc` invocation when compiling Protobuf files.
1080    pub fn add_protoc_arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
1081        self.protoc_args.push(arg.as_ref().to_owned());
1082        self
1083    }
1084
1085    /// Declare an externally-provided Protobuf type.
1086    ///
1087    /// When compiling a `.proto` file that imports types from another `.proto` file, `micropb`
1088    /// won't compile the imported file if it's not included in the
1089    /// [`compile_protos`](Self::compile_protos) invocation. This is because the imported file may
1090    /// have already been compiled in another crate. In order to recognize externally-imported
1091    /// types, use `extern_type_path` to map the full Protobuf path of the imported type to the
1092    /// full path of the corresponding Rust type.
1093    ///
1094    /// # Example
1095    ///
1096    /// For example, let's say we have `app.proto`:
1097    /// ```proto
1098    /// // app.proto
1099    ///
1100    /// syntax = "proto3";
1101    /// package app;
1102    ///
1103    /// message App {
1104    ///     time.Timestamp timestamp = 1;
1105    ///     time.TZ timezone = 2;
1106    /// }
1107    /// ```
1108    ///
1109    /// `app.proto` imports from `time.proto`, which has already been compiled into the
1110    /// `time` crate:
1111    /// ```proto
1112    /// // time.proto
1113    ///
1114    /// syntax = "proto3";
1115    /// package time;
1116    ///
1117    /// message Timestamp {
1118    ///     uint32 ts = 1;
1119    /// }
1120    ///
1121    /// enum TZ {
1122    ///     TZ_UTC = 0;
1123    ///     TZ_PST = 1;
1124    /// }
1125    /// ```
1126    ///
1127    /// For our application, we're only interested in compiling `app.proto`, since `time.proto` has
1128    /// already been compiled by another crate. As such, we need to substitute Protobuf types
1129    /// imported from `time.proto` with Rust definitions from the `time` crate.
1130    /// ```no_run
1131    /// // build.rs of app
1132    ///
1133    /// let mut generator = micropb_gen::Generator::new();
1134    /// // Substitute Timestamp message
1135    /// generator.extern_type_path(".time.Timestamp", "time::Timestamp");
1136    /// // Substitute TZ enum
1137    /// generator.extern_type_path(".time.TZ", "time::Tz");
1138    /// // Compile only app.proto, not time.proto
1139    /// generator.compile_protos(&["app.proto"], std::env::var("OUT_DIR").unwrap() + "/output.rs").unwrap();
1140    /// ```
1141    ///
1142    /// # Note
1143    /// It's technically possible to substitute in Rust types that aren't generated by `micropb-gen`.
1144    /// However, the generated code expects substituted messages to implement `MessageDecode` and
1145    /// `MessageEncode`, and substituted enums to have the "open-enum" structure.
1146    pub fn extern_type_path<P1: AsRef<str>, P2: AsRef<str>>(
1147        &mut self,
1148        proto_path: P1,
1149        rust_path: P2,
1150    ) -> &mut Self {
1151        assert!(
1152            proto_path.as_ref().starts_with('.'),
1153            "Fully-qualified Proto path must start with '.'"
1154        );
1155        self.extern_paths.insert(
1156            proto_path.as_ref().to_owned(),
1157            syn::parse_str(rust_path.as_ref()).expect("failed to tokenize extern path"),
1158        );
1159        self
1160    }
1161
1162    /// Determines whether to generate code to calculate the `MAX_SIZE` constant on each message.
1163    ///
1164    /// By default, `micropb-gen` generates code to calculate the `MAX_SIZE` associated constant
1165    /// for each message struct, which determines the max buffer size needed to encode it. If this
1166    /// is set to false, then it replaces the calculations with `Err`, effectively disabling the
1167    /// use of `MAX_SIZE`. This has no runtime impact, but it can reduce the size of the output
1168    /// file.
1169    pub fn calculate_max_size(&mut self, flag: bool) -> &mut Self {
1170        self.calculate_max_size = flag;
1171        self
1172    }
1173
1174    /// Determines whether the modules names generated from package specifiers are suffixed with an
1175    /// underscore.
1176    ///
1177    /// This is on by default. Even when off, module names like "super" and modules created from
1178    /// from message names will still be suffixed.
1179    pub fn suffixed_package_names(&mut self, suffixed: bool) -> &mut Self {
1180        self.suffixed_package_names = suffixed;
1181        self
1182    }
1183
1184    /// For messages with only a single oneof and no other fields, generate an enum representing
1185    /// the oneof rather than a struct.
1186    ///
1187    /// # Example
1188    ///
1189    /// Given the following message:
1190    /// ```proto
1191    /// message Number {
1192    ///     oneof inner {
1193    ///         sint32 signed = 1;
1194    ///         uint32 unsigned = 2;
1195    ///         float fraction = 3;
1196    ///     }
1197    /// }
1198    /// ```
1199    ///
1200    /// The following enum type will be generated:
1201    /// ```no_run
1202    /// pub enum Number {
1203    ///     Signed(i32),
1204    ///     Unsigned(u32),
1205    ///     Fraction(f32),
1206    ///     None,
1207    /// }
1208    /// ```
1209    ///
1210    /// All other message structures, including those with multiple oneofs or a single oneof plus
1211    /// normal fields, will be generated as normal message structs.
1212    ///
1213    /// # Ignored configs
1214    ///
1215    /// With this option, configurations that apply to the oneof itself (`.Number.inner`) will be
1216    /// ignored. Also, [`unknown_handler`](Config::unknown_handler) will be ignored.
1217    pub fn single_oneof_msg_as_enum(&mut self, as_enum: bool) -> &mut Self {
1218        self.single_oneof_msg_as_enum = as_enum;
1219        self
1220    }
1221
1222    /// If enabled, comments in the Proto file will be used to generate doc comments on the
1223    /// messages, enums, oneofs, and fields in the generated code.
1224    ///
1225    /// Enabled by default.
1226    pub fn comments_to_docs(&mut self, flag: bool) -> &mut Self {
1227        self.comments_to_docs = flag;
1228        self
1229    }
1230
1231    /// Enable caching in the generated encoding logic, improving encoding performance in exchange
1232    /// for more stack memory.
1233    ///
1234    /// When encoding Protobuf, length-delimited fields (such as message fields) must be prefixed
1235    /// by their lengths. For a nested field, its length must be computed twice, once for the
1236    /// length calculation of the outer message, and once more to encode the field itself. The
1237    /// number of repeated length calculation increases for each layer of nesting, which can slow
1238    /// the encoding performance of deeply nested messages.
1239    ///
1240    /// If this flag is enabled, the generated encode functions will cache the lengths of nested
1241    /// length-delimited fields on the stack, preventing repeated calculations. This does not
1242    /// change the generated API.
1243    ///
1244    /// Disabled by default.
1245    pub fn encode_cache(&mut self, encode_cache: bool) -> &mut Self {
1246        self.encode_cache = encode_cache;
1247        self
1248    }
1249
1250    /// Determines whether types handled by [`extern_type_path`](Self::extern_type_path) are
1251    /// cached when encoding.
1252    ///
1253    /// This option only has effect if [`encode_cache`](Self::encode_cache) is set. If set, then
1254    /// the external message types must have been generated with `encode_cache`, because the
1255    /// generator expects those types to implement the caching traits.
1256    ///
1257    /// Enabled by default.
1258    pub fn cache_extern_types(&mut self, cache_extern_types: bool) -> &mut Self {
1259        self.cache_extern_types = cache_extern_types;
1260        self
1261    }
1262}
1263
1264fn split_pkg_name(name: &str) -> impl Iterator<Item = &str> {
1265    // ignore empty segments, so empty pkg name points to root node
1266    name.split('.').filter(|seg| !seg.is_empty())
1267}
1268
1269fn split_dot_prefixed_pkg_name(mut name: &str) -> impl Iterator<Item = &str> {
1270    if name.starts_with('.') {
1271        name = &name[1..];
1272    }
1273    split_pkg_name(name)
1274}