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//! The generated structs implement `Debug`, `Clone`, `PartialEq`, Eq, `PartialOrd`, `Ord`, `Hash`, `Serialize`, and
115//! `Deserialize`. They implement `Copy` if they consist entirely of copyable primitive types.
116//!
117//! ## Unions
118//!
119//! Conjure unions turn into Rust enums. By default, unions are *extensible* through an additional `Unknown` variant.
120//! This allows unions to be forward-compatible by allowing clients to deserialize variants they don't yet know about
121//! and reserialize them properly:
122//!
123//! ```
124//! # use conjure_codegen::example_types::objects::product::UnionTypeExample;
125//! # let union_value = UnionTypeExample::If(0);
126//! match union_value {
127//!     UnionTypeExample::StringExample(string) => {
128//!         // ...
129//!     }
130//!     UnionTypeExample::Set(set) => {
131//!         // ...
132//!     }
133//!     // ...
134//!     UnionTypeExample::Unknown(unknown) => {
135//!         println!("got unknown variant: {}", unknown.type_());
136//!     }
137//!     # _ => {}
138//! }
139//! ```
140//!
141//! The generated enums implement `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Serialize`, and
142//! `Deserialize`. Union variants which are themselves unions are boxed in the generated enum to avoid self-referential
143//! type definitions.
144//!
145//! ## Enums
146//!
147//! Conjure enums turn into Rust enums. By default, enums are *extensible*. This allows enums to be forward-compatible
148//! by allowing clients to deserialize variants they don't yet know about and reserialize them properly:
149//!
150//! ```
151//! # use conjure_codegen::example_types::objects::product::EnumExample;
152//! # let enum_value = EnumExample::One;
153//! match enum_value {
154//!     EnumExample::One => println!("found one"),
155//!     EnumExample::Two => println!("found two"),
156//!     EnumExample::Unknown(unknown) => println!("got unknown variant: {}", unknown),
157//! }
158//! ```
159//!
160//! The generated enums implement `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Display`,
161//! `Serialize`, and `Deserialize`.
162//!
163//! ## Aliases
164//!
165//! Conjure aliases turn into Rust newtype structs that act like their inner value:
166//!
167//! ```
168//! # use conjure_codegen::example_types::objects::product::StringAliasExample;
169//! let alias_value = StringAliasExample("hello world".to_string());
170//! assert!(alias_value.starts_with("hello"));
171//! ```
172//!
173//! The generated structs implement `Deref`, `DerefMut`, `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`,
174//! `Hash`, `Serialize`, and `Deserialize`. They implement `Copy` if they wrap a copyable primitive type, `Default`
175//! if they wrap a type implementing `Default`, `FromIterator` if they wrap a type implementing `FromIterator`, and
176//! `Display` if they wrap a type implementing `Display`. They also implement `From<T>`, where `T` is the fully
177//! de-aliased inner type.
178//!
179//! ## Errors
180//!
181//! Conjure errors turn into Rust structs storing the error's parameters as if it were a Conjure object. The struct
182//! additionally implements the `conjure_error::ErrorType` trait which encodes the extra error metadata:
183//!
184//! ```
185//! # use conjure_codegen::example_types::errors::product::InvalidServiceDefinition;
186//! # let (name, definition) = ("", "");
187//! use conjure_error::{ErrorType, ErrorCode};
188//!
189//! assert_eq!(InvalidServiceDefinition::code(), ErrorCode::InvalidArgument);
190//! assert_eq!(InvalidServiceDefinition::name(), "Conjure:InvalidServiceDefinition");
191//! ```
192//!
193//! ## Services
194//!
195//! Conjure services turn into client- and server-side interfaces:
196//!
197//! ### Clients
198//!
199//! The client object wraps a raw HTTP client and provides methods to interact with the service's endpoints. Both
200//! synchronous and asynchronous clients are provided:
201//!
202//! Synchronous:
203//! ```
204//! use conjure_http::client::Service;
205//! # use conjure_codegen::example_types::clients::another::{TestService, TestServiceClient};
206//! # fn foo<T: conjure_http::client::Client>(http_client: T, runtime: &std::sync::Arc<conjure_http::client::ConjureRuntime>) -> Result<(), conjure_error::Error> {
207//! # let auth_token = "foobar".parse().unwrap();
208//! let client = TestServiceClient::new(http_client, runtime);
209//! let file_systems = client.get_file_systems(&auth_token)?;
210//! # Ok(())
211//! # }
212//! ```
213//!
214//! Asynchronous:
215//! ```
216//! use conjure_http::client::AsyncService;
217//! # use conjure_codegen::example_types::clients::another::{AsyncTestService, AsyncTestServiceClient};
218//! # 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 {
219//! # let auth_token = "foobar".parse().unwrap();
220//! let client = AsyncTestServiceClient::new(http_client, runtime);
221//! let file_systems = client.get_file_systems(&auth_token).await?;
222//! # Ok(())
223//! # }
224//! ```
225//!
226//! ### Servers
227//!
228//! Conjure generates a trait and accompanying wrapper resource which are used to implement the service's endpoints.
229//! Both synchronous and asynchronous servers are supported:
230//!
231//! Synchronous:
232//! ```ignore
233//! struct TestServiceHandler;
234//!
235//! impl<T> TestService<T> for TestServiceHandler
236//! where
237//!     T: Read
238//! {
239//!     fn get_file_systems(
240//!         &self,
241//!         auth: AuthToken,
242//!     ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
243//!         // ...
244//!     }
245//!
246//!     // ...
247//! }
248//!
249//! let resource = TestServiceResource::new(TestServiceHandler);
250//! http_server.register(resource);
251//! ```
252//!
253//! Asynchronous:
254//! ```ignore
255//! struct TestServiceHandler;
256//!
257//! impl<T> AsyncTestService<T> for TestServiceHandler
258//! where
259//!     T: AsyncRead + 'static + Send
260//! {
261//!     async fn get_file_systems(
262//!         &self,
263//!         auth: AuthToken,
264//!     ) -> Result<BTreeMap<String, BackingFileSystem>, Error> {
265//!         // ...
266//!     }
267//!
268//!     // ...
269//! }
270//!
271//! let resource = TestServiceResource::new(TestServiceHandler);
272//! http_server.register(resource);
273//! ```
274//!
275//! ### Endpoint Tags
276//!
277//! * `server-request-context` - The generated server trait method will have an additional
278//!   `RequestContext` argument providing lower level access to request and response information.
279//! * `server-limit-request-size: <size>` - Sets the maximum request body size for endpoints with
280//!   serializable request bodies. `<size>` should be a human-readable byte count (e.g. `50Mi` or
281//!   `100Ki`). Defaults to `50Mi`.
282#![warn(clippy::all, missing_docs)]
283#![allow(clippy::needless_doctest_main)]
284#![recursion_limit = "256"]
285
286use crate::context::Context;
287use crate::merge_toml::left_merge;
288use crate::types::objects::{ConjureDefinition, TypeDefinition};
289use anyhow::{bail, Context as _, Error};
290use context::BaseModule;
291use proc_macro2::TokenStream;
292use quote::quote;
293use std::collections::BTreeMap;
294use std::env;
295use std::fs;
296use std::path::Path;
297use toml::Value;
298
299mod aliases;
300mod cargo_toml;
301mod clients;
302mod context;
303mod enums;
304mod errors;
305mod merge_toml;
306mod objects;
307mod servers;
308#[allow(dead_code, clippy::all)]
309#[rustfmt::skip]
310mod types;
311mod human_size;
312mod unions;
313
314/// Examples of generated Conjure code.
315///
316/// This module is only intended to be present in documentation; it shouldn't be relied on by any library code.
317#[cfg(feature = "example-types")]
318#[allow(warnings)]
319#[rustfmt::skip]
320pub mod example_types;
321
322struct CrateInfo {
323    name: String,
324    version: String,
325}
326
327/// Codegen configuration.
328pub struct Config {
329    exhaustive: bool,
330    serialize_empty_collections: bool,
331    use_legacy_error_serialization: bool,
332    strip_prefix: Option<String>,
333    version: Option<String>,
334    build_crate: Option<CrateInfo>,
335    extra_manifest_config: Option<Value>,
336}
337
338impl Default for Config {
339    fn default() -> Config {
340        Config::new()
341    }
342}
343
344impl Config {
345    /// Creates a new `Config` with default settings.
346    pub fn new() -> Config {
347        Config {
348            exhaustive: false,
349            serialize_empty_collections: false,
350            use_legacy_error_serialization: true,
351            strip_prefix: None,
352            version: None,
353            build_crate: None,
354            extra_manifest_config: None,
355        }
356    }
357
358    /// Controls exhaustive matchability of unions and enums.
359    ///
360    /// Non-exhaustive unions and enums have the ability to deserialize and reserialize unknown variants. This enables
361    /// clients to be more forward-compatible with changes made by newer servers.
362    ///
363    /// Defaults to `false`.
364    pub fn exhaustive(&mut self, exhaustive: bool) -> &mut Config {
365        self.exhaustive = exhaustive;
366        self
367    }
368
369    /// Controls serialization of empty collection fields in objects.
370    ///
371    /// Some Conjure implementations don't properly handle deserialization of objects when empty collections are
372    /// omitted. Enabling this option will cause empty optional, set, list, and map fields to be included in the
373    /// serialized output.
374    ///
375    /// Defaults to `false`.
376    pub fn serialize_empty_collections(
377        &mut self,
378        serialize_empty_collections: bool,
379    ) -> &mut Config {
380        self.serialize_empty_collections = serialize_empty_collections;
381        self
382    }
383
384    /// Parameters of service errors were historically stringified when serialized into response bodies.
385    ///
386    /// Setting this to `false` will cause error parameters to be serialized directly as their underlying types would
387    /// be.
388    ///
389    /// Defaults to `true`.
390    pub fn use_legacy_error_serialization(
391        &mut self,
392        use_legacy_error_serialization: bool,
393    ) -> &mut Config {
394        self.use_legacy_error_serialization = use_legacy_error_serialization;
395        self
396    }
397
398    /// Sets a prefix that will be stripped from package names.
399    ///
400    /// Defaults to `None`.
401    pub fn strip_prefix<T>(&mut self, strip_prefix: T) -> &mut Config
402    where
403        T: Into<Option<String>>,
404    {
405        self.strip_prefix = strip_prefix.into();
406        self
407    }
408
409    /// Sets the version included in endpoint metadata for generated client bindings.
410    ///
411    /// Defaults to the version passed to [`Self::build_crate`], or `None` otherwise.
412    pub fn version<T>(&mut self, version: T) -> &mut Config
413    where
414        T: Into<Option<String>>,
415    {
416        self.version = version.into();
417        self
418    }
419
420    /// Sets extra manifest configuration to be merged into the generated Cargo.toml.
421    ///
422    /// Defaults to `None`
423    pub fn extra_manifest_config<T>(&mut self, config: T) -> &mut Config
424    where
425        T: Into<Option<toml::Table>>,
426    {
427        self.extra_manifest_config = config.into().map(Value::Table);
428        self
429    }
430
431    /// Switches generation to create a full crate.
432    ///
433    /// Defaults to just generating a single module.
434    pub fn build_crate(&mut self, name: &str, version: &str) -> &mut Config {
435        self.build_crate = Some(CrateInfo {
436            name: name.to_string(),
437            version: version.to_string(),
438        });
439        self
440    }
441
442    /// Generates Rust source files from a JSON-encoded Conjure IR file.
443    pub fn generate_files<P, Q>(&self, ir_file: P, out_dir: Q) -> Result<(), Error>
444    where
445        P: AsRef<Path>,
446        Q: AsRef<Path>,
447    {
448        self.generate_files_inner(ir_file.as_ref(), out_dir.as_ref())
449    }
450
451    fn generate_files_inner(&self, ir_file: &Path, out_dir: &Path) -> Result<(), Error> {
452        let defs = self.parse_ir(ir_file)?;
453
454        if defs.version() != 1 {
455            bail!("unsupported IR version {}", defs.version());
456        }
457
458        let modules = self.create_modules(&defs);
459        let (src_dir, lib_root) = if self.build_crate.is_some() {
460            (out_dir.join("src"), true)
461        } else {
462            (out_dir.to_path_buf(), false)
463        };
464
465        if let Some(info) = &self.build_crate {
466            self.write_cargo_toml(out_dir, info, &defs)?;
467            self.write_rustfmt_toml(out_dir)?;
468        }
469
470        modules.render(&src_dir, lib_root)?;
471
472        Ok(())
473    }
474
475    fn parse_ir(&self, ir_file: &Path) -> Result<ConjureDefinition, Error> {
476        let ir = fs::read_to_string(ir_file)
477            .with_context(|| format!("error reading file {}", ir_file.display()))?;
478
479        let defs = conjure_serde::json::client_from_str(&ir)
480            .with_context(|| format!("error parsing Conjure IR file {}", ir_file.display()))?;
481
482        Ok(defs)
483    }
484
485    fn create_modules(&self, defs: &ConjureDefinition) -> ModuleTrie {
486        let context = Context::new(
487            defs,
488            self.exhaustive,
489            self.serialize_empty_collections,
490            self.use_legacy_error_serialization,
491            self.strip_prefix.as_deref(),
492            self.version
493                .as_deref()
494                .or_else(|| self.build_crate.as_ref().map(|v| &*v.version)),
495        );
496
497        let mut root = ModuleTrie::new();
498
499        for def in defs.types() {
500            let (type_name, contents) = match def {
501                TypeDefinition::Enum(def) => (def.type_name(), enums::generate(&context, def)),
502                TypeDefinition::Alias(def) => (def.type_name(), aliases::generate(&context, def)),
503                TypeDefinition::Union(def) => (def.type_name(), unions::generate(&context, def)),
504                TypeDefinition::Object(def) => (
505                    def.type_name(),
506                    objects::generate(&context, BaseModule::Objects, def),
507                ),
508            };
509
510            let type_ = Type {
511                module_name: context.module_name(type_name),
512                type_names: vec![context.type_name(type_name.name()).to_string()],
513                contents,
514            };
515            root.insert(&context.module_path(BaseModule::Objects, type_name), type_);
516        }
517
518        for def in defs.errors() {
519            let type_ = Type {
520                module_name: context.module_name(def.error_name()),
521                type_names: vec![context.type_name(def.error_name().name()).to_string()],
522                contents: errors::generate(&context, def),
523            };
524            root.insert(
525                &context.module_path(BaseModule::Errors, def.error_name()),
526                type_,
527            );
528        }
529
530        for def in defs.services() {
531            let client = clients::generate(&context, def);
532
533            let contents = quote! {
534                #client
535            };
536            let type_ = Type {
537                module_name: context.module_name(def.service_name()),
538                type_names: vec![
539                    format!("{}", def.service_name().name()),
540                    format!("{}Client", def.service_name().name()),
541                    format!("Async{}", def.service_name().name()),
542                    format!("Async{}Client", def.service_name().name()),
543                ],
544                contents,
545            };
546            root.insert(
547                &context.module_path(BaseModule::Clients, def.service_name()),
548                type_,
549            );
550
551            let server = servers::generate(&context, def);
552
553            let contents = quote! {
554                #server
555            };
556            let type_ = Type {
557                module_name: context.module_name(def.service_name()),
558                type_names: vec![
559                    context.type_name(def.service_name().name()).to_string(),
560                    format!("Async{}", def.service_name().name()),
561                    format!("{}Endpoints", def.service_name().name()),
562                    format!("Async{}Endpoints", def.service_name().name()),
563                ],
564                contents,
565            };
566            root.insert(
567                &context.module_path(BaseModule::Endpoints, def.service_name()),
568                type_,
569            );
570        }
571
572        root
573    }
574
575    fn write_cargo_toml(
576        &self,
577        dir: &Path,
578        info: &CrateInfo,
579        def: &ConjureDefinition,
580    ) -> Result<(), Error> {
581        fs::create_dir_all(dir)
582            .with_context(|| format!("error creating directory {}", dir.display()))?;
583
584        let metadata = def
585            .extensions()
586            .get("recommended-product-dependencies")
587            .map(|deps| cargo_toml::Metadata {
588                sls: cargo_toml::Sls {
589                    recommended_product_dependencies: deps,
590                },
591            });
592
593        let mut needs_object = false;
594        let mut needs_error = false;
595        let mut needs_http = false;
596
597        if !def.types().is_empty() {
598            needs_object = true;
599        }
600
601        if !def.errors().is_empty() {
602            needs_object = true;
603            needs_error = true;
604        }
605
606        if !def.services().is_empty() {
607            needs_http = true;
608            needs_object = true;
609        }
610
611        let conjure_version = env!("CARGO_PKG_VERSION");
612        let mut dependencies = BTreeMap::new();
613        if needs_object {
614            dependencies.insert("conjure-object", conjure_version);
615        }
616        if needs_error {
617            dependencies.insert("conjure-error", conjure_version);
618        }
619        if needs_http {
620            dependencies.insert("conjure-http", conjure_version);
621        }
622
623        let manifest = cargo_toml::Manifest {
624            package: cargo_toml::Package {
625                name: &info.name,
626                version: &info.version,
627                edition: "2018",
628                metadata,
629            },
630            dependencies,
631        };
632
633        let manifest = if let Some(extra_manifest_toml) = self.extra_manifest_config.as_ref() {
634            let mut manifest_toml = toml::Value::Table(toml::Table::try_from(&manifest)?);
635            left_merge(&mut manifest_toml, extra_manifest_toml)?;
636            toml::to_string_pretty(&manifest_toml).unwrap()
637        } else {
638            toml::to_string_pretty(&manifest).unwrap()
639        };
640
641        let file = dir.join("Cargo.toml");
642
643        fs::write(&file, manifest)
644            .with_context(|| format!("error writing manifest file {}", file.display()))?;
645
646        Ok(())
647    }
648
649    fn write_rustfmt_toml(&self, dir: &Path) -> Result<(), Error> {
650        let contents = "\
651disable_all_formatting = true
652";
653
654        let file = dir.join("rustfmt.toml");
655
656        fs::write(file, contents).with_context(|| "error writing rustfmt.toml")?;
657
658        Ok(())
659    }
660}
661
662struct Type {
663    module_name: String,
664    type_names: Vec<String>,
665    contents: TokenStream,
666}
667
668struct ModuleTrie {
669    submodules: BTreeMap<String, ModuleTrie>,
670    types: Vec<Type>,
671}
672
673impl ModuleTrie {
674    fn new() -> ModuleTrie {
675        ModuleTrie {
676            submodules: BTreeMap::new(),
677            types: vec![],
678        }
679    }
680
681    fn insert(&mut self, module_path: &[String], type_: Type) {
682        match module_path.split_first() {
683            Some((first, rest)) => self
684                .submodules
685                .entry(first.clone())
686                .or_insert_with(ModuleTrie::new)
687                .insert(rest, type_),
688            None => self.types.push(type_),
689        }
690    }
691
692    fn render(&self, dir: &Path, lib_root: bool) -> Result<(), Error> {
693        fs::create_dir_all(dir)
694            .with_context(|| format!("error creating directory {}", dir.display()))?;
695
696        for type_ in &self.types {
697            self.write_module(
698                &dir.join(format!("{}.rs", type_.module_name)),
699                &type_.contents,
700            )?;
701        }
702
703        for (name, module) in &self.submodules {
704            module.render(&dir.join(name), false)?;
705        }
706
707        let root = self.create_root_module(lib_root);
708        let file_name = if lib_root { "lib.rs" } else { "mod.rs" };
709        self.write_module(&dir.join(file_name), &root)?;
710
711        Ok(())
712    }
713
714    fn write_module(&self, path: &Path, contents: &TokenStream) -> Result<(), Error> {
715        let file = syn::parse2(contents.clone())?;
716        let formatted = prettyplease::unparse(&file);
717
718        fs::write(path, formatted)
719            .with_context(|| format!("error writing module {}", path.display()))?;
720        Ok(())
721    }
722
723    fn create_root_module(&self, lib_root: bool) -> TokenStream {
724        let attrs = if lib_root {
725            quote! {
726                #![allow(warnings)]
727            }
728        } else {
729            quote! {}
730        };
731
732        let uses = self.types.iter().map(|m| {
733            let module_name = m.module_name.parse::<TokenStream>().unwrap();
734            let type_names = m
735                .type_names
736                .iter()
737                .map(|n| n.parse::<TokenStream>().unwrap());
738            quote! {
739                #[doc(inline)]
740                pub use self::#module_name::{#(#type_names),*};
741            }
742        });
743
744        let type_mods = self.types.iter().map(|m| {
745            let module_name = m.module_name.parse::<TokenStream>().unwrap();
746            quote! {
747                pub mod #module_name;
748            }
749        });
750
751        let sub_mods = self.submodules.keys().map(|v| {
752            let module_name = v.parse::<TokenStream>().unwrap();
753            quote! {
754                pub mod #module_name;
755            }
756        });
757
758        quote! {
759            #attrs
760            #(#uses)*
761
762            #(#type_mods)*
763            #(#sub_mods)*
764        }
765    }
766}