Skip to main content

conjure_codegen/
lib.rs

1// Copyright 2018 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Code generation for Conjure definitions.
15//!
16//! # Examples
17//!
18//! Code generation via a build script, assuming we have a `service-api.conjure.json` file in the crate root:
19//!
20//! build.rs:
21//!
22//! ```no_run
23//! use std::env;
24//! use std::path::Path;
25//!
26//! fn main() {
27//!     let input = "service-api.conjure.json";
28//!     let output = Path::new(&env::var_os("OUT_DIR").unwrap()).join("service_api");
29//!
30//!     println!("cargo:rerun-if-changed={}", input);
31//!     conjure_codegen::Config::new()
32//!         .strip_prefix("com.foobar.service".to_string())
33//!         .generate_files(input, output)
34//!         .unwrap();
35//! }
36//! ```
37//!
38//! src/lib.rs:
39//!
40//! ```ignore
41//! mod service_api {
42//!     include!(concat!(env!("OUT_DIR"), "/service_api/mod.rs"));
43//! }
44//! ```
45//!
46//! # Types
47//!
48//! ## Builtin
49//!
50//! Builtin types map directly to existing Rust types:
51//!
52//! | Conjure       | Rust                                 |
53//! | ------------- | ------------------------------------ |
54//! | `string`      | `String`                             |
55//! | `datetime`    | `chrono::DateTime<Utc>`              |
56//! | `integer`     | `i32`                                |
57//! | `double`      | `f64`                                |
58//! | `safelong`    | `conjure_object::SafeLong`           |
59//! | `binary`      | `bytes::Bytes`                       |
60//! | `any`         | `conjure_object::Any`                |
61//! | `boolean`     | `bool`                               |
62//! | `uuid`        | `uuid::Uuid`                         |
63//! | `rid`         | `conjure_object::ResourceIdentifier` |
64//! | `bearertoken` | `conjure_object::BearerToken`        |
65//! | `optional<T>` | `Option<T>`                          |
66//! | `list<T>`     | `Vec<T>`                             |
67//! | `set<T>`      | `BTreeSet<T>`                        |
68//! | `map<K, V>`   | `BTreeMap<K, V>`                     |
69//!
70//! Many of these are exposed by the `conjure-object` crate, which is a required dependency of crates containing the
71//! generated code.
72//!
73//! ### `double`
74//!
75//! Rust's `f64` type does not implement `Ord`, `Eq`, or `Hash`, which requires some special casing. Sets and maps keyed
76//! directly by `double` are represented as `BTreeSet<DoubleKey>` and `BTreeMap<DoubleKey, T>`, where the
77//! [`DoubleKey`](conjure_object::DoubleKey) type wraps `f64` and implements those traits by ordering `NaN` greater
78//! than all other values and equal to itself.
79//!
80//! Conjure aliases, objects, and unions wrapping `double` types have trait implementations which use the same logic.
81//!
82//! ## Objects
83//!
84//! Conjure objects turn into Rust structs along with builders used to construct them:
85//!
86//! ```
87//! # use conjure_codegen::example_types::objects::product::{ManyFieldExample, StringAliasExample};
88//! let object = ManyFieldExample::builder()
89//!     .string("foo")
90//!     .integer(123)
91//!     .double_value(3.14)
92//!     .alias(StringAliasExample("foobar".to_string()))
93//!     .optional_item("bar".to_string())
94//!     .items(vec!["hello".to_string(), "world".to_string()])
95//!     .build();
96//!
97//! assert_eq!(object.string(), "foo");
98//! assert_eq!(object.optional_item(), Some("bar"));
99//! ```
100//!
101//! The builder types are by-value and infallible - the compiler will prevent code from compiling if all required
102//! fields are not set. The API requires that all required fields be set first strictly in declaration order,
103//! after which optional fields can be set in any order.
104//!
105//! Objects with 3 or fewer required fields also have an explicit constructor:
106//!
107//! ```rust
108//! # use conjure_codegen::example_types::objects::product::BooleanExample;
109//! let object = BooleanExample::new(true);
110//!
111//! assert_eq!(object.coin(), true);
112//! ```
113//!
114//! By default, struct fields are private and have accessor methods. The [`Config::public_fields`] option can be set to
115//! make the fields public.
116//!
117//! The generated structs implement `Debug`, `Clone`, `PartialEq`, Eq, `PartialOrd`, `Ord`, `Hash`, `Serialize`, and
118//! `Deserialize`. They implement `Copy` if they consist entirely of copyable primitive types.
119//!
120//! ## Unions
121//!
122//! Conjure unions turn into Rust enums. By default, unions are *extensible* through an additional `Unknown` variant.
123//! This allows unions to be forward-compatible by allowing clients to deserialize variants they don't yet know about
124//! and reserialize them properly:
125//!
126//! ```
127//! # use conjure_codegen::example_types::objects::product::UnionTypeExample;
128//! # let union_value = UnionTypeExample::If(0);
129//! match union_value {
130//!     UnionTypeExample::StringExample(string) => {
131//!         // ...
132//!     }
133//!     UnionTypeExample::Set(set) => {
134//!         // ...
135//!     }
136//!     // ...
137//!     UnionTypeExample::Unknown(unknown) => {
138//!         println!("got unknown variant: {}", unknown.type_());
139//!     }
140//!     # _ => {}
141//! }
142//! ```
143//!
144//! The generated enums implement `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Serialize`, and
145//! `Deserialize`. Union variants which are themselves unions are boxed in the generated enum to avoid self-referential
146//! type definitions.
147//!
148//! ## Enums
149//!
150//! Conjure enums turn into Rust enums. By default, enums are *extensible*. This allows enums to be forward-compatible
151//! by allowing clients to deserialize variants they don't yet know about and reserialize them properly:
152//!
153//! ```
154//! # use conjure_codegen::example_types::objects::product::EnumExample;
155//! # let enum_value = EnumExample::One;
156//! match enum_value {
157//!     EnumExample::One => println!("found one"),
158//!     EnumExample::Two => println!("found two"),
159//!     EnumExample::Unknown(unknown) => println!("got unknown variant: {}", unknown),
160//! }
161//! ```
162//!
163//! The generated enums implement `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Display`,
164//! `Serialize`, and `Deserialize`.
165//!
166//! ## Aliases
167//!
168//! Conjure aliases turn into Rust newtype structs that act like their inner value:
169//!
170//! ```
171//! # use conjure_codegen::example_types::objects::product::StringAliasExample;
172//! let alias_value = StringAliasExample("hello world".to_string());
173//! assert!(alias_value.starts_with("hello"));
174//! ```
175//!
176//! The generated structs implement `Deref`, `DerefMut`, `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`,
177//! `Hash`, `Serialize`, and `Deserialize`. They implement `Copy` if they wrap a copyable primitive type, `Default`
178//! if they wrap a type implementing `Default`, `FromIterator` if they wrap a type implementing `FromIterator`, and
179//! `Display` if they wrap a type implementing `Display`. They also implement `From<T>`, where `T` is the fully
180//! de-aliased inner type.
181//!
182//! ## Errors
183//!
184//! Conjure errors turn into Rust structs storing the error's parameters as if it were a Conjure object. The struct
185//! additionally implements the `conjure_error::ErrorType` trait which encodes the extra error metadata:
186//!
187//! ```
188//! # use conjure_codegen::example_types::errors::product::InvalidServiceDefinition;
189//! # let (name, definition) = ("", "");
190//! use conjure_error::{ErrorType, ErrorCode};
191//!
192//! assert_eq!(InvalidServiceDefinition::code(), ErrorCode::InvalidArgument);
193//! assert_eq!(InvalidServiceDefinition::name(), "Conjure:InvalidServiceDefinition");
194//! ```
195//!
196//! ## Services
197//!
198//! Conjure services turn into client- and server-side interfaces:
199//!
200//! ### Clients
201//!
202//! The client object wraps a raw HTTP client and provides methods to interact with the service's endpoints. Both
203//! synchronous and asynchronous clients are provided:
204//!
205//! Synchronous:
206//! ```
207//! use conjure_http::client::Service;
208//! # use conjure_codegen::example_types::clients::another::{TestService, TestServiceClient};
209//! # fn foo<T: conjure_http::client::Client>(http_client: T, runtime: &std::sync::Arc<conjure_http::client::ConjureRuntime>) -> Result<(), conjure_error::Error> {
210//! # let auth_token = "foobar".parse().unwrap();
211//! let client = TestServiceClient::new(http_client, runtime);
212//! let file_systems = client.get_file_systems(&auth_token)?;
213//! # Ok(())
214//! # }
215//! ```
216//!
217//! Asynchronous:
218//! ```
219//! use conjure_http::client::AsyncService;
220//! # use conjure_codegen::example_types::clients::another::{AsyncTestService, AsyncTestServiceClient};
221//! # async fn foo<T>(http_client: T, runtime: &std::sync::Arc<conjure_http::client::ConjureRuntime>) -> Result<(), conjure_error::Error> where T: conjure_http::client::AsyncClient + Sync + Send, T::ResponseBody: 'static + Send {
222//! # let auth_token = "foobar".parse().unwrap();
223//! let client = AsyncTestServiceClient::new(http_client, runtime);
224//! let file_systems = client.get_file_systems(&auth_token).await?;
225//! # Ok(())
226//! # }
227//! ```
228//!
229//! ### Servers
230//!
231//! Conjure generates a trait and accompanying wrapper resource which are used to implement the service's endpoints.
232//! Both synchronous and asynchronous servers are supported:
233//!
234//! Synchronous:
235//! ```ignore
236//! struct TestServiceHandler;
237//!
238//! impl<T> TestService<T> for TestServiceHandler
239//! where
240//!     T: Read
241//! {
242//!     fn get_file_systems(
243//!         &self,
244//!         auth: AuthToken,
245//!     ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
246//!         // ...
247//!     }
248//!
249//!     // ...
250//! }
251//!
252//! let resource = TestServiceResource::new(TestServiceHandler);
253//! http_server.register(resource);
254//! ```
255//!
256//! Asynchronous:
257//! ```ignore
258//! struct TestServiceHandler;
259//!
260//! impl<T> AsyncTestService<T> for TestServiceHandler
261//! where
262//!     T: AsyncRead + 'static + Send
263//! {
264//!     async fn get_file_systems(
265//!         &self,
266//!         auth: AuthToken,
267//!     ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
268//!         // ...
269//!     }
270//!
271//!     // ...
272//! }
273//!
274//! let resource = TestServiceResource::new(TestServiceHandler);
275//! http_server.register(resource);
276//! ```
277//!
278//! ### Endpoint Tags
279//!
280//! * `server-request-context` - The generated server trait method will have an additional
281//!   `RequestContext` argument providing lower level access to request and response information.
282//! * `server-limit-request-size: <size>` - Sets the maximum request body size for endpoints with
283//!   serializable request bodies. `<size>` should be a human-readable byte count (e.g. `50Mi` or
284//!   `100Ki`). Defaults to `50Mi`.
285#![warn(clippy::all, missing_docs)]
286#![allow(clippy::needless_doctest_main)]
287#![recursion_limit = "256"]
288
289use crate::context::Context;
290use crate::merge_toml::left_merge;
291use crate::types::objects::{ConjureDefinition, TypeDefinition};
292use anyhow::{bail, Context as _, Error};
293use context::BaseModule;
294use proc_macro2::TokenStream;
295use quote::quote;
296use std::collections::BTreeMap;
297use std::env;
298use std::fs;
299use std::path::Path;
300use toml::Value;
301
302mod aliases;
303mod cargo_toml;
304mod clients;
305mod context;
306mod enums;
307mod errors;
308mod merge_toml;
309mod objects;
310mod servers;
311#[allow(dead_code, clippy::all)]
312#[rustfmt::skip]
313mod types;
314mod human_size;
315mod unions;
316
317/// Examples of generated Conjure code.
318///
319/// This module is only intended to be present in documentation; it shouldn't be relied on by any library code.
320#[cfg(feature = "example-types")]
321#[allow(warnings)]
322#[rustfmt::skip]
323pub mod example_types;
324
325struct CrateInfo {
326    name: String,
327    version: String,
328}
329
330/// Codegen configuration.
331pub struct Config {
332    exhaustive: bool,
333    serialize_empty_collections: bool,
334    use_legacy_error_serialization: bool,
335    public_fields: bool,
336    strip_prefix: Option<String>,
337    version: Option<String>,
338    build_crate: Option<CrateInfo>,
339    extra_manifest_config: Option<Value>,
340}
341
342impl Default for Config {
343    fn default() -> Config {
344        Config::new()
345    }
346}
347
348impl Config {
349    /// Creates a new `Config` with default settings.
350    pub fn new() -> Config {
351        Config {
352            exhaustive: false,
353            serialize_empty_collections: false,
354            use_legacy_error_serialization: true,
355            public_fields: false,
356            strip_prefix: None,
357            version: None,
358            build_crate: None,
359            extra_manifest_config: None,
360        }
361    }
362
363    /// Controls exhaustive matchability of unions and enums.
364    ///
365    /// Non-exhaustive unions and enums have the ability to deserialize and reserialize unknown variants. This enables
366    /// clients to be more forward-compatible with changes made by newer servers. Note that this is distinct from the
367    /// Rust concept of exhaustive matching.
368    ///
369    /// Defaults to `false`.
370    pub fn exhaustive(&mut self, exhaustive: bool) -> &mut Config {
371        self.exhaustive = exhaustive;
372        self
373    }
374
375    /// Controls serialization of empty collection fields in objects.
376    ///
377    /// Some Conjure implementations don't properly handle deserialization of objects when empty collections are
378    /// omitted. Enabling this option will cause empty optional, set, list, and map fields to be included in the
379    /// serialized output.
380    ///
381    /// Defaults to `false`.
382    pub fn serialize_empty_collections(
383        &mut self,
384        serialize_empty_collections: bool,
385    ) -> &mut Config {
386        self.serialize_empty_collections = serialize_empty_collections;
387        self
388    }
389
390    /// Parameters of service errors were historically stringified when serialized into response bodies.
391    ///
392    /// Setting this to `false` will cause error parameters to be serialized directly as their underlying types would
393    /// be.
394    ///
395    /// Defaults to `true`.
396    pub fn use_legacy_error_serialization(
397        &mut self,
398        use_legacy_error_serialization: bool,
399    ) -> &mut Config {
400        self.use_legacy_error_serialization = use_legacy_error_serialization;
401        self
402    }
403
404    /// Controls visibility of fields in objects.
405    ///
406    /// If enabled, fields will be public, but the struct will be annotated `#[non_exhaustive]`. If disabled, fields
407    /// will be private and accessor methods will be defined. Note that this is separate from the Conjure concept of
408    /// non-exhaustive deserialization.
409    ///
410    /// Defaults to `false`.
411    pub fn public_fields(&mut self, public_fields: bool) -> &mut Config {
412        self.public_fields = public_fields;
413        self
414    }
415
416    /// Sets a prefix that will be stripped from package names.
417    ///
418    /// Defaults to `None`.
419    pub fn strip_prefix<T>(&mut self, strip_prefix: T) -> &mut Config
420    where
421        T: Into<Option<String>>,
422    {
423        self.strip_prefix = strip_prefix.into();
424        self
425    }
426
427    /// Sets the version included in endpoint metadata for generated client bindings.
428    ///
429    /// Defaults to the version passed to [`Self::build_crate`], or `None` otherwise.
430    pub fn version<T>(&mut self, version: T) -> &mut Config
431    where
432        T: Into<Option<String>>,
433    {
434        self.version = version.into();
435        self
436    }
437
438    /// Sets extra manifest configuration to be merged into the generated Cargo.toml.
439    ///
440    /// Defaults to `None`
441    pub fn extra_manifest_config<T>(&mut self, config: T) -> &mut Config
442    where
443        T: Into<Option<toml::Table>>,
444    {
445        self.extra_manifest_config = config.into().map(Value::Table);
446        self
447    }
448
449    /// Switches generation to create a full crate.
450    ///
451    /// Defaults to just generating a single module.
452    pub fn build_crate(&mut self, name: &str, version: &str) -> &mut Config {
453        self.build_crate = Some(CrateInfo {
454            name: name.to_string(),
455            version: version.to_string(),
456        });
457        self
458    }
459
460    /// Generates Rust source files from a JSON-encoded Conjure IR file.
461    pub fn generate_files<P, Q>(&self, ir_file: P, out_dir: Q) -> Result<(), Error>
462    where
463        P: AsRef<Path>,
464        Q: AsRef<Path>,
465    {
466        self.generate_files_inner(ir_file.as_ref(), out_dir.as_ref())
467    }
468
469    fn generate_files_inner(&self, ir_file: &Path, out_dir: &Path) -> Result<(), Error> {
470        let defs = self.parse_ir(ir_file)?;
471
472        if defs.version() != 1 {
473            bail!("unsupported IR version {}", defs.version());
474        }
475
476        let modules = self.create_modules(&defs);
477        let (src_dir, lib_root) = if self.build_crate.is_some() {
478            (out_dir.join("src"), true)
479        } else {
480            (out_dir.to_path_buf(), false)
481        };
482
483        if let Some(info) = &self.build_crate {
484            self.write_cargo_toml(out_dir, info, &defs)?;
485            self.write_rustfmt_toml(out_dir)?;
486        }
487
488        modules.render(&src_dir, lib_root)?;
489
490        Ok(())
491    }
492
493    fn parse_ir(&self, ir_file: &Path) -> Result<ConjureDefinition, Error> {
494        let ir = fs::read_to_string(ir_file)
495            .with_context(|| format!("error reading file {}", ir_file.display()))?;
496
497        let defs = conjure_serde::json::client_from_str(&ir)
498            .with_context(|| format!("error parsing Conjure IR file {}", ir_file.display()))?;
499
500        Ok(defs)
501    }
502
503    fn create_modules(&self, defs: &ConjureDefinition) -> ModuleTrie {
504        let context = Context::new(
505            defs,
506            self.exhaustive,
507            self.serialize_empty_collections,
508            self.use_legacy_error_serialization,
509            self.public_fields,
510            self.strip_prefix.as_deref(),
511            self.version
512                .as_deref()
513                .or_else(|| self.build_crate.as_ref().map(|v| &*v.version)),
514        );
515
516        let mut root = ModuleTrie::new();
517
518        for def in defs.types() {
519            let (type_name, contents) = match def {
520                TypeDefinition::Enum(def) => (def.type_name(), enums::generate(&context, def)),
521                TypeDefinition::Alias(def) => (def.type_name(), aliases::generate(&context, def)),
522                TypeDefinition::Union(def) => (def.type_name(), unions::generate(&context, def)),
523                TypeDefinition::Object(def) => (
524                    def.type_name(),
525                    objects::generate(&context, BaseModule::Objects, def),
526                ),
527            };
528
529            let type_ = Type {
530                module_name: context.module_name(type_name),
531                type_names: vec![context.type_name(type_name.name()).to_string()],
532                contents,
533            };
534            root.insert(&context.module_path(BaseModule::Objects, type_name), type_);
535        }
536
537        for def in defs.errors() {
538            let type_ = Type {
539                module_name: context.module_name(def.error_name()),
540                type_names: vec![context.type_name(def.error_name().name()).to_string()],
541                contents: errors::generate(&context, def),
542            };
543            root.insert(
544                &context.module_path(BaseModule::Errors, def.error_name()),
545                type_,
546            );
547        }
548
549        for def in defs.services() {
550            let client = clients::generate(&context, def);
551
552            let contents = quote! {
553                #client
554            };
555            let type_ = Type {
556                module_name: context.module_name(def.service_name()),
557                type_names: vec![
558                    format!("{}", def.service_name().name()),
559                    format!("{}Client", def.service_name().name()),
560                    format!("Async{}", def.service_name().name()),
561                    format!("Async{}Client", def.service_name().name()),
562                ],
563                contents,
564            };
565            root.insert(
566                &context.module_path(BaseModule::Clients, def.service_name()),
567                type_,
568            );
569
570            let server = servers::generate(&context, def);
571
572            let contents = quote! {
573                #server
574            };
575            let type_ = Type {
576                module_name: context.module_name(def.service_name()),
577                type_names: vec![
578                    context.type_name(def.service_name().name()).to_string(),
579                    format!("Async{}", def.service_name().name()),
580                    format!("{}Endpoints", def.service_name().name()),
581                    format!("Async{}Endpoints", def.service_name().name()),
582                ],
583                contents,
584            };
585            root.insert(
586                &context.module_path(BaseModule::Endpoints, def.service_name()),
587                type_,
588            );
589        }
590
591        root.deconflict();
592
593        root
594    }
595
596    fn write_cargo_toml(
597        &self,
598        dir: &Path,
599        info: &CrateInfo,
600        def: &ConjureDefinition,
601    ) -> Result<(), Error> {
602        fs::create_dir_all(dir)
603            .with_context(|| format!("error creating directory {}", dir.display()))?;
604
605        let metadata = def
606            .extensions()
607            .get("recommended-product-dependencies")
608            .map(|deps| cargo_toml::Metadata {
609                sls: cargo_toml::Sls {
610                    recommended_product_dependencies: deps,
611                },
612            });
613
614        let mut needs_object = false;
615        let mut needs_error = false;
616        let mut needs_http = false;
617
618        if !def.types().is_empty() {
619            needs_object = true;
620        }
621
622        if !def.errors().is_empty() {
623            needs_object = true;
624            needs_error = true;
625        }
626
627        if !def.services().is_empty() {
628            needs_http = true;
629            needs_object = true;
630        }
631
632        let conjure_version = env!("CARGO_PKG_VERSION");
633        let mut dependencies = BTreeMap::new();
634        if needs_object {
635            dependencies.insert("conjure-object", conjure_version);
636        }
637        if needs_error {
638            dependencies.insert("conjure-error", conjure_version);
639        }
640        if needs_http {
641            dependencies.insert("conjure-http", conjure_version);
642        }
643
644        let manifest = cargo_toml::Manifest {
645            package: cargo_toml::Package {
646                name: &info.name,
647                version: &info.version,
648                edition: "2018",
649                metadata,
650            },
651            dependencies,
652        };
653
654        let manifest = if let Some(extra_manifest_toml) = self.extra_manifest_config.as_ref() {
655            let mut manifest_toml = toml::Value::Table(toml::Table::try_from(&manifest)?);
656            left_merge(&mut manifest_toml, extra_manifest_toml)?;
657            toml::to_string_pretty(&manifest_toml).unwrap()
658        } else {
659            toml::to_string_pretty(&manifest).unwrap()
660        };
661
662        let file = dir.join("Cargo.toml");
663
664        fs::write(&file, manifest)
665            .with_context(|| format!("error writing manifest file {}", file.display()))?;
666
667        Ok(())
668    }
669
670    fn write_rustfmt_toml(&self, dir: &Path) -> Result<(), Error> {
671        let contents = "\
672disable_all_formatting = true
673";
674
675        let file = dir.join("rustfmt.toml");
676
677        fs::write(file, contents).with_context(|| "error writing rustfmt.toml")?;
678
679        Ok(())
680    }
681}
682
683struct Type {
684    module_name: String,
685    type_names: Vec<String>,
686    contents: TokenStream,
687}
688
689struct ModuleTrie {
690    submodules: BTreeMap<String, ModuleTrie>,
691    types: Vec<Type>,
692}
693
694impl ModuleTrie {
695    fn new() -> ModuleTrie {
696        ModuleTrie {
697            submodules: BTreeMap::new(),
698            types: vec![],
699        }
700    }
701
702    fn insert(&mut self, module_path: &[String], type_: Type) {
703        match module_path.split_first() {
704            Some((first, rest)) => self
705                .submodules
706                .entry(first.clone())
707                .or_insert_with(ModuleTrie::new)
708                .insert(rest, type_),
709            None => self.types.push(type_),
710        }
711    }
712
713    fn deconflict(&mut self) {
714        for type_ in &mut self.types {
715            if self.submodules.contains_key(&type_.module_name) {
716                type_.module_name.push('_');
717            }
718        }
719
720        for submodule in self.submodules.values_mut() {
721            submodule.deconflict();
722        }
723    }
724
725    fn render(&self, dir: &Path, lib_root: bool) -> Result<(), Error> {
726        fs::create_dir_all(dir)
727            .with_context(|| format!("error creating directory {}", dir.display()))?;
728
729        for type_ in &self.types {
730            self.write_module(
731                &dir.join(format!("{}.rs", type_.module_name)),
732                &type_.contents,
733            )?;
734        }
735
736        for (name, module) in &self.submodules {
737            module.render(&dir.join(name), false)?;
738        }
739
740        let root = self.create_root_module(lib_root);
741        let file_name = if lib_root { "lib.rs" } else { "mod.rs" };
742        self.write_module(&dir.join(file_name), &root)?;
743
744        Ok(())
745    }
746
747    fn write_module(&self, path: &Path, contents: &TokenStream) -> Result<(), Error> {
748        let file = syn::parse2(contents.clone())?;
749        let formatted = prettyplease::unparse(&file);
750
751        fs::write(path, formatted)
752            .with_context(|| format!("error writing module {}", path.display()))?;
753        Ok(())
754    }
755
756    fn create_root_module(&self, lib_root: bool) -> TokenStream {
757        let attrs = if lib_root {
758            quote! {
759                #![allow(warnings)]
760            }
761        } else {
762            quote! {}
763        };
764
765        let uses = self.types.iter().map(|m| {
766            let module_name = m.module_name.parse::<TokenStream>().unwrap();
767            let type_names = m
768                .type_names
769                .iter()
770                .map(|n| n.parse::<TokenStream>().unwrap());
771            quote! {
772                #[doc(inline)]
773                pub use self::#module_name::{#(#type_names),*};
774            }
775        });
776
777        let type_mods = self.types.iter().map(|m| {
778            let module_name = m.module_name.parse::<TokenStream>().unwrap();
779            quote! {
780                pub mod #module_name;
781            }
782        });
783
784        let sub_mods = self.submodules.keys().map(|v| {
785            let module_name = v.parse::<TokenStream>().unwrap();
786            quote! {
787                pub mod #module_name;
788            }
789        });
790
791        quote! {
792            #attrs
793            #(#uses)*
794
795            #(#type_mods)*
796            #(#sub_mods)*
797        }
798    }
799}