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}