schemafy_lib/
generator.rs

1use crate::Expander;
2use std::{
3    io,
4    path::{Path, PathBuf},
5};
6
7/// A configurable builder for generating Rust types from a JSON
8/// schema.
9///
10/// The default options are usually fine. In that case, you can use
11/// the [`generate()`](fn.generate.html) convenience method instead.
12#[derive(Debug, PartialEq)]
13#[must_use]
14pub struct Generator<'a, 'b> {
15    /// The name of the root type defined by the schema. If the schema
16    /// does not define a root type (some schemas are simply a
17    /// collection of definitions) then simply pass `None`.
18    pub root_name: Option<String>,
19    /// The module path to this crate. Some generated code may make
20    /// use of types defined in this crate. Unless you have
21    /// re-exported this crate or imported it under a different name,
22    /// the default should be fine.
23    pub schemafy_path: &'a str,
24    /// The JSON schema file to read
25    pub input_file: &'b Path,
26}
27
28impl<'a, 'b> Generator<'a, 'b> {
29    /// Get a builder for the Generator
30    pub fn builder() -> GeneratorBuilder<'a, 'b> {
31        GeneratorBuilder::default()
32    }
33
34    pub fn generate(&self) -> proc_macro2::TokenStream {
35        let input_file = if self.input_file.is_relative() {
36            let crate_root = get_crate_root().unwrap();
37            crate_root.join(self.input_file)
38        } else {
39            PathBuf::from(self.input_file)
40        };
41
42        let json = std::fs::read_to_string(&input_file).unwrap_or_else(|err| {
43            panic!("Unable to read `{}`: {}", input_file.to_string_lossy(), err)
44        });
45
46        let schema = serde_json::from_str(&json).unwrap_or_else(|err| {
47            panic!(
48                "Cannot parse `{}` as JSON: {}",
49                input_file.to_string_lossy(),
50                err
51            )
52        });
53        let mut expander = Expander::new(self.root_name.as_deref(), self.schemafy_path, &schema);
54        expander.expand(&schema)
55    }
56
57    pub fn generate_to_file<P: ?Sized + AsRef<Path>>(&self, output_file: &'b P) -> io::Result<()> {
58        use std::process::Command;
59        let tokens = self.generate();
60        let out = tokens.to_string();
61        std::fs::write(output_file, &out)?;
62        Command::new("rustfmt")
63            .arg(output_file.as_ref().as_os_str())
64            .output()?;
65        Ok(())
66    }
67}
68
69#[derive(Debug, PartialEq)]
70#[must_use]
71pub struct GeneratorBuilder<'a, 'b> {
72    inner: Generator<'a, 'b>,
73}
74
75impl<'a, 'b> Default for GeneratorBuilder<'a, 'b> {
76    fn default() -> Self {
77        Self {
78            inner: Generator {
79                root_name: None,
80                schemafy_path: "::schemafy_core::",
81                input_file: Path::new("schema.json"),
82            },
83        }
84    }
85}
86
87impl<'a, 'b> GeneratorBuilder<'a, 'b> {
88    pub fn with_root_name(mut self, root_name: Option<String>) -> Self {
89        self.inner.root_name = root_name;
90        self
91    }
92    pub fn with_root_name_str(mut self, root_name: &str) -> Self {
93        self.inner.root_name = Some(root_name.to_string());
94        self
95    }
96    pub fn with_input_file<P: ?Sized + AsRef<Path>>(mut self, input_file: &'b P) -> Self {
97        self.inner.input_file = input_file.as_ref();
98        self
99    }
100    pub fn with_schemafy_path(mut self, schemafy_path: &'a str) -> Self {
101        self.inner.schemafy_path = schemafy_path;
102        self
103    }
104    pub fn build(self) -> Generator<'a, 'b> {
105        self.inner
106    }
107}
108
109fn get_crate_root() -> std::io::Result<PathBuf> {
110    if let Ok(path) = std::env::var("CARGO_MANIFEST_DIR") {
111        return Ok(PathBuf::from(path));
112    }
113
114    let current_dir = std::env::current_dir()?;
115
116    for p in current_dir.ancestors() {
117        if std::fs::read_dir(p)?
118            .into_iter()
119            .filter_map(Result::ok)
120            .any(|p| p.file_name().eq("Cargo.toml"))
121        {
122            return Ok(PathBuf::from(p));
123        }
124    }
125
126    Ok(current_dir)
127}