prost_build/
config.rs

1use std::collections::HashMap;
2use std::default;
3use std::env;
4use std::ffi::{OsStr, OsString};
5use std::fmt;
6use std::fs;
7use std::io::{Error, ErrorKind, Result, Write};
8use std::path::{Path, PathBuf};
9use std::process::Command;
10
11use log::debug;
12use log::trace;
13
14use prost::Message;
15use prost_types::{FileDescriptorProto, FileDescriptorSet};
16
17use crate::code_generator::CodeGenerator;
18use crate::context::Context;
19use crate::extern_paths::ExternPaths;
20use crate::message_graph::MessageGraph;
21use crate::path::PathMap;
22use crate::BytesType;
23use crate::MapType;
24use crate::Module;
25use crate::ServiceGenerator;
26
27/// Configuration options for Protobuf code generation.
28///
29/// This configuration builder can be used to set non-default code generation options.
30pub struct Config {
31    pub(crate) file_descriptor_set_path: Option<PathBuf>,
32    pub(crate) service_generator: Option<Box<dyn ServiceGenerator>>,
33    pub(crate) map_type: PathMap<MapType>,
34    pub(crate) bytes_type: PathMap<BytesType>,
35    pub(crate) type_attributes: PathMap<String>,
36    pub(crate) message_attributes: PathMap<String>,
37    pub(crate) enum_attributes: PathMap<String>,
38    pub(crate) field_attributes: PathMap<String>,
39    pub(crate) boxed: PathMap<()>,
40    pub(crate) prost_types: bool,
41    pub(crate) strip_enum_prefix: bool,
42    pub(crate) out_dir: Option<PathBuf>,
43    pub(crate) extern_paths: Vec<(String, String)>,
44    pub(crate) default_package_filename: String,
45    pub(crate) enable_type_names: bool,
46    pub(crate) type_name_domains: PathMap<String>,
47    pub(crate) protoc_args: Vec<OsString>,
48    pub(crate) protoc_executable: PathBuf,
49    pub(crate) disable_comments: PathMap<()>,
50    pub(crate) skip_debug: PathMap<()>,
51    pub(crate) skip_protoc_run: bool,
52    pub(crate) skip_source_info: bool,
53    pub(crate) include_file: Option<PathBuf>,
54    pub(crate) prost_path: Option<String>,
55    pub(crate) prost_types_path: Option<String>,
56    #[cfg(feature = "format")]
57    pub(crate) fmt: bool,
58}
59
60impl Config {
61    /// Creates a new code generator configuration with default options.
62    pub fn new() -> Config {
63        Config::default()
64    }
65
66    /// Configure the code generator to generate Rust [`BTreeMap`][1] fields for Protobuf
67    /// [`map`][2] type fields.
68    ///
69    /// # Arguments
70    ///
71    /// **`paths`** - paths to specific fields, messages, or packages which should use a Rust
72    /// `BTreeMap` for Protobuf `map` fields. Paths are specified in terms of the Protobuf type
73    /// name (not the generated Rust type name). Paths with a leading `.` are treated as fully
74    /// qualified names. Paths without a leading `.` are treated as relative, and are suffix
75    /// matched on the fully qualified field name. If a Protobuf map field matches any of the
76    /// paths, a Rust `BTreeMap` field is generated instead of the default [`HashMap`][3].
77    ///
78    /// The matching is done on the Protobuf names, before converting to Rust-friendly casing
79    /// standards.
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// # let mut config = prost_build::Config::new();
85    /// // Match a specific field in a message type.
86    /// config.btree_map(&[".my_messages.MyMessageType.my_map_field"]);
87    ///
88    /// // Match all map fields in a message type.
89    /// config.btree_map(&[".my_messages.MyMessageType"]);
90    ///
91    /// // Match all map fields in a package.
92    /// config.btree_map(&[".my_messages"]);
93    ///
94    /// // Match all map fields. Specially useful in `no_std` contexts.
95    /// config.btree_map(&["."]);
96    ///
97    /// // Match all map fields in a nested message.
98    /// config.btree_map(&[".my_messages.MyMessageType.MyNestedMessageType"]);
99    ///
100    /// // Match all fields named 'my_map_field'.
101    /// config.btree_map(&["my_map_field"]);
102    ///
103    /// // Match all fields named 'my_map_field' in messages named 'MyMessageType', regardless of
104    /// // package or nesting.
105    /// config.btree_map(&["MyMessageType.my_map_field"]);
106    ///
107    /// // Match all fields named 'my_map_field', and all fields in the 'foo.bar' package.
108    /// config.btree_map(&["my_map_field", ".foo.bar"]);
109    /// ```
110    ///
111    /// [1]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
112    /// [2]: https://protobuf.dev/programming-guides/proto3/#maps
113    /// [3]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
114    pub fn btree_map<I, S>(&mut self, paths: I) -> &mut Self
115    where
116        I: IntoIterator<Item = S>,
117        S: AsRef<str>,
118    {
119        self.map_type.clear();
120        for matcher in paths {
121            self.map_type
122                .insert(matcher.as_ref().to_string(), MapType::BTreeMap);
123        }
124        self
125    }
126
127    /// Configure the code generator to generate Rust [`bytes::Bytes`](prost::bytes::Bytes) fields for Protobuf
128    /// [`bytes`][2] type fields.
129    ///
130    /// # Arguments
131    ///
132    /// **`paths`** - paths to specific fields, messages, or packages which should use a Rust
133    /// `Bytes` for Protobuf `bytes` fields. Paths are specified in terms of the Protobuf type
134    /// name (not the generated Rust type name). Paths with a leading `.` are treated as fully
135    /// qualified names. Paths without a leading `.` are treated as relative, and are suffix
136    /// matched on the fully qualified field name. If a Protobuf map field matches any of the
137    /// paths, a Rust `Bytes` field is generated instead of the default [`Vec<u8>`][3].
138    ///
139    /// The matching is done on the Protobuf names, before converting to Rust-friendly casing
140    /// standards.
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// # let mut config = prost_build::Config::new();
146    /// // Match a specific field in a message type.
147    /// config.bytes(&[".my_messages.MyMessageType.my_bytes_field"]);
148    ///
149    /// // Match all bytes fields in a message type.
150    /// config.bytes(&[".my_messages.MyMessageType"]);
151    ///
152    /// // Match all bytes fields in a package.
153    /// config.bytes(&[".my_messages"]);
154    ///
155    /// // Match all bytes fields. Specially useful in `no_std` contexts.
156    /// config.bytes(&["."]);
157    ///
158    /// // Match all bytes fields in a nested message.
159    /// config.bytes(&[".my_messages.MyMessageType.MyNestedMessageType"]);
160    ///
161    /// // Match all fields named 'my_bytes_field'.
162    /// config.bytes(&["my_bytes_field"]);
163    ///
164    /// // Match all fields named 'my_bytes_field' in messages named 'MyMessageType', regardless of
165    /// // package or nesting.
166    /// config.bytes(&["MyMessageType.my_bytes_field"]);
167    ///
168    /// // Match all fields named 'my_bytes_field', and all fields in the 'foo.bar' package.
169    /// config.bytes(&["my_bytes_field", ".foo.bar"]);
170    /// ```
171    ///
172    /// [2]: https://protobuf.dev/programming-guides/proto3/#scalar
173    /// [3]: https://doc.rust-lang.org/std/vec/struct.Vec.html
174    pub fn bytes<I, S>(&mut self, paths: I) -> &mut Self
175    where
176        I: IntoIterator<Item = S>,
177        S: AsRef<str>,
178    {
179        self.bytes_type.clear();
180        for matcher in paths {
181            self.bytes_type
182                .insert(matcher.as_ref().to_string(), BytesType::Bytes);
183        }
184        self
185    }
186
187    /// Add additional attribute to matched fields.
188    ///
189    /// # Arguments
190    ///
191    /// **`path`** - a path matching any number of fields. These fields get the attribute.
192    /// For details about matching fields see [`btree_map`](Self::btree_map).
193    ///
194    /// **`attribute`** - an arbitrary string that'll be placed before each matched field. The
195    /// expected usage are additional attributes, usually in concert with whole-type
196    /// attributes set with [`type_attribute`](Self::type_attribute), but it is not
197    /// checked and anything can be put there.
198    ///
199    /// Note that the calls to this method are cumulative ‒ if multiple paths from multiple calls
200    /// match the same field, the field gets all the corresponding attributes.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// # let mut config = prost_build::Config::new();
206    /// // Prost renames fields named `in` to `in_`. But if serialized through serde,
207    /// // they should as `in`.
208    /// config.field_attribute("in", "#[serde(rename = \"in\")]");
209    /// ```
210    pub fn field_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
211    where
212        P: AsRef<str>,
213        A: AsRef<str>,
214    {
215        self.field_attributes
216            .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
217        self
218    }
219
220    /// Add additional attribute to matched messages, enums and one-ofs.
221    ///
222    /// # Arguments
223    ///
224    /// **`paths`** - a path matching any number of types. It works the same way as in
225    /// [`btree_map`](Self::btree_map), just with the field name omitted.
226    ///
227    /// **`attribute`** - an arbitrary string to be placed before each matched type. The
228    /// expected usage are additional attributes, but anything is allowed.
229    ///
230    /// The calls to this method are cumulative. They don't overwrite previous calls and if a
231    /// type is matched by multiple calls of the method, all relevant attributes are added to
232    /// it.
233    ///
234    /// For things like serde it might be needed to combine with [field
235    /// attributes](Self::field_attribute).
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// # let mut config = prost_build::Config::new();
241    /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
242    /// config.type_attribute(".", "#[derive(Eq)]");
243    /// // Some messages want to be serializable with serde as well.
244    /// config.type_attribute("my_messages.MyMessageType",
245    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
246    /// config.type_attribute("my_messages.MyMessageType.MyNestedMessageType",
247    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
248    /// ```
249    ///
250    /// # Oneof fields
251    ///
252    /// The `oneof` fields don't have a type name of their own inside Protobuf. Therefore, the
253    /// field name can be used both with `type_attribute` and `field_attribute` ‒ the first is
254    /// placed before the `enum` type definition, the other before the field inside corresponding
255    /// message `struct`.
256    ///
257    /// In other words, to place an attribute on the `enum` implementing the `oneof`, the match
258    /// would look like `my_messages.MyMessageType.oneofname`.
259    pub fn type_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
260    where
261        P: AsRef<str>,
262        A: AsRef<str>,
263    {
264        self.type_attributes
265            .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
266        self
267    }
268
269    /// Add additional attribute to matched messages.
270    ///
271    /// # Arguments
272    ///
273    /// **`paths`** - a path matching any number of types. It works the same way as in
274    /// [`btree_map`](Self::btree_map), just with the field name omitted.
275    ///
276    /// **`attribute`** - an arbitrary string to be placed before each matched type. The
277    /// expected usage are additional attributes, but anything is allowed.
278    ///
279    /// The calls to this method are cumulative. They don't overwrite previous calls and if a
280    /// type is matched by multiple calls of the method, all relevant attributes are added to
281    /// it.
282    ///
283    /// For things like serde it might be needed to combine with [field
284    /// attributes](Self::field_attribute).
285    ///
286    /// # Examples
287    ///
288    /// ```rust
289    /// # let mut config = prost_build::Config::new();
290    /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
291    /// config.message_attribute(".", "#[derive(Eq)]");
292    /// // Some messages want to be serializable with serde as well.
293    /// config.message_attribute("my_messages.MyMessageType",
294    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
295    /// config.message_attribute("my_messages.MyMessageType.MyNestedMessageType",
296    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
297    /// ```
298    pub fn message_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
299    where
300        P: AsRef<str>,
301        A: AsRef<str>,
302    {
303        self.message_attributes
304            .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
305        self
306    }
307
308    /// Add additional attribute to matched enums and one-ofs.
309    ///
310    /// # Arguments
311    ///
312    /// **`paths`** - a path matching any number of types. It works the same way as in
313    /// [`btree_map`](Self::btree_map), just with the field name omitted.
314    ///
315    /// **`attribute`** - an arbitrary string to be placed before each matched type. The
316    /// expected usage are additional attributes, but anything is allowed.
317    ///
318    /// The calls to this method are cumulative. They don't overwrite previous calls and if a
319    /// type is matched by multiple calls of the method, all relevant attributes are added to
320    /// it.
321    ///
322    /// For things like serde it might be needed to combine with [field
323    /// attributes](Self::field_attribute).
324    ///
325    /// # Examples
326    ///
327    /// ```rust
328    /// # let mut config = prost_build::Config::new();
329    /// // Nothing around uses floats, so we can derive real `Eq` in addition to `PartialEq`.
330    /// config.enum_attribute(".", "#[derive(Eq)]");
331    /// // Some messages want to be serializable with serde as well.
332    /// config.enum_attribute("my_messages.MyEnumType",
333    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
334    /// config.enum_attribute("my_messages.MyMessageType.MyNestedEnumType",
335    ///                       "#[derive(Serialize)] #[serde(rename_all = \"snake_case\")]");
336    /// ```
337    ///
338    /// # Oneof fields
339    ///
340    /// The `oneof` fields don't have a type name of their own inside Protobuf. Therefore, the
341    /// field name can be used both with `enum_attribute` and `field_attribute` ‒ the first is
342    /// placed before the `enum` type definition, the other before the field inside corresponding
343    /// message `struct`.
344    ///
345    /// In other words, to place an attribute on the `enum` implementing the `oneof`, the match
346    /// would look like `my_messages.MyNestedMessageType.oneofname`.
347    pub fn enum_attribute<P, A>(&mut self, path: P, attribute: A) -> &mut Self
348    where
349        P: AsRef<str>,
350        A: AsRef<str>,
351    {
352        self.enum_attributes
353            .insert(path.as_ref().to_string(), attribute.as_ref().to_string());
354        self
355    }
356
357    /// Wrap matched fields in a `Box`.
358    ///
359    /// # Arguments
360    ///
361    /// **`path`** - a path matching any number of fields. These fields get the attribute.
362    /// For details about matching fields see [`btree_map`](Self::btree_map).
363    ///
364    /// # Examples
365    ///
366    /// ```rust
367    /// # let mut config = prost_build::Config::new();
368    /// config.boxed(".my_messages.MyMessageType.my_field");
369    /// ```
370    pub fn boxed<P>(&mut self, path: P) -> &mut Self
371    where
372        P: AsRef<str>,
373    {
374        self.boxed.insert(path.as_ref().to_string(), ());
375        self
376    }
377
378    /// Configures the code generator to use the provided service generator.
379    pub fn service_generator(&mut self, service_generator: Box<dyn ServiceGenerator>) -> &mut Self {
380        self.service_generator = Some(service_generator);
381        self
382    }
383
384    /// Configures the code generator to not use the `prost_types` crate for Protobuf well-known
385    /// types, and instead generate Protobuf well-known types from their `.proto` definitions.
386    pub fn compile_well_known_types(&mut self) -> &mut Self {
387        self.prost_types = false;
388        self
389    }
390
391    /// Configures the code generator to omit documentation comments on generated Protobuf types.
392    ///
393    /// # Example
394    ///
395    /// Occasionally `.proto` files contain code blocks which are not valid Rust. To avoid doctest
396    /// failures, annotate the invalid code blocks with an [`ignore` or `no_run` attribute][1], or
397    /// disable doctests for the crate with a [Cargo.toml entry][2]. If neither of these options
398    /// are possible, then omit comments on generated code during doctest builds:
399    ///
400    /// ```rust,no_run
401    /// # fn main() -> std::io::Result<()> {
402    /// let mut config = prost_build::Config::new();
403    /// config.disable_comments(&["."]);
404    /// config.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
405    /// #     Ok(())
406    /// # }
407    /// ```
408    ///
409    /// As with other options which take a set of paths, comments can be disabled on a per-package
410    /// or per-symbol basis.
411    ///
412    /// [1]: https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#attributes
413    /// [2]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target
414    pub fn disable_comments<I, S>(&mut self, paths: I) -> &mut Self
415    where
416        I: IntoIterator<Item = S>,
417        S: AsRef<str>,
418    {
419        self.disable_comments.clear();
420        for matcher in paths {
421            self.disable_comments
422                .insert(matcher.as_ref().to_string(), ());
423        }
424        self
425    }
426
427    /// Skips generating `impl Debug` for types
428    pub fn skip_debug<I, S>(&mut self, paths: I) -> &mut Self
429    where
430        I: IntoIterator<Item = S>,
431        S: AsRef<str>,
432    {
433        self.skip_debug.clear();
434        for matcher in paths {
435            self.skip_debug.insert(matcher.as_ref().to_string(), ());
436        }
437        self
438    }
439
440    /// Declare an externally provided Protobuf package or type.
441    ///
442    /// `extern_path` allows `prost` types in external crates to be referenced in generated code.
443    ///
444    /// When `prost` compiles a `.proto` which includes an import of another `.proto`, it will
445    /// automatically recursively compile the imported file as well. `extern_path` can be used
446    /// to instead substitute types from an external crate.
447    ///
448    /// # Example
449    ///
450    /// As an example, consider a crate, `uuid`, with a `prost`-generated `Uuid` type:
451    ///
452    /// ```proto
453    /// // uuid.proto
454    ///
455    /// syntax = "proto3";
456    /// package uuid;
457    ///
458    /// message Uuid {
459    ///     string uuid_str = 1;
460    /// }
461    /// ```
462    ///
463    /// The `uuid` crate implements some traits for `Uuid`, and publicly exports it:
464    ///
465    /// ```rust,ignore
466    /// // lib.rs in the uuid crate
467    ///
468    /// include!(concat!(env!("OUT_DIR"), "/uuid.rs"));
469    ///
470    /// pub trait DoSomething {
471    ///     fn do_it(&self);
472    /// }
473    ///
474    /// impl DoSomething for Uuid {
475    ///     fn do_it(&self) {
476    ///         println!("Done");
477    ///     }
478    /// }
479    /// ```
480    ///
481    /// A separate crate, `my_application`, uses `prost` to generate message types which reference
482    /// `Uuid`:
483    ///
484    /// ```proto
485    /// // my_application.proto
486    ///
487    /// syntax = "proto3";
488    /// package my_application;
489    ///
490    /// import "uuid.proto";
491    ///
492    /// message MyMessage {
493    ///     uuid.Uuid message_id = 1;
494    ///     string some_payload = 2;
495    /// }
496    /// ```
497    ///
498    /// Additionally, `my_application` depends on the trait impls provided by the `uuid` crate:
499    ///
500    /// ```rust,ignore
501    /// // `main.rs` of `my_application`
502    ///
503    /// use uuid::{DoSomething, Uuid};
504    ///
505    /// include!(concat!(env!("OUT_DIR"), "/my_application.rs"));
506    ///
507    /// pub fn process_message(msg: MyMessage) {
508    ///     if let Some(uuid) = msg.message_id {
509    ///         uuid.do_it();
510    ///     }
511    /// }
512    /// ```
513    ///
514    /// Without configuring `uuid` as an external path in `my_application`'s `build.rs`, `prost`
515    /// would compile a completely separate version of the `Uuid` type, and `process_message` would
516    /// fail to compile. However, if `my_application` configures `uuid` as an extern path with a
517    /// call to `.extern_path(".uuid", "::uuid")`, `prost` will use the external type instead of
518    /// compiling a new version of `Uuid`. Note that the configuration could also be specified as
519    /// `.extern_path(".uuid.Uuid", "::uuid::Uuid")` if only the `Uuid` type were externally
520    /// provided, and not the whole `uuid` package.
521    ///
522    /// # Usage
523    ///
524    /// `extern_path` takes a fully-qualified Protobuf path, and the corresponding Rust path that
525    /// it will be substituted with in generated code. The Protobuf path can refer to a package or
526    /// a type, and the Rust path should correspondingly refer to a Rust module or type.
527    ///
528    /// ```rust
529    /// # let mut config = prost_build::Config::new();
530    /// // Declare the `uuid` Protobuf package and all nested packages and types as externally
531    /// // provided by the `uuid` crate.
532    /// config.extern_path(".uuid", "::uuid");
533    ///
534    /// // Declare the `foo.bar.baz` Protobuf package and all nested packages and types as
535    /// // externally provided by the `foo_bar_baz` crate.
536    /// config.extern_path(".foo.bar.baz", "::foo_bar_baz");
537    ///
538    /// // Declare the `uuid.Uuid` Protobuf type (and all nested types) as externally provided
539    /// // by the `uuid` crate's `Uuid` type.
540    /// config.extern_path(".uuid.Uuid", "::uuid::Uuid");
541    /// ```
542    pub fn extern_path<P1, P2>(&mut self, proto_path: P1, rust_path: P2) -> &mut Self
543    where
544        P1: Into<String>,
545        P2: Into<String>,
546    {
547        self.extern_paths
548            .push((proto_path.into(), rust_path.into()));
549        self
550    }
551
552    /// When set, the `FileDescriptorSet` generated by `protoc` is written to the provided
553    /// filesystem path.
554    ///
555    /// This option can be used in conjunction with the [`include_bytes!`] macro and the types in
556    /// the `prost-types` crate for implementing reflection capabilities, among other things.
557    ///
558    /// ## Example
559    ///
560    /// In `build.rs`:
561    ///
562    /// ```rust, no_run
563    /// # use std::env;
564    /// # use std::path::PathBuf;
565    /// # let mut config = prost_build::Config::new();
566    /// config.file_descriptor_set_path(
567    ///     PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set"))
568    ///         .join("file_descriptor_set.bin"));
569    /// ```
570    ///
571    /// In `lib.rs`:
572    ///
573    /// ```rust,ignore
574    /// let file_descriptor_set_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin"));
575    /// let file_descriptor_set = prost_types::FileDescriptorSet::decode(&file_descriptor_set_bytes[..]).unwrap();
576    /// ```
577    pub fn file_descriptor_set_path<P>(&mut self, path: P) -> &mut Self
578    where
579        P: Into<PathBuf>,
580    {
581        self.file_descriptor_set_path = Some(path.into());
582        self
583    }
584
585    /// In combination with `file_descriptor_set_path`, this can be used to provide a file
586    /// descriptor set as an input file, rather than having prost-build generate the file by calling
587    /// protoc.
588    ///
589    /// In `build.rs`:
590    ///
591    /// ```rust
592    /// # let mut config = prost_build::Config::new();
593    /// config.file_descriptor_set_path("path/from/build/system")
594    ///     .skip_protoc_run()
595    ///     .compile_protos(&["src/items.proto"], &["src/"]);
596    /// ```
597    ///
598    pub fn skip_protoc_run(&mut self) -> &mut Self {
599        self.skip_protoc_run = true;
600        self
601    }
602
603    /// Configures the code generator to remove surrounding comments and documentation.
604    ///
605    /// If enabled, this will cause `protoc` to not be passed the `--include_source_info` argument.
606    /// Typically, `--include_source_info` is passed by default, but it results in larger
607    /// [`FileDescriptorSet`s](https://github.com/protocolbuffers/protobuf/blob/cff254d32f850ba8186227ce6775b3f01a1f8cf8/src/google/protobuf/descriptor.proto#L54-L66) that include information about the
608    /// original location of each declaration in the source file as well as surrounding
609    /// comments and documentation.
610    ///
611    /// In `build.rs`:
612    ///
613    /// ```rust
614    /// # let mut config = prost_build::Config::new();
615    /// config.file_descriptor_set_path("path/from/build/system")
616    ///     .skip_source_info()
617    ///     .compile_protos(&["src/items.proto"], &["src/"]);
618    /// ```
619    pub fn skip_source_info(&mut self) -> &mut Self {
620        self.skip_source_info = true;
621        self
622    }
623
624    /// Configures the code generator to not strip the enum name from variant names.
625    ///
626    /// Protobuf enum definitions commonly include the enum name as a prefix of every variant name.
627    /// This style is non-idiomatic in Rust, so by default `prost` strips the enum name prefix from
628    /// variants which include it. Configuring this option prevents `prost` from stripping the
629    /// prefix.
630    pub fn retain_enum_prefix(&mut self) -> &mut Self {
631        self.strip_enum_prefix = false;
632        self
633    }
634
635    /// Configures the output directory where generated Rust files will be written.
636    ///
637    /// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
638    /// executing build scripts, so `out_dir` typically does not need to be configured.
639    pub fn out_dir<P>(&mut self, path: P) -> &mut Self
640    where
641        P: Into<PathBuf>,
642    {
643        self.out_dir = Some(path.into());
644        self
645    }
646
647    /// Configures what filename protobufs with no package definition are written to.
648    /// The filename will be appended with the `.rs` extension.
649    pub fn default_package_filename<S>(&mut self, filename: S) -> &mut Self
650    where
651        S: Into<String>,
652    {
653        self.default_package_filename = filename.into();
654        self
655    }
656
657    /// Configures the code generator to include type names.
658    ///
659    /// Message types will implement `Name` trait, which provides type and package name.
660    /// This is needed for encoding messages as `Any` type.
661    pub fn enable_type_names(&mut self) -> &mut Self {
662        self.enable_type_names = true;
663        self
664    }
665
666    /// Specify domain names to use with message type URLs.
667    ///
668    /// # Domains
669    ///
670    /// **`paths`** - a path matching any number of types. It works the same way as in
671    /// [`btree_map`](Self::btree_map), just with the field name omitted.
672    ///
673    /// **`domain`** - an arbitrary string to be used as a prefix for type URLs.
674    ///
675    /// # Examples
676    ///
677    /// ```rust
678    /// # let mut config = prost_build::Config::new();
679    /// // Full type URL of the message `google.profile.Person`,
680    /// // will be `type.googleapis.com/google.profile.Person`.
681    /// config.type_name_domain(&["."], "type.googleapis.com");
682    /// ```
683    pub fn type_name_domain<I, S, D>(&mut self, paths: I, domain: D) -> &mut Self
684    where
685        I: IntoIterator<Item = S>,
686        S: AsRef<str>,
687        D: AsRef<str>,
688    {
689        for matcher in paths {
690            self.type_name_domains
691                .insert(matcher.as_ref().to_string(), domain.as_ref().to_string());
692        }
693        self
694    }
695
696    /// Configures the path that's used for deriving `Message` for generated messages.
697    /// This is mainly useful for generating crates that wish to re-export prost.
698    /// Defaults to `::prost` if not specified.
699    pub fn prost_path<S>(&mut self, path: S) -> &mut Self
700    where
701        S: Into<String>,
702    {
703        self.prost_path = Some(path.into());
704        self
705    }
706
707    /// Configures the path that's used well known types.
708    /// This is mainly useful for generating crates that wish to re-export prost_types.
709    /// Defaults to `::prost_types` if not specified.`
710    pub fn prost_types_path<S>(&mut self, path: S) -> &mut Self
711    where
712        S: Into<String>,
713    {
714        self.prost_types_path = Some(path.into());
715        self
716    }
717
718    /// Add an argument to the `protoc` protobuf compilation invocation.
719    ///
720    /// # Example `build.rs`
721    ///
722    /// ```rust,no_run
723    /// # use std::io::Result;
724    /// fn main() -> Result<()> {
725    ///   let mut prost_build = prost_build::Config::new();
726    ///   // Enable a protoc experimental feature.
727    ///   prost_build.protoc_arg("--experimental_allow_proto3_optional");
728    ///   prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
729    ///   Ok(())
730    /// }
731    /// ```
732    pub fn protoc_arg<S>(&mut self, arg: S) -> &mut Self
733    where
734        S: AsRef<OsStr>,
735    {
736        self.protoc_args.push(arg.as_ref().to_owned());
737        self
738    }
739
740    /// Set the path to `protoc` executable to be used by `prost-build`
741    ///
742    /// Use the provided path to find `protoc`. This can either be a file name which is
743    /// searched for in the `PATH` or an absolute path to use a specific executable.
744    ///
745    /// # Example `build.rs`
746    ///
747    /// ```rust,no_run
748    /// # use std::io::Result;
749    /// fn main() -> Result<()> {
750    ///   let mut prost_build = prost_build::Config::new();
751    ///   prost_build.protoc_executable("protoc-27.1");
752    ///   prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
753    ///   Ok(())
754    /// }
755    /// ```
756    pub fn protoc_executable<S>(&mut self, executable: S) -> &mut Self
757    where
758        S: Into<PathBuf>,
759    {
760        self.protoc_executable = executable.into();
761        self
762    }
763
764    /// Configures the optional module filename for easy inclusion of all generated Rust files
765    ///
766    /// If set, generates a file (inside the `OUT_DIR` or `out_dir()` as appropriate) which contains
767    /// a set of `pub mod XXX` statements combining to load all Rust files generated.  This can allow
768    /// for a shortcut where multiple related proto files have been compiled together resulting in
769    /// a semi-complex set of includes.
770    ///
771    /// Turning a need for:
772    ///
773    /// ```rust,no_run,ignore
774    /// pub mod Foo {
775    ///     pub mod Bar {
776    ///         include!(concat!(env!("OUT_DIR"), "/foo.bar.rs"));
777    ///     }
778    ///     pub mod Baz {
779    ///         include!(concat!(env!("OUT_DIR"), "/foo.baz.rs"));
780    ///     }
781    /// }
782    /// ```
783    ///
784    /// Into the simpler:
785    ///
786    /// ```rust,no_run,ignore
787    /// include!(concat!(env!("OUT_DIR"), "/_includes.rs"));
788    /// ```
789    pub fn include_file<P>(&mut self, path: P) -> &mut Self
790    where
791        P: Into<PathBuf>,
792    {
793        self.include_file = Some(path.into());
794        self
795    }
796
797    // IMPROVEMENT: https://github.com/tokio-rs/prost/pull/1022/files#r1563818651
798    /// Configures the code generator to format the output code via `prettyplease`.
799    ///
800    /// By default, this is enabled but if the `format` feature is not enabled this does
801    /// nothing.
802    #[cfg(feature = "format")]
803    pub fn format(&mut self, enabled: bool) -> &mut Self {
804        self.fmt = enabled;
805        self
806    }
807
808    /// Compile a [`FileDescriptorSet`] into Rust files during a Cargo build with
809    /// additional code generator configuration options.
810    ///
811    /// This method is like `compile_protos` function except it does not invoke `protoc`
812    /// and instead requires the user to supply a [`FileDescriptorSet`].
813    ///
814    /// # Example `build.rs`
815    ///
816    /// ```rust,no_run
817    /// # use prost_types::FileDescriptorSet;
818    /// # fn fds() -> FileDescriptorSet { todo!() }
819    /// fn main() -> std::io::Result<()> {
820    ///   let file_descriptor_set = fds();
821    ///
822    ///   prost_build::Config::new()
823    ///     .compile_fds(file_descriptor_set)
824    /// }
825    /// ```
826    pub fn compile_fds(&mut self, fds: FileDescriptorSet) -> Result<()> {
827        let mut target_is_env = false;
828        let target: PathBuf = self.out_dir.clone().map(Ok).unwrap_or_else(|| {
829            env::var_os("OUT_DIR")
830                .ok_or_else(|| {
831                    Error::new(ErrorKind::Other, "OUT_DIR environment variable is not set")
832                })
833                .map(|val| {
834                    target_is_env = true;
835                    Into::into(val)
836                })
837        })?;
838
839        let requests = fds
840            .file
841            .into_iter()
842            .map(|descriptor| {
843                (
844                    Module::from_protobuf_package_name(descriptor.package()),
845                    descriptor,
846                )
847            })
848            .collect::<Vec<_>>();
849
850        let file_names = requests
851            .iter()
852            .map(|req| {
853                (
854                    req.0.clone(),
855                    req.0.to_file_name_or(&self.default_package_filename),
856                )
857            })
858            .collect::<HashMap<Module, String>>();
859
860        let modules = self.generate(requests)?;
861        for (module, content) in &modules {
862            let file_name = file_names
863                .get(module)
864                .expect("every module should have a filename");
865            let output_path = target.join(file_name);
866
867            write_file_if_changed(&output_path, content.as_bytes())?;
868        }
869
870        if let Some(ref include_file) = self.include_file {
871            let path = target.join(include_file);
872            trace!("Writing include file: {}", path.display());
873            let mut buffer = Vec::new();
874            self.write_line(&mut buffer, 0, "// This file is @generated by prost-build.")?;
875            self.write_includes(
876                modules.keys().collect(),
877                &mut buffer,
878                if target_is_env { None } else { Some(&target) },
879                &file_names,
880            )?;
881
882            write_file_if_changed(&path, &buffer)?;
883        }
884
885        Ok(())
886    }
887
888    /// Loads `.proto` files as a [`FileDescriptorSet`]. This allows inspection of the descriptors
889    /// before calling [`Config::compile_fds`]. This could be used to change [`Config`]
890    /// attributes after introspecting what is actually present in the `.proto` files.
891    ///
892    /// # Example `build.rs`
893    ///
894    /// ```rust,no_run
895    /// # use prost_types::FileDescriptorSet;
896    /// # use prost_build::Config;
897    /// fn main() -> std::io::Result<()> {
898    ///   let mut config = Config::new();
899    ///   let file_descriptor_set = config.load_fds(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
900    ///
901    ///   // Add custom attributes to messages that are service inputs or outputs.
902    ///   for file in &file_descriptor_set.file {
903    ///       for service in &file.service {
904    ///           for method in &service.method {
905    ///               if let Some(input) = &method.input_type {
906    ///                   config.message_attribute(input, "#[derive(custom_proto::Input)]");
907    ///               }
908    ///               if let Some(output) = &method.output_type {
909    ///                   config.message_attribute(output, "#[derive(custom_proto::Output)]");
910    ///               }
911    ///           }
912    ///       }
913    ///   }
914    ///
915    ///   config.compile_fds(file_descriptor_set)
916    /// }
917    /// ```
918    pub fn load_fds(
919        &mut self,
920        protos: &[impl AsRef<Path>],
921        includes: &[impl AsRef<Path>],
922    ) -> Result<FileDescriptorSet> {
923        let tmp;
924        let file_descriptor_set_path = if let Some(path) = &self.file_descriptor_set_path {
925            path.clone()
926        } else {
927            if self.skip_protoc_run {
928                return Err(Error::new(
929                    ErrorKind::Other,
930                    "file_descriptor_set_path is required with skip_protoc_run",
931                ));
932            }
933            tmp = tempfile::Builder::new().prefix("prost-build").tempdir()?;
934            tmp.path().join("prost-descriptor-set")
935        };
936
937        if !self.skip_protoc_run {
938            let mut cmd = Command::new(&self.protoc_executable);
939            cmd.arg("--include_imports");
940            if !self.skip_source_info {
941                cmd.arg("--include_source_info");
942            }
943            cmd.arg("-o").arg(&file_descriptor_set_path);
944
945            for include in includes {
946                if include.as_ref().exists() {
947                    cmd.arg("-I").arg(include.as_ref());
948                } else {
949                    debug!(
950                        "ignoring {} since it does not exist.",
951                        include.as_ref().display()
952                    )
953                }
954            }
955
956            // Set the protoc include after the user includes in case the user wants to
957            // override one of the built-in .protos.
958            if let Some(protoc_include) = protoc_include_from_env() {
959                cmd.arg("-I").arg(protoc_include);
960            }
961
962            for arg in &self.protoc_args {
963                cmd.arg(arg);
964            }
965
966            for proto in protos {
967                cmd.arg(proto.as_ref());
968            }
969
970            debug!("Running: {cmd:?}");
971
972            let output = match cmd.output() {
973            Err(err) if ErrorKind::NotFound == err.kind() => return Err(Error::new(
974                err.kind(),
975                error_message_protoc_not_found()
976            )),
977            Err(err) => return Err(Error::new(
978                err.kind(),
979                format!("failed to invoke protoc (hint: https://docs.rs/prost-build/#sourcing-protoc): (path: {}): {}", &self.protoc_executable.display(), err),
980            )),
981            Ok(output) => output,
982        };
983
984            if !output.status.success() {
985                return Err(Error::new(
986                    ErrorKind::Other,
987                    format!("protoc failed: {}", String::from_utf8_lossy(&output.stderr)),
988                ));
989            }
990        }
991
992        let buf = fs::read(&file_descriptor_set_path).map_err(|e| {
993            Error::new(
994                e.kind(),
995                format!(
996                    "unable to open file_descriptor_set_path: {}, OS: {}",
997                    file_descriptor_set_path.display(),
998                    e
999                ),
1000            )
1001        })?;
1002        let file_descriptor_set = FileDescriptorSet::decode(buf.as_slice()).map_err(|error| {
1003            Error::new(
1004                ErrorKind::InvalidInput,
1005                format!("invalid FileDescriptorSet: {error}"),
1006            )
1007        })?;
1008
1009        Ok(file_descriptor_set)
1010    }
1011
1012    /// Compile `.proto` files into Rust files during a Cargo build with additional code generator
1013    /// configuration options.
1014    ///
1015    /// This method is like the `prost_build::compile_protos` function, with the added ability to
1016    /// specify non-default code generation options. See that function for more information about
1017    /// the arguments and generated outputs.
1018    ///
1019    /// The `protos` and `includes` arguments are ignored if `skip_protoc_run` is specified.
1020    ///
1021    /// # Example `build.rs`
1022    ///
1023    /// ```rust,no_run
1024    /// # use std::io::Result;
1025    /// fn main() -> Result<()> {
1026    ///   let mut prost_build = prost_build::Config::new();
1027    ///   prost_build.btree_map(&["."]);
1028    ///   prost_build.compile_protos(&["src/frontend.proto", "src/backend.proto"], &["src"])?;
1029    ///   Ok(())
1030    /// }
1031    /// ```
1032    pub fn compile_protos(
1033        &mut self,
1034        protos: &[impl AsRef<Path>],
1035        includes: &[impl AsRef<Path>],
1036    ) -> Result<()> {
1037        // TODO: This should probably emit 'rerun-if-changed=PATH' directives for cargo, however
1038        // according to [1] if any are output then those paths replace the default crate root,
1039        // which is undesirable. Figure out how to do it in an additive way; perhaps gcc-rs has
1040        // this figured out.
1041        // [1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
1042
1043        let file_descriptor_set = self.load_fds(protos, includes)?;
1044
1045        self.compile_fds(file_descriptor_set)
1046    }
1047
1048    pub(crate) fn prost_path_or_default(&self) -> &str {
1049        self.prost_path.as_deref().unwrap_or("::prost")
1050    }
1051
1052    pub(crate) fn prost_types_path_or_default(&self) -> &str {
1053        self.prost_types_path.as_deref().unwrap_or("::prost_types")
1054    }
1055
1056    pub(crate) fn write_includes(
1057        &self,
1058        mut modules: Vec<&Module>,
1059        outfile: &mut impl Write,
1060        basepath: Option<&PathBuf>,
1061        file_names: &HashMap<Module, String>,
1062    ) -> Result<()> {
1063        modules.sort();
1064
1065        let mut stack = Vec::new();
1066
1067        for module in modules {
1068            while !module.starts_with(&stack) {
1069                stack.pop();
1070                self.write_line(outfile, stack.len(), "}")?;
1071            }
1072            while stack.len() < module.len() {
1073                self.write_line(
1074                    outfile,
1075                    stack.len(),
1076                    &format!("pub mod {} {{", module.part(stack.len())),
1077                )?;
1078                stack.push(module.part(stack.len()).to_owned());
1079            }
1080
1081            let file_name = file_names
1082                .get(module)
1083                .expect("every module should have a filename");
1084
1085            if basepath.is_some() {
1086                self.write_line(outfile, stack.len(), &format!("include!(\"{file_name}\");"))?;
1087            } else {
1088                self.write_line(
1089                    outfile,
1090                    stack.len(),
1091                    &format!("include!(concat!(env!(\"OUT_DIR\"), \"/{file_name}\"));"),
1092                )?;
1093            }
1094        }
1095
1096        for depth in (0..stack.len()).rev() {
1097            self.write_line(outfile, depth, "}")?;
1098        }
1099
1100        Ok(())
1101    }
1102
1103    fn write_line(&self, outfile: &mut impl Write, depth: usize, line: &str) -> Result<()> {
1104        outfile.write_all(format!("{}{}\n", ("    ").to_owned().repeat(depth), line).as_bytes())
1105    }
1106
1107    /// Processes a set of modules and file descriptors, returning a map of modules to generated
1108    /// code contents.
1109    ///
1110    /// This is generally used when control over the output should not be managed by Prost,
1111    /// such as in a flow for a `protoc` code generating plugin. When compiling as part of a
1112    /// `build.rs` file, instead use [`Self::compile_protos()`].
1113    pub fn generate(
1114        &mut self,
1115        requests: Vec<(Module, FileDescriptorProto)>,
1116    ) -> Result<HashMap<Module, String>> {
1117        let mut modules = HashMap::new();
1118        let mut packages = HashMap::new();
1119
1120        let message_graph = MessageGraph::new(requests.iter().map(|x| &x.1));
1121        let extern_paths = ExternPaths::new(
1122            &self.extern_paths,
1123            self.prost_path_or_default(),
1124            self.prost_types_path_or_default(),
1125            self.prost_types,
1126        )
1127        .map_err(|error| Error::new(ErrorKind::InvalidInput, error))?;
1128        let mut context = Context::new(self, message_graph, extern_paths);
1129
1130        for (request_module, request_fd) in requests {
1131            // Only record packages that have services
1132            if !request_fd.service.is_empty() {
1133                packages.insert(request_module.clone(), request_fd.package().to_string());
1134            }
1135            let buf = modules
1136                .entry(request_module.clone())
1137                .or_insert_with(String::new);
1138            CodeGenerator::generate(&mut context, request_fd, buf);
1139            if buf.is_empty() {
1140                // Did not generate any code, remove from list to avoid inclusion in include file or output file list
1141                modules.remove(&request_module);
1142            }
1143        }
1144
1145        if let Some(service_generator) = context.service_generator_mut() {
1146            for (module, package) in packages {
1147                let buf = modules.get_mut(&module).unwrap();
1148                service_generator.finalize_package(&package, buf);
1149            }
1150        }
1151
1152        #[cfg(feature = "format")]
1153        if self.fmt {
1154            for buf in modules.values_mut() {
1155                let file = syn::parse_file(buf).unwrap();
1156                let formatted = prettyplease::unparse(&file);
1157                *buf = formatted;
1158            }
1159        }
1160
1161        self.add_generated_modules(&mut modules);
1162
1163        Ok(modules)
1164    }
1165
1166    fn add_generated_modules(&mut self, modules: &mut HashMap<Module, String>) {
1167        for buf in modules.values_mut() {
1168            let with_generated = "// This file is @generated by prost-build.\n".to_string() + buf;
1169            *buf = with_generated;
1170        }
1171    }
1172}
1173
1174/// Write a slice as the entire contents of a file.
1175///
1176/// This function will create a file if it does not exist,
1177/// and will entirely replace its contents if it does. When
1178/// the contents is already correct, it doesn't touch to the file.
1179fn write_file_if_changed(path: &Path, content: &[u8]) -> std::io::Result<()> {
1180    let previous_content = fs::read(path);
1181
1182    if previous_content
1183        .map(|previous_content| previous_content == content)
1184        .unwrap_or(false)
1185    {
1186        trace!("unchanged: {}", path.display());
1187        Ok(())
1188    } else {
1189        trace!("writing: {}", path.display());
1190        fs::write(path, content)
1191    }
1192}
1193
1194impl default::Default for Config {
1195    fn default() -> Config {
1196        Config {
1197            file_descriptor_set_path: None,
1198            service_generator: None,
1199            map_type: PathMap::default(),
1200            bytes_type: PathMap::default(),
1201            type_attributes: PathMap::default(),
1202            message_attributes: PathMap::default(),
1203            enum_attributes: PathMap::default(),
1204            field_attributes: PathMap::default(),
1205            boxed: PathMap::default(),
1206            prost_types: true,
1207            strip_enum_prefix: true,
1208            out_dir: None,
1209            extern_paths: Vec::new(),
1210            default_package_filename: "_".to_string(),
1211            enable_type_names: false,
1212            type_name_domains: PathMap::default(),
1213            protoc_args: Vec::new(),
1214            protoc_executable: protoc_from_env(),
1215            disable_comments: PathMap::default(),
1216            skip_debug: PathMap::default(),
1217            skip_protoc_run: false,
1218            skip_source_info: false,
1219            include_file: None,
1220            prost_path: None,
1221            prost_types_path: None,
1222            #[cfg(feature = "format")]
1223            fmt: true,
1224        }
1225    }
1226}
1227
1228impl fmt::Debug for Config {
1229    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
1230        fmt.debug_struct("Config")
1231            .field("file_descriptor_set_path", &self.file_descriptor_set_path)
1232            .field("service_generator", &self.service_generator.is_some())
1233            .field("map_type", &self.map_type)
1234            .field("bytes_type", &self.bytes_type)
1235            .field("type_attributes", &self.type_attributes)
1236            .field("field_attributes", &self.field_attributes)
1237            .field("prost_types", &self.prost_types)
1238            .field("strip_enum_prefix", &self.strip_enum_prefix)
1239            .field("out_dir", &self.out_dir)
1240            .field("extern_paths", &self.extern_paths)
1241            .field("default_package_filename", &self.default_package_filename)
1242            .field("enable_type_names", &self.enable_type_names)
1243            .field("type_name_domains", &self.type_name_domains)
1244            .field("protoc_args", &self.protoc_args)
1245            .field("disable_comments", &self.disable_comments)
1246            .field("skip_debug", &self.skip_debug)
1247            .field("prost_path", &self.prost_path)
1248            .finish()
1249    }
1250}
1251
1252pub fn error_message_protoc_not_found() -> String {
1253    let error_msg = "Could not find `protoc`. If `protoc` is installed, try setting the `PROTOC` environment variable to the path of the `protoc` binary.";
1254
1255    let os_specific_hint = if cfg!(target_os = "macos") {
1256        "To install it on macOS, run `brew install protobuf`."
1257    } else if cfg!(target_os = "linux") {
1258        "To install it on Debian, run `apt-get install protobuf-compiler`."
1259    } else {
1260        "Try installing `protobuf-compiler` or `protobuf` using your package manager."
1261    };
1262    let download_msg =
1263        "It is also available at https://github.com/protocolbuffers/protobuf/releases";
1264
1265    format!(
1266        "{error_msg} {os_specific_hint} {download_msg}  For more information: https://docs.rs/prost-build/#sourcing-protoc"
1267    )
1268}
1269
1270/// Returns the path to the `protoc` binary.
1271pub fn protoc_from_env() -> PathBuf {
1272    env::var_os("PROTOC")
1273        .map(PathBuf::from)
1274        .unwrap_or(PathBuf::from("protoc"))
1275}
1276
1277/// Returns the path to the Protobuf include directory.
1278pub fn protoc_include_from_env() -> Option<PathBuf> {
1279    let protoc_include: PathBuf = env::var_os("PROTOC_INCLUDE")?.into();
1280
1281    if !protoc_include.exists() {
1282        panic!(
1283            "PROTOC_INCLUDE environment variable points to non-existent directory ({})",
1284            protoc_include.display()
1285        );
1286    }
1287    if !protoc_include.is_dir() {
1288        panic!(
1289            "PROTOC_INCLUDE environment variable points to a non-directory file ({})",
1290            protoc_include.display()
1291        );
1292    }
1293
1294    Some(protoc_include)
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299    use super::*;
1300
1301    macro_rules! assert_starts_with {
1302        ($left:expr, $right:expr) => {
1303            match (&$left, &$right) {
1304                (left_val, right_val) => {
1305                    if !(left_val.starts_with(right_val)) {
1306                        panic!(
1307                            "assertion 'starts_with` failed:\nleft: {}\nright: {}",
1308                            left_val, right_val
1309                        )
1310                    }
1311                }
1312            }
1313        };
1314    }
1315
1316    #[test]
1317    fn test_error_protoc_not_found() {
1318        let mut config = Config::new();
1319        config.protoc_executable("path-does-not-exist");
1320
1321        let err = config.load_fds(&[""], &[""]).unwrap_err();
1322        assert_eq!(err.to_string(), error_message_protoc_not_found())
1323    }
1324
1325    #[test]
1326    fn test_error_protoc_not_executable() {
1327        let mut config = Config::new();
1328        config.protoc_executable("src/lib.rs");
1329
1330        let err = config.load_fds(&[""], &[""]).unwrap_err();
1331        assert_starts_with!(err.to_string(), "failed to invoke protoc (hint: https://docs.rs/prost-build/#sourcing-protoc): (path: src/lib.rs): ")
1332    }
1333
1334    #[test]
1335    fn test_error_incorrect_skip_protoc_run() {
1336        let mut config = Config::new();
1337        config.skip_protoc_run();
1338
1339        let err = config.load_fds(&[""], &[""]).unwrap_err();
1340        assert_eq!(
1341            err.to_string(),
1342            "file_descriptor_set_path is required with skip_protoc_run"
1343        )
1344    }
1345
1346    #[test]
1347    fn test_error_protoc_failed() {
1348        let mut config = Config::new();
1349
1350        let err = config.load_fds(&[""], &[""]).unwrap_err();
1351        assert_starts_with!(
1352            err.to_string(),
1353            "protoc failed: You seem to have passed an empty string as one of the arguments to "
1354        )
1355    }
1356
1357    #[test]
1358    fn test_error_non_existing_file_descriptor_set() {
1359        let mut config = Config::new();
1360        config.skip_protoc_run();
1361        config.file_descriptor_set_path("path-does-not-exist");
1362
1363        let err = config.load_fds(&[""], &[""]).unwrap_err();
1364        assert_starts_with!(
1365            err.to_string(),
1366            "unable to open file_descriptor_set_path: path-does-not-exist, OS: "
1367        )
1368    }
1369
1370    #[test]
1371    fn test_error_text_incorrect_file_descriptor_set() {
1372        let mut config = Config::new();
1373        config.skip_protoc_run();
1374        config.file_descriptor_set_path("src/lib.rs");
1375
1376        let err = config.load_fds(&[""], &[""]).unwrap_err();
1377        assert_eq!(
1378            err.to_string(),
1379            "invalid FileDescriptorSet: failed to decode Protobuf message: unexpected end group tag"
1380        )
1381    }
1382
1383    #[test]
1384    fn test_error_unset_out_dir() {
1385        let mut config = Config::new();
1386
1387        let err = config
1388            .compile_fds(FileDescriptorSet::default())
1389            .unwrap_err();
1390        assert_eq!(err.to_string(), "OUT_DIR environment variable is not set")
1391    }
1392}