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}