1use std::{
4 collections::{BTreeMap, BTreeSet},
5 path::{Path, PathBuf},
6};
7
8use checksum::crc32::Crc32;
9use codegen::{Field, Scope, Variant};
10use convert_case::{Case, Casing};
11use json_shape::JsonShape;
12
13#[cfg(test)]
14mod test;
15
16#[macro_export]
46macro_rules! include_json_shape {
47 ($package: tt) => {
48 include!(concat!(
49 env!("OUT_DIR"),
50 concat!("/", $package, ".gen.shape.rs")
51 ));
52 };
53}
54
55#[allow(clippy::missing_panics_doc)]
72pub fn compile_json(
73 collection_name: &'static str,
74 jsons: &[impl AsRef<Path>],
75) -> std::io::Result<String> {
76 for path in jsons {
77 println!("cargo:rerun-if-changed={}", path.as_ref().display());
78 }
79
80 let sources = jsons
81 .iter()
82 .filter_map(|path| path.as_ref().to_str())
83 .map(std::fs::read_to_string)
84 .collect::<Result<Vec<String>, std::io::Error>>()?;
85
86 let shape = json_shape::JsonShape::from_sources(&sources).map_err(std::io::Error::other)?;
87 let target: PathBuf =
88 std::env::var_os("OUT_DIR").map_or_else(|| std::env::current_dir().unwrap(), PathBuf::from);
89 let target = target.join(collection_name).with_extension("gen.shape.rs");
90
91 let mut scope = Scope::new();
92
93 first_pass(&shape, &mut scope);
94 let content = format!(
95 "//! Generated `JsonShape` file.\nuse serde;\n\n{}",
96 scope.to_string()
97 );
98 std::fs::write(target, content)?;
99 Ok(scope.to_string())
100}
101
102pub(crate) fn first_pass(shape: &JsonShape, scope: &mut Scope) {
103 match &shape {
104 json_shape::JsonShape::Null => {
105 scope.new_type_alias("Void", "()").vis("pub");
106 }
107 json_shape::JsonShape::Bool { optional } => {
108 if *optional {
109 scope
110 .new_type_alias("NullableBool", "Option<bool>")
111 .vis("pub");
112 } else {
113 scope.new_type_alias("Bool", "bool").vis("pub");
114 }
115 }
116 json_shape::JsonShape::Number { optional } => {
117 if *optional {
118 scope
119 .new_type_alias("NullableNumber", "Option<f64>")
120 .vis("pub");
121 } else {
122 scope.new_type_alias("Number", "f64").vis("pub");
123 }
124 }
125 json_shape::JsonShape::String { optional } => {
126 if *optional {
127 scope
128 .new_type_alias("NullableStr", "Option<String>")
129 .vis("pub");
130 } else {
131 scope.new_type_alias("Str", "String").vis("pub");
132 }
133 }
134 json_shape::JsonShape::Array {
135 r#type: inner,
136 optional,
137 } => {
138 let name = shape_name(shape);
139 create_array(scope, &name, *optional, inner);
140 create_subtype(scope, inner);
141 }
142 json_shape::JsonShape::Object { content, .. } => {
143 let name = shape_name(shape);
144 create_object(scope, &name, content);
145 for inner in content.values() {
146 create_subtype(scope, inner);
147 }
148 }
149 json_shape::JsonShape::OneOf { variants, .. } => {
150 let name = shape_name(shape);
151 create_enum(scope, &name, variants);
152 for inner in variants {
153 create_subtype(scope, inner);
154 }
155 }
156 json_shape::JsonShape::Tuple { elements, optional } => {
157 let name = shape_name(shape);
158 create_tuple(scope, &name, *optional, elements);
159 for inner in elements {
160 create_subtype(scope, inner);
161 }
162 }
163 }
164}
165
166fn create_subtype(scope: &mut Scope, shape: &JsonShape) {
167 match shape {
168 json_shape::JsonShape::Array {
169 r#type: inner,
170 optional: _,
171 } => {
172 create_subtype(scope, inner);
173 }
174 json_shape::JsonShape::Object { content, .. } => {
175 let name = shape_name(shape);
176 create_object(scope, &name, content);
177 for inner in content.values() {
178 create_subtype(scope, inner);
179 }
180 }
181 json_shape::JsonShape::OneOf { variants, .. } => {
182 let name = shape_name(shape);
183 create_enum(scope, &name, variants);
184 for inner in variants {
185 create_subtype(scope, inner);
186 }
187 }
188 json_shape::JsonShape::Tuple {
189 elements,
190 optional: _,
191 } => {
192 for inner in elements {
193 create_subtype(scope, inner);
194 }
195 }
196 _ => {}
197 }
198}
199
200fn create_object(scope: &mut Scope, name: &str, content: &BTreeMap<String, json_shape::JsonShape>) {
201 let struct_data = scope
202 .new_struct(name)
203 .vis("pub")
204 .derive("Debug")
205 .derive("Clone")
206 .derive("serde::Serialize")
207 .derive("serde::Deserialize");
208 for (name, r#type) in content {
209 let mut field = Field::new(&name.to_case(Case::Snake), shape_representation(r#type));
210 field.vis("pub");
211 struct_data.push_field(field);
212 }
213}
214
215fn create_enum(scope: &mut Scope, name: &str, variants: &BTreeSet<json_shape::JsonShape>) {
216 let variants = variants
217 .iter()
218 .map(|shape| (shape_name(shape), shape_representation(shape)))
219 .map(|(name, representation)| {
220 let mut var = Variant::new(name);
221 var.tuple(&representation);
222 var
223 });
224 let enum_data = scope
225 .new_enum(name)
226 .vis("pub")
227 .derive("Debug")
228 .derive("Clone")
229 .derive("serde::Serialize")
230 .derive("serde::Deserialize");
231 for var in variants {
232 enum_data.push_variant(var);
233 }
234}
235
236fn create_array(scope: &mut Scope, name: &str, optional: bool, r#type: &JsonShape) {
237 let target = if optional {
238 format!("Option<Vec<{}>>", shape_representation(r#type))
239 } else {
240 format!("Vec<{}>", shape_representation(r#type))
241 };
242 scope.new_type_alias(name, target).vis("pub");
243}
244
245fn create_tuple(scope: &mut Scope, name: &str, optional: bool, elements: &[json_shape::JsonShape]) {
246 let representations = elements
247 .iter()
248 .map(shape_representation)
249 .collect::<Vec<_>>()
250 .join(", ");
251 let target = format!(
252 "{}{}({representations}){}",
253 if optional { "Option" } else { "" },
254 if optional { "<" } else { "" },
255 if optional { ">" } else { "" }
256 );
257 scope.new_type_alias(name, target).vis("pub");
258}
259
260fn shape_representation(shape: &JsonShape) -> String {
261 match shape {
262 JsonShape::Null => "()".to_string(),
263 JsonShape::Bool { optional } => {
264 if *optional {
265 "Option<bool>".to_string()
266 } else {
267 "bool".to_string()
268 }
269 }
270 JsonShape::Number { optional } => {
271 if *optional {
272 "Option<f64>".to_string()
273 } else {
274 "f64".to_string()
275 }
276 }
277 JsonShape::String { optional } => {
278 if *optional {
279 "Option<String>".to_string()
280 } else {
281 "String".to_string()
282 }
283 }
284 JsonShape::Array { r#type, optional } => {
285 let sub_shape = shape_representation(r#type);
286 if *optional {
287 format!("Optional<Vec<{sub_shape}>>")
288 } else {
289 format!("Vec<{sub_shape}>")
290 }
291 }
292 JsonShape::Object {
293 content: _,
294 optional,
295 }
296 | JsonShape::OneOf {
297 variants: _,
298 optional,
299 } => {
300 let name = shape_name(shape);
301 if *optional {
302 format!("Option<{name}>")
303 } else {
304 name
305 }
306 }
307 JsonShape::Tuple { elements, optional } => {
308 let sub_shapes = elements
309 .iter()
310 .map(shape_representation)
311 .collect::<Vec<_>>()
312 .join(", ");
313 if *optional {
314 format!("Option<({sub_shapes})>")
315 } else {
316 format!("({sub_shapes})")
317 }
318 }
319 }
320}
321
322fn shape_name(shape: &JsonShape) -> String {
323 match shape {
324 JsonShape::Null => "Null".to_string(),
325 JsonShape::Bool { optional } => {
326 if *optional {
327 "OptionalBool".to_string()
328 } else {
329 "Bool".to_string()
330 }
331 }
332 JsonShape::Number { optional } => {
333 if *optional {
334 "OptionalNumber".to_string()
335 } else {
336 "Number".to_string()
337 }
338 }
339 JsonShape::String { optional } => {
340 if *optional {
341 "OptionalStr".to_string()
342 } else {
343 "Str".to_string()
344 }
345 }
346 JsonShape::Array { r#type, optional } => {
347 let sub_shape = shape_name(r#type);
348 if *optional {
349 format!("OptionalArrayOf{sub_shape}")
350 } else {
351 format!("ArrayOf{sub_shape}")
352 }
353 }
354 JsonShape::Object { content, optional } => {
355 let len = content.len();
356 let sub_shapes = content
357 .values()
358 .map(shape_name)
359 .collect::<String>()
360 .to_case(convert_case::Case::Pascal);
361 let mut crc = Crc32::new();
362 crc.update(sub_shapes.as_bytes());
363 crc.finalize();
364 let name = format!("{:X}", crc.getsum());
365
366 if *optional {
367 format!("OptionalStruct{len}Crc{name}")
368 } else {
369 format!("Struct{len}Crc{name}")
370 }
371 }
372 JsonShape::OneOf { variants, optional } => {
373 let len = variants.len();
374 let sub_shapes = variants
375 .iter()
376 .map(shape_name)
377 .collect::<String>()
378 .to_case(convert_case::Case::Pascal);
379 let mut crc = Crc32::new();
380 crc.update(sub_shapes.as_bytes());
381 crc.finalize();
382 let name = format!("{:X}", crc.getsum());
383 if *optional {
384 format!("OptionalEnum{len}Crc{name}")
385 } else {
386 format!("Enum{len}Crc{name}")
387 }
388 }
389 JsonShape::Tuple { elements, optional } => {
390 let len = elements.len();
391 let sub_shapes = elements
392 .iter()
393 .map(shape_representation)
394 .collect::<String>()
395 .to_case(convert_case::Case::Pascal);
396 let mut crc = Crc32::new();
397 crc.update(sub_shapes.as_bytes());
398 crc.finalize();
399 let name = format!("{:X}", crc.getsum());
400
401 if *optional {
402 format!("OptionalTuple{len}Crc{name}")
403 } else {
404 format!("Tuple{len}Crc{name}")
405 }
406 }
407 }
408}