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}