quick_type_schema/
lib.rs

1use json::{object, JsonValue};
2
3#[cfg(feature = "add_type")]
4use schemars::{schema_for, JsonSchema};
5
6mod cli_builder;
7mod langs;
8
9use cli_builder::CliBuilder;
10pub use langs::*;
11
12#[derive(Debug, Clone)]
13pub struct CodegenContext {
14    base_name: String,
15    schema: Schema,
16    override_quicktype_args: Option<Vec<String>>,
17}
18
19impl CodegenContext {
20    pub fn new(base_name: &str, override_quicktype_args: Option<&[&str]>) -> Self {
21        let schema = Schema {
22            current_num: 0,
23            final_val: object! {
24                "$schema": "http://json-schema.org/draft-07/schema#",
25                "type": "object",
26                "definitions": {
27
28                },
29            },
30            raw_schemas: vec![],
31        };
32        CodegenContext {
33            base_name: base_name.to_owned(),
34            schema,
35            override_quicktype_args: override_quicktype_args.map(|override_quicktype_args| {
36                override_quicktype_args
37                    .iter()
38                    .map(|s| s.to_string())
39                    .collect()
40            }),
41        }
42    }
43
44    #[cfg(feature = "add_type")]
45    pub fn add_type<T: JsonSchema>(&mut self) {
46        self.schema
47            .push_schema_str(&serde_json::to_string(&schema_for!(T)).unwrap());
48    }
49
50    pub fn add_schema(&mut self, schema: &str) {
51        self.schema.push_schema_str(schema);
52    }
53
54    pub fn finish(&self, lang: Language) -> String {
55        let args = lang.get_args();
56        let proc_id = std::process::id();
57
58        let mut out_path = std::env::temp_dir();
59        out_path.push(format!("quick-type-code-{}-{}", lang.name(), proc_id));
60
61        let mut schema_path = std::env::temp_dir();
62        schema_path.push(format!("quick-type-schema-{}.json", proc_id));
63
64        std::fs::write(&schema_path, self.schema.final_val.to_string()).unwrap();
65
66        let mut quicktype_args = [
67            "--quiet",
68            "-t",
69            &self.base_name,
70            "-o",
71            out_path.to_str().unwrap(),
72            "--src-lang",
73            "schema",
74            schema_path.to_str().unwrap(),
75        ]
76        .into_iter()
77        .map(|s| s.to_owned())
78        .collect::<Vec<_>>();
79        if let Some(overrides) = self.override_quicktype_args.as_ref() {
80            quicktype_args.append(&mut overrides.clone());
81        } else {
82            quicktype_args.append(&mut args.to_vec());
83        }
84
85        let cmd = if std::process::Command::new("quicktype")
86            .arg("--version")
87            .output()
88            .is_ok()
89        {
90            "quicktype"
91        } else if std::process::Command::new("npx")
92            .arg("--version")
93            .output()
94            .is_ok()
95        {
96            quicktype_args.insert(0, "quicktype".to_owned());
97            "npx"
98        } else {
99            panic!("Neither `quicktype` and `npx` are in $PATH")
100        };
101
102        let out = String::from_utf8(
103            std::process::Command::new(cmd)
104                .args(quicktype_args.iter())
105                .output()
106                .unwrap()
107                .stderr,
108        )
109        .unwrap();
110
111        if !out.is_empty() {
112            panic!("quicktype {}", out);
113        }
114
115        if !out_path.exists() {
116            panic!("Error: quicktype generated an unexpected noutput");
117        }
118
119        let output = std::fs::read_to_string(&out_path).unwrap();
120        let _ = std::fs::remove_file(out_path);
121        let _ = std::fs::remove_file(schema_path);
122        output
123    }
124}
125
126#[derive(Debug, Clone)]
127pub struct Schema {
128    current_num: usize,
129    final_val: JsonValue,
130    raw_schemas: Vec<(String, String)>,
131}
132
133impl Schema {
134    pub fn push_schema_str(&mut self, s: &str) {
135        let val = json::parse(s).unwrap();
136        let title = val["title"].as_str().unwrap();
137
138        self.raw_schemas.push((title.to_owned(), s.to_owned()));
139
140        self.final_val["definitions"][title] = val.clone();
141        self.final_val["properties"][format!("t{}", self.current_num)] = object! {
142            "$ref": format!("#/definitions/{}", title)
143        };
144        let JsonValue::Object(val) = val else {
145            unreachable!()
146        };
147        if let Some(definitions) = val.get("definitions") {
148            for (k, v) in definitions.entries() {
149                self.final_val["definitions"][k] = v.clone();
150            }
151        }
152        self.current_num += 1;
153    }
154}