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 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}