pbjson_build/
lib.rs

1//! `pbjson-build` consumes the descriptor output of [`prost-build`][1] and generates
2//! [`serde::Serialize`][2] and [`serde::Deserialize`][3] implementations
3//! that are compliant with the [protobuf JSON mapping][4]
4//!
5//! # Usage
6//!
7//! _It is recommended you first follow the example in [prost-build][1] to familiarise
8//! yourself with `prost`_
9//!
10//! Add `prost-build`, `prost`, `pbjson`, `pbjson-build` and `pbjson-types` to
11//! your `Cargo.toml`
12//!
13//! ```toml
14//! [dependencies]
15//! prost = <prost-version>
16//! pbjson = <pbjson-version>
17//! pbjson-types = <pbjson-version>
18//!
19//! [build-dependencies]
20//! prost-build = <prost-version>
21//! pbjson-build = <pbjson-version>
22//! ```
23//!
24//! Next create a `build.rs` containing the following
25//!
26//! ```ignore
27//! // This assumes protobuf files are under a directory called `protos`
28//! // and in a protobuf package `mypackage`
29//!
30//! let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
31//! let proto_files = vec![root.join("myproto.proto")];
32//!
33//! // Tell cargo to recompile if any of these proto files are changed
34//! for proto_file in &proto_files {
35//!     println!("cargo:rerun-if-changed={}", proto_file.display());
36//! }
37//!
38//! let descriptor_path = PathBuf::from(env::var("OUT_DIR").unwrap())
39//!     .join("proto_descriptor.bin");
40//!
41//! prost_build::Config::new()
42//!     // Save descriptors to file
43//!     .file_descriptor_set_path(&descriptor_path)
44//!     // Override prost-types with pbjson-types
45//!     .compile_well_known_types()
46//!     .extern_path(".google.protobuf", "::pbjson_types")
47//!     // Generate prost structs
48//!     .compile_protos(&proto_files, &[root])?;
49//!
50//! let descriptor_set = std::fs::read(descriptor_path)?;
51//! pbjson_build::Builder::new()
52//!     .register_descriptors(&descriptor_set)?
53//!     .build(&[".mypackage"])?;
54//! ```
55//!
56//! Finally within `lib.rs`
57//!
58//! ```ignore
59//! /// Generated by [`prost-build`]
60//! include!(concat!(env!("OUT_DIR"), "/mypackage.rs"));
61//! /// Generated by [`pbjson-build`]
62//! include!(concat!(env!("OUT_DIR"), "/mypackage.serde.rs"));
63//! ```
64//!
65//! The module will now contain the generated prost structs for your protobuf definition
66//! along with compliant implementations of [serde::Serialize][2] and [serde::Deserialize][3]
67//!
68//! [1]: https://docs.rs/prost-build
69//! [2]: https://docs.rs/serde/1.0.130/serde/trait.Serialize.html
70//! [3]: https://docs.rs/serde/1.0.130/serde/trait.Deserialize.html
71//! [4]: https://developers.google.com/protocol-buffers/docs/proto3#json
72
73#![deny(rustdoc::broken_intra_doc_links, rustdoc::bare_urls, rust_2018_idioms)]
74#![warn(
75    missing_debug_implementations,
76    clippy::explicit_iter_loop,
77    clippy::use_self,
78    clippy::clone_on_ref_ptr,
79    clippy::future_not_send
80)]
81
82use prost_types::FileDescriptorProto;
83use std::io::{BufWriter, Error, Result, Write};
84use std::path::PathBuf;
85
86use crate::descriptor::{Descriptor, Package};
87use crate::message::resolve_message;
88use crate::{
89    generator::{generate_enum, generate_message},
90    resolver::Resolver,
91};
92
93mod descriptor;
94mod escape;
95mod generator;
96mod message;
97mod resolver;
98
99#[derive(Debug, Default)]
100pub struct Builder {
101    descriptors: descriptor::DescriptorSet,
102    exclude: Vec<String>,
103    out_dir: Option<PathBuf>,
104    extern_paths: Vec<(String, String)>,
105    retain_enum_prefix: bool,
106    ignore_unknown_fields: bool,
107    btree_map_paths: Vec<String>,
108    emit_fields: bool,
109    use_integers_for_enums: bool,
110    ignore_unknown_enum_variants: bool,
111    preserve_proto_field_names: bool,
112}
113
114impl Builder {
115    /// Create a new `Builder`
116    pub fn new() -> Self {
117        Self::default()
118    }
119
120    /// Configures the output directory where generated Rust files will be written.
121    ///
122    /// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
123    /// executing build scripts, so `out_dir` typically does not need to be configured.
124    pub fn out_dir<P>(&mut self, path: P) -> &mut Self
125    where
126        P: Into<PathBuf>,
127    {
128        self.out_dir = Some(path.into());
129        self
130    }
131
132    /// Register an encoded `FileDescriptorSet` with this `Builder`
133    pub fn register_descriptors(&mut self, descriptors: &[u8]) -> Result<&mut Self> {
134        self.descriptors.register_encoded(descriptors)?;
135        Ok(self)
136    }
137
138    /// Register a decoded `FileDescriptor` with this `Builder`
139    pub fn register_file_descriptor(&mut self, file: FileDescriptorProto) -> &mut Self {
140        self.descriptors.register_file_descriptor(file);
141        self
142    }
143
144    /// Don't generate code for the following type prefixes
145    pub fn exclude<S: Into<String>, I: IntoIterator<Item = S>>(
146        &mut self,
147        prefixes: I,
148    ) -> &mut Self {
149        self.exclude.extend(prefixes.into_iter().map(Into::into));
150        self
151    }
152
153    /// Configures the code generator to not strip the enum name from variant names.
154    pub fn retain_enum_prefix(&mut self) -> &mut Self {
155        self.retain_enum_prefix = true;
156        self
157    }
158
159    /// Declare an externally provided Protobuf package or type
160    pub fn extern_path(
161        &mut self,
162        proto_path: impl Into<String>,
163        rust_path: impl Into<String>,
164    ) -> &mut Self {
165        self.extern_paths
166            .push((proto_path.into(), rust_path.into()));
167        self
168    }
169
170    /// Don't error out in the presence of unknown fields when deserializing,
171    /// instead skip the field.
172    pub fn ignore_unknown_fields(&mut self) -> &mut Self {
173        self.ignore_unknown_fields = true;
174
175        self
176    }
177
178    /// Generate Rust BTreeMap implementations for Protobuf map type fields.
179    pub fn btree_map<S: Into<String>, I: IntoIterator<Item = S>>(&mut self, paths: I) -> &mut Self {
180        self.btree_map_paths
181            .extend(paths.into_iter().map(Into::into));
182        self
183    }
184
185    /// Output fields with their default values.
186    pub fn emit_fields(&mut self) -> &mut Self {
187        self.emit_fields = true;
188        self
189    }
190
191    /// Print integers instead of enum names.
192    pub fn use_integers_for_enums(&mut self) -> &mut Self {
193        self.use_integers_for_enums = true;
194        self
195    }
196
197    /// Ignore unknown enum variants, and instead return the enum default.
198    pub fn ignore_unknown_enum_variants(&mut self) -> &mut Self {
199        self.ignore_unknown_enum_variants = true;
200        self
201    }
202
203    /// Output fields with their original names as defined in their proto schemas, instead of
204    /// lowerCamelCase
205    pub fn preserve_proto_field_names(&mut self) -> &mut Self {
206        self.preserve_proto_field_names = true;
207        self
208    }
209
210    /// Generates code for all registered types where `prefixes` contains a prefix of
211    /// the fully-qualified path of the type
212    pub fn build<S: AsRef<str>>(&mut self, prefixes: &[S]) -> Result<()> {
213        let mut output: PathBuf = self.out_dir.clone().map(Ok).unwrap_or_else(|| {
214            std::env::var_os("OUT_DIR")
215                .ok_or_else(|| Error::other("OUT_DIR environment variable is not set"))
216                .map(Into::into)
217        })?;
218        output.push("FILENAME");
219
220        let write_factory = move |package: &Package| {
221            output.set_file_name(format!("{}.serde.rs", package));
222
223            let file = std::fs::OpenOptions::new()
224                .write(true)
225                .truncate(true)
226                .create(true)
227                .open(&output)?;
228
229            Ok(BufWriter::new(file))
230        };
231
232        let writers = self.generate(prefixes, write_factory)?;
233        for (_, mut writer) in writers {
234            writer.flush()?;
235        }
236
237        Ok(())
238    }
239
240    /// Generates code into instances of write as provided by the `write_factory`
241    ///
242    /// This function is intended for use when writing output of code generation
243    /// directly to output files is not desired. For most use cases inside a
244    /// `build.rs` file, the [`build()`][Self::build] method should be preferred.
245    pub fn generate<S: AsRef<str>, W: Write, F: FnMut(&Package) -> Result<W>>(
246        &self,
247        prefixes: &[S],
248        mut write_factory: F,
249    ) -> Result<Vec<(Package, W)>> {
250        let iter = self.descriptors.iter().filter(move |(t, _)| {
251            let exclude = self
252                .exclude
253                .iter()
254                .any(|prefix| t.prefix_match(prefix.as_ref()).is_some());
255            let include = prefixes
256                .iter()
257                .any(|prefix| t.prefix_match(prefix.as_ref()).is_some());
258            include && !exclude
259        });
260
261        // Exploit the fact descriptors is ordered to group together types from the same package
262        let mut ret: Vec<(Package, W)> = Vec::new();
263        for (type_path, descriptor) in iter {
264            let writer = match ret.last_mut() {
265                Some((package, writer)) if package == type_path.package() => writer,
266                _ => {
267                    let package = type_path.package();
268                    ret.push((package.clone(), write_factory(package)?));
269                    &mut ret.last_mut().unwrap().1
270                }
271            };
272
273            let resolver = Resolver::new(
274                &self.extern_paths,
275                type_path.package(),
276                self.retain_enum_prefix,
277            );
278
279            match descriptor {
280                Descriptor::Enum(descriptor) => generate_enum(
281                    &resolver,
282                    type_path,
283                    descriptor,
284                    writer,
285                    self.use_integers_for_enums,
286                    self.ignore_unknown_enum_variants,
287                )?,
288                Descriptor::Message(descriptor) => {
289                    if let Some(message) = resolve_message(&self.descriptors, descriptor) {
290                        generate_message(
291                            &resolver,
292                            &message,
293                            writer,
294                            self.ignore_unknown_fields,
295                            &self.btree_map_paths,
296                            self.emit_fields,
297                            self.preserve_proto_field_names,
298                        )?
299                    }
300                }
301            }
302        }
303
304        Ok(ret)
305    }
306}