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}