jsonto/generation/
rust.rs

1use linked_hash_map::LinkedHashMap;
2use std::collections::HashSet;
3
4use crate::generation::serde_case::RenameRule;
5use crate::options::{ImportStyle, Options, StringTransform};
6use crate::shape::{self, Shape};
7use crate::word_case::to_singular;
8use crate::word_case::{snake_case, type_case};
9
10pub struct Ctxt {
11    options: Options,
12    type_names: HashSet<String>,
13    imports: HashSet<String>,
14    created_structs: Vec<(Shape, Ident)>,
15}
16
17pub type Ident = String;
18pub type Code = String;
19
20pub fn to(name: &str, shape: &Shape, options: Options) -> Code {
21    let mut ctxt = Ctxt {
22        options,
23        type_names: HashSet::new(),
24        imports: HashSet::new(),
25        created_structs: Vec::new(),
26    };
27
28    if ctxt.options.import_style != ImportStyle::QualifiedPaths {
29        ctxt.options.derives = ctxt
30            .options
31            .derives
32            .clone()
33            .split(',')
34            .map(|s| import(&mut ctxt, s.trim()))
35            .collect::<Vec<_>>()
36            .join(", ");
37    };
38
39    if !matches!(shape, Shape::Struct { .. }) {
40        // reserve the requested name
41        ctxt.type_names.insert(name.to_string());
42    }
43
44    let (ident, code) = type_from_shape(&mut ctxt, name, shape);
45    let mut code = code.unwrap_or_default();
46
47    if ident != name {
48        code = format!(
49            "{} type {} = {};\n\n{}",
50            ctxt.options.type_visibility, name, ident, code
51        );
52    }
53
54    if !ctxt.imports.is_empty() {
55        let mut imports: Vec<_> = ctxt.imports.drain().collect();
56        imports.sort();
57        let mut import_code = String::new();
58        for import in imports {
59            import_code += "use ";
60            import_code += &import;
61            import_code += ";\n";
62        }
63        import_code += "\n";
64        code = import_code + &code;
65    }
66
67    code
68}
69
70fn type_from_shape(ctxt: &mut Ctxt, path: &str, shape: &Shape) -> (Ident, Option<Code>) {
71    use crate::shape::Shape::*;
72    match shape {
73        Null | Any | Bottom => (import(ctxt, "serde_json::Value"), None),
74        Bool => ("bool".into(), None),
75        StringT => ("String".into(), None),
76        Integer => ("i64".into(), None),
77        Floating => ("f64".into(), None),
78        Tuple(shapes, _n) => {
79            let folded = shape::fold_shapes(shapes.clone());
80            if folded == Any && shapes.iter().any(|s| s != &Any) {
81                generate_tuple_type(ctxt, path, shapes)
82            } else {
83                generate_vec_type(ctxt, path, &folded)
84            }
85        }
86        VecT { elem_type: e } => generate_vec_type(ctxt, path, e),
87        Struct { fields } => generate_struct_type(ctxt, path, fields, shape),
88        MapT { val_type: v } => generate_map_type(ctxt, path, v),
89        Opaque(t) => (t.clone(), None),
90        Optional(e) => {
91            let (inner, defs) = type_from_shape(ctxt, path, e);
92            if ctxt.options.use_default_for_missing_fields {
93                (inner, defs)
94            } else {
95                (format!("Option<{}>", inner), defs)
96            }
97        }
98    }
99}
100
101fn generate_vec_type(ctxt: &mut Ctxt, path: &str, shape: &Shape) -> (Ident, Option<Code>) {
102    let singular = to_singular(path);
103    let (inner, defs) = type_from_shape(ctxt, &singular, shape);
104    (format!("Vec<{}>", inner), defs)
105}
106
107fn generate_map_type(ctxt: &mut Ctxt, path: &str, shape: &Shape) -> (Ident, Option<Code>) {
108    let singular = to_singular(path);
109    let (inner, defs) = type_from_shape(ctxt, &singular, shape);
110    (
111        format!(
112            "{}<String, {}>",
113            import(ctxt, "std::collections::HashMap"),
114            inner
115        ),
116        defs,
117    )
118}
119
120fn generate_tuple_type(ctxt: &mut Ctxt, path: &str, shapes: &[Shape]) -> (Ident, Option<Code>) {
121    let mut types = Vec::new();
122    let mut defs = Vec::new();
123
124    for shape in shapes {
125        let (typ, def) = type_from_shape(ctxt, path, shape);
126        types.push(typ);
127        if let Some(code) = def {
128            defs.push(code)
129        }
130    }
131
132    (format!("({})", types.join(", ")), Some(defs.join("\n\n")))
133}
134
135fn field_name(name: &str, used_names: &HashSet<String>) -> Ident {
136    type_or_field_name(name, used_names, "field", snake_case)
137}
138
139fn type_name(name: &str, used_names: &HashSet<String>) -> Ident {
140    type_or_field_name(name, used_names, "GeneratedType", type_case)
141}
142
143const RUST_KEYWORDS: &[&str] = &[
144    "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do",
145    "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop",
146    "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub", "pure",
147    "ref", "return", "Self", "self", "sizeof", "static", "struct", "super", "trait", "true",
148    "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", "async",
149    "await", "try",
150];
151
152fn type_or_field_name(
153    name: &str,
154    used_names: &HashSet<String>,
155    default_name: &str,
156    case_fn: fn(&str) -> String,
157) -> Ident {
158    let name = name.trim();
159    let mut output_name = case_fn(name);
160    if RUST_KEYWORDS.contains(&&*output_name) {
161        output_name.push_str("_field");
162    }
163    if output_name.is_empty() {
164        output_name.push_str(default_name);
165    }
166    if let Some(c) = output_name.chars().next() {
167        if c.is_ascii_digit() {
168            output_name = String::from("n") + &output_name;
169        }
170    }
171    if !used_names.contains(&output_name) {
172        return output_name;
173    }
174    for n in 2.. {
175        let temp = format!("{}{}", output_name, n);
176        if !used_names.contains(&temp) {
177            return temp;
178        }
179    }
180    unreachable!()
181}
182
183fn collapse_option_vec<'a>(ctxt: &mut Ctxt, typ: &'a Shape) -> (bool, &'a Shape) {
184    if !(ctxt.options.allow_option_vec || ctxt.options.use_default_for_missing_fields) {
185        if let Shape::Optional(inner) = typ {
186            if let Shape::VecT { .. } = **inner {
187                return (true, &**inner);
188            }
189        }
190    }
191    (false, typ)
192}
193
194fn import(ctxt: &mut Ctxt, qualified: &str) -> String {
195    if !qualified.contains("::") {
196        return qualified.into();
197    }
198    match ctxt.options.import_style {
199        ImportStyle::AddImports => {
200            ctxt.imports.insert(qualified.into());
201            qualified.rsplit("::").next().unwrap().into()
202        }
203        ImportStyle::AssumeExisting => qualified.rsplit("::").next().unwrap().into(),
204        ImportStyle::QualifiedPaths => qualified.into(),
205    }
206}
207
208fn generate_struct_type(
209    ctxt: &mut Ctxt,
210    path: &str,
211    field_shapes: &LinkedHashMap<String, Shape>,
212    containing_shape: &Shape,
213) -> (Ident, Option<Code>) {
214    for (created_for_shape, ident) in ctxt.created_structs.iter() {
215        if created_for_shape.is_acceptable_substitution_for(containing_shape) {
216            return (ident.into(), None);
217        }
218    }
219
220    let type_name = type_name(path, &ctxt.type_names);
221    ctxt.type_names.insert(type_name.clone());
222    ctxt.created_structs
223        .push((containing_shape.clone(), type_name.clone()));
224
225    let visibility = ctxt.options.type_visibility.clone();
226    let field_visibility = match ctxt.options.field_visibility {
227        None => visibility.clone(),
228        Some(ref v) => v.clone(),
229    };
230
231    let mut field_names = HashSet::new();
232    let mut defs = Vec::new();
233
234    let fields: Vec<Code> = field_shapes
235        .iter()
236        .map(|(name, typ)| {
237            let field_name = field_name(name, &field_names);
238            field_names.insert(field_name.clone());
239
240            let needs_rename = if let Some(ref transform) = ctxt.options.property_name_format {
241                &to_rename_rule(transform).apply_to_field(&field_name) != name
242            } else {
243                &field_name != name
244            };
245            let mut field_code = String::new();
246            if needs_rename {
247                field_code += &format!("    #[serde(rename = \"{}\")]\n", name)
248            }
249
250            let (is_collapsed, collapsed) = collapse_option_vec(ctxt, typ);
251            if is_collapsed {
252                field_code += "    #[serde(default)]\n";
253            }
254
255            let (field_type, child_defs) = type_from_shape(ctxt, name, collapsed);
256
257            if let Some(code) = child_defs {
258                defs.push(code);
259            }
260
261            field_code += "    ";
262            if !field_visibility.is_empty() {
263                field_code += &field_visibility;
264                field_code += " ";
265            }
266
267            format!("{}{}: {},", field_code, field_name, field_type)
268        })
269        .collect();
270
271    let mut code = format!("#[derive({})]\n", ctxt.options.derives);
272
273    if ctxt.options.deny_unknown_fields {
274        code += "#[serde(deny_unknown_fields)]\n";
275    }
276
277    if ctxt.options.use_default_for_missing_fields {
278        code += "#[serde(default)]\n";
279    }
280
281    if let Some(ref transform) = ctxt.options.property_name_format {
282        if *transform != StringTransform::SnakeCase {
283            code += &format!("#[serde(rename_all = \"{}\")]\n", serde_name(transform))
284        }
285    }
286
287    if !visibility.is_empty() {
288        code += &visibility;
289        code += " ";
290    }
291
292    code += &format!("struct {} {{\n", type_name);
293
294    if !fields.is_empty() {
295        code += &fields.join("\n");
296        code += "\n";
297    }
298    if ctxt.options.collect_additional {
299        code += &format!(
300            "    #[serde(flatten)]\n    additional_fields: {}<String, {}>,\n",
301            import(ctxt, "std::collections::HashMap"),
302            import(ctxt, "serde_json::Value"),
303        )
304    }
305    code += "}";
306
307    if !defs.is_empty() {
308        code += "\n\n";
309        code += &defs.join("\n\n");
310    }
311
312    (type_name, Some(code))
313}
314
315fn to_rename_rule(transform: &StringTransform) -> RenameRule {
316    match transform {
317        StringTransform::LowerCase => RenameRule::LowerCase,
318        StringTransform::UpperCase => RenameRule::UPPERCASE,
319        StringTransform::PascalCase => RenameRule::PascalCase,
320        StringTransform::CamelCase => RenameRule::CamelCase,
321        StringTransform::SnakeCase => RenameRule::SnakeCase,
322        StringTransform::ScreamingSnakeCase => RenameRule::ScreamingSnakeCase,
323        StringTransform::KebabCase => RenameRule::KebabCase,
324        StringTransform::ScreamingKebabCase => RenameRule::ScreamingKebabCase,
325    }
326}
327
328fn serde_name(transform: &StringTransform) -> &'static str {
329    match transform {
330        StringTransform::LowerCase => "lowercase",
331        StringTransform::UpperCase => "UPPERCASE",
332        StringTransform::PascalCase => "PascalCase",
333        StringTransform::CamelCase => "camelCase",
334        StringTransform::SnakeCase => "snake_case",
335        StringTransform::ScreamingSnakeCase => "SCREAMING_SNAKE_CASE",
336        StringTransform::KebabCase => "kebab-case",
337        StringTransform::ScreamingKebabCase => "SCREAMING-KEBAB-CASE",
338    }
339}