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, ErrorKind, 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    preserve_proto_field_names: bool,
111}
112
113impl Builder {
114    /// Create a new `Builder`
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Configures the output directory where generated Rust files will be written.
120    ///
121    /// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
122    /// executing build scripts, so `out_dir` typically does not need to be configured.
123    pub fn out_dir<P>(&mut self, path: P) -> &mut Self
124    where
125        P: Into<PathBuf>,
126    {
127        self.out_dir = Some(path.into());
128        self
129    }
130
131    /// Register an encoded `FileDescriptorSet` with this `Builder`
132    pub fn register_descriptors(&mut self, descriptors: &[u8]) -> Result<&mut Self> {
133        self.descriptors.register_encoded(descriptors)?;
134        Ok(self)
135    }
136
137    /// Register a decoded `FileDescriptor` with this `Builder`
138    pub fn register_file_descriptor(&mut self, file: FileDescriptorProto) -> &mut Self {
139        self.descriptors.register_file_descriptor(file);
140        self
141    }
142
143    /// Don't generate code for the following type prefixes
144    pub fn exclude<S: Into<String>, I: IntoIterator<Item = S>>(
145        &mut self,
146        prefixes: I,
147    ) -> &mut Self {
148        self.exclude.extend(prefixes.into_iter().map(Into::into));
149        self
150    }
151
152    /// Configures the code generator to not strip the enum name from variant names.
153    pub fn retain_enum_prefix(&mut self) -> &mut Self {
154        self.retain_enum_prefix = true;
155        self
156    }
157
158    /// Declare an externally provided Protobuf package or type
159    pub fn extern_path(
160        &mut self,
161        proto_path: impl Into<String>,
162        rust_path: impl Into<String>,
163    ) -> &mut Self {
164        self.extern_paths
165            .push((proto_path.into(), rust_path.into()));
166        self
167    }
168
169    /// Don't error out in the presence of unknown fields when deserializing,
170    /// instead skip the field.
171    pub fn ignore_unknown_fields(&mut self) -> &mut Self {
172        self.ignore_unknown_fields = true;
173
174        self
175    }
176
177    /// Generate Rust BTreeMap implementations for Protobuf map type fields.
178    pub fn btree_map<S: Into<String>, I: IntoIterator<Item = S>>(&mut self, paths: I) -> &mut Self {
179        self.btree_map_paths
180            .extend(paths.into_iter().map(Into::into));
181        self
182    }
183
184    /// Output fields with their default values.
185    pub fn emit_fields(&mut self) -> &mut Self {
186        self.emit_fields = true;
187        self
188    }
189
190    /// Print integers instead of enum names.
191    pub fn use_integers_for_enums(&mut self) -> &mut Self {
192        self.use_integers_for_enums = true;
193        self
194    }
195
196    /// Output fields with their original names as defined in their proto schemas, instead of
197    /// lowerCamelCase
198    pub fn preserve_proto_field_names(&mut self) -> &mut Self {
199        self.preserve_proto_field_names = true;
200        self
201    }
202
203    /// Generates code for all registered types where `prefixes` contains a prefix of
204    /// the fully-qualified path of the type
205    pub fn build<S: AsRef<str>>(&mut self, prefixes: &[S]) -> Result<()> {
206        let mut output: PathBuf = self.out_dir.clone().map(Ok).unwrap_or_else(|| {
207            std::env::var_os("OUT_DIR")
208                .ok_or_else(|| {
209                    Error::new(ErrorKind::Other, "OUT_DIR environment variable is not set")
210                })
211                .map(Into::into)
212        })?;
213        output.push("FILENAME");
214
215        let write_factory = move |package: &Package| {
216            output.set_file_name(format!("{}.serde.rs", package));
217
218            let file = std::fs::OpenOptions::new()
219                .write(true)
220                .truncate(true)
221                .create(true)
222                .open(&output)?;
223
224            Ok(BufWriter::new(file))
225        };
226
227        let writers = self.generate(prefixes, write_factory)?;
228        for (_, mut writer) in writers {
229            writer.flush()?;
230        }
231
232        Ok(())
233    }
234
235    /// Generates code into instances of write as provided by the `write_factory`
236    ///
237    /// This function is intended for use when writing output of code generation
238    /// directly to output files is not desired. For most use cases inside a
239    /// `build.rs` file, the [`build()`][Self::build] method should be preferred.
240    pub fn generate<S: AsRef<str>, W: Write, F: FnMut(&Package) -> Result<W>>(
241        &self,
242        prefixes: &[S],
243        mut write_factory: F,
244    ) -> Result<Vec<(Package, W)>> {
245        let iter = self.descriptors.iter().filter(move |(t, _)| {
246            let exclude = self
247                .exclude
248                .iter()
249                .any(|prefix| t.prefix_match(prefix.as_ref()).is_some());
250            let include = prefixes
251                .iter()
252                .any(|prefix| t.prefix_match(prefix.as_ref()).is_some());
253            include && !exclude
254        });
255
256        // Exploit the fact descriptors is ordered to group together types from the same package
257        let mut ret: Vec<(Package, W)> = Vec::new();
258        for (type_path, descriptor) in iter {
259            let writer = match ret.last_mut() {
260                Some((package, writer)) if package == type_path.package() => writer,
261                _ => {
262                    let package = type_path.package();
263                    ret.push((package.clone(), write_factory(package)?));
264                    &mut ret.last_mut().unwrap().1
265                }
266            };
267
268            let resolver = Resolver::new(
269                &self.extern_paths,
270                type_path.package(),
271                self.retain_enum_prefix,
272            );
273
274            match descriptor {
275                Descriptor::Enum(descriptor) => generate_enum(
276                    &resolver,
277                    type_path,
278                    descriptor,
279                    writer,
280                    self.use_integers_for_enums,
281                )?,
282                Descriptor::Message(descriptor) => {
283                    if let Some(message) = resolve_message(&self.descriptors, descriptor) {
284                        generate_message(
285                            &resolver,
286                            &message,
287                            writer,
288                            self.ignore_unknown_fields,
289                            &self.btree_map_paths,
290                            self.emit_fields,
291                            self.preserve_proto_field_names,
292                        )?
293                    }
294                }
295            }
296        }
297
298        Ok(ret)
299    }
300}