1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use json::{object, JsonValue};

#[cfg(feature = "add_type")]
use schemars::{schema_for, JsonSchema};

mod langs;

pub use langs::*;

#[derive(Debug, Clone)]
pub struct CodegenContext {
    schema: Schema,
    override_quicktype_args: Option<Vec<String>>,
}

impl CodegenContext {
    pub fn new(override_quicktype_args: Option<&[&str]>) -> Self {
        let schema = Schema {
            current_num: 0,
            final_val: object! {
                "$schema": "http://json-schema.org/draft-07/schema#",
                "type": "object",
                "definitions": {},
                "properties": {},
            },
            raw_schemas: vec![],
        };
        CodegenContext {
            schema,
            override_quicktype_args: override_quicktype_args.map(|override_quicktype_args| {
                override_quicktype_args
                    .iter()
                    .map(|s| s.to_string())
                    .collect()
            }),
        }
    }

    #[cfg(feature = "add_type")]
    pub fn add_type<T: JsonSchema>(&mut self) {
        self.schema
            .push_schema_str(&serde_json::to_string(&schema_for!(T)).unwrap());
    }

    pub fn add_schema(&mut self, schema: &str) {
        self.schema.push_schema_str(schema);
    }

    pub fn finish(&self, lang: Language) -> String {
        let args = lang.get_args();
        let proc_id = std::process::id();

        let mut out_path = std::env::temp_dir();
        out_path.push(format!("quick-type-code-{}-{}", lang.name(), proc_id));

        let mut schema_path = std::env::temp_dir();
        schema_path.push(format!("quick-type-schema-{}.json", proc_id));

        std::fs::write(&schema_path, self.schema.final_val.to_string()).unwrap();

        let mut quicktype_args = vec![
            "--quiet",
            "-o",
            &out_path.to_str().unwrap(),
            "--src-lang",
            "schema",
            &schema_path.to_str().unwrap(),
        ];
        if let Some(overrides) = self.override_quicktype_args.as_ref() {
            let mut overrides = overrides.iter().map(|s| s.as_str()).collect();
            quicktype_args.append(&mut overrides);
        } else {
            quicktype_args.append(&mut args.to_vec());
        }

        let cmd = if std::process::Command::new("quicktype")
            .arg("--version")
            .output()
            .is_ok()
        {
            "quicktype"
        } else if std::process::Command::new("npx")
            .arg("--version")
            .output()
            .is_ok()
        {
            quicktype_args.insert(0, "quicktype");
            "npx"
        } else {
            panic!("Neither `quicktype` and `npx` are in $PATH")
        };

        let out = String::from_utf8(
            std::process::Command::new(cmd)
                .args(quicktype_args.iter())
                .output()
                .unwrap()
                .stderr,
        )
        .unwrap();

        if !out.is_empty() {
            panic!("quicktype {}", out);
        }

        if !out_path.exists() {
            panic!("Error: quicktype generated an unexpected noutput");
        }

        let output = std::fs::read_to_string(&out_path).unwrap();
        let _ = std::fs::remove_file(out_path);
        let _ = std::fs::remove_file(schema_path);
        output
    }
}

impl Default for CodegenContext {
    fn default() -> Self {
        Self::new(None)
    }
}

#[derive(Debug, Clone)]
pub struct Schema {
    current_num: usize,
    final_val: JsonValue,
    raw_schemas: Vec<(String, String)>,
}

impl Schema {
    pub fn push_schema_str(&mut self, s: &str) {
        let val = json::parse(s).unwrap();
        let title = val["title"].as_str().unwrap();

        self.raw_schemas.push((title.to_owned(), s.to_owned()));

        self.final_val["definitions"][title] = val.clone();
        self.final_val["properties"][format!("t{}", self.current_num)] = object! {
            "$ref": format!("#/definitions/{}", title)
        };
        let JsonValue::Object(val) = val else {
            unreachable!()
        };
        if let Some(definitions) = val.get("definitions") {
            for (k, v) in definitions.entries() {
                self.final_val["definitions"][k] = v.clone();
            }
        }
        self.current_num += 1;
    }
}