mold_cli/parser/
json.rs

1use crate::types::{MoldError, Schema, SchemaType};
2use crate::utils::{get_file_stem, to_pascal_case};
3use anyhow::Result;
4use std::path::Path;
5
6use super::inference::{infer_type_flat, infer_type_with_extraction};
7
8pub fn parse_json_file(path: &Path, name: Option<&str>, flat_mode: bool) -> Result<Schema> {
9    let content = std::fs::read_to_string(path)?;
10    let root_name = name
11        .map(to_pascal_case)
12        .unwrap_or_else(|| to_pascal_case(get_file_stem(path).as_str()));
13
14    parse_json_string(&content, &root_name, flat_mode)
15}
16
17pub fn parse_json_string(json: &str, name: &str, flat_mode: bool) -> Result<Schema> {
18    let value: serde_json::Value = serde_json::from_str(json)?;
19    parse_json_value(&value, name, flat_mode)
20}
21
22pub fn parse_json_value(value: &serde_json::Value, name: &str, flat_mode: bool) -> Result<Schema> {
23    let mut nested_types = Vec::new();
24    let mut path = vec![name.to_string()];
25
26    let root_type = if flat_mode {
27        infer_type_flat(value)
28    } else {
29        infer_type_with_extraction(value, &mut path, &mut nested_types)
30    };
31
32    if !matches!(root_type, SchemaType::Object(_)) {
33        return Err(MoldError::InvalidRoot(format!("{:?}", value)).into());
34    }
35
36    Ok(Schema::new(name, root_type).with_nested_types(nested_types))
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn test_parse_simple_object() {
45        let json = r#"{"name": "John", "age": 30}"#;
46        let schema = parse_json_string(json, "User", true).unwrap();
47
48        assert_eq!(schema.name, "User");
49        if let SchemaType::Object(obj) = &schema.root_type {
50            assert_eq!(obj.fields.len(), 2);
51        } else {
52            panic!("Expected Object type");
53        }
54    }
55
56    #[test]
57    fn test_infer_integer_vs_number() {
58        let int_json = r#"{"count": 42}"#;
59        let float_json = r#"{"price": 19.99}"#;
60
61        let int_schema = parse_json_string(int_json, "Test", true).unwrap();
62        let float_schema = parse_json_string(float_json, "Test", true).unwrap();
63
64        if let SchemaType::Object(obj) = &int_schema.root_type {
65            assert_eq!(obj.fields[0].field_type, SchemaType::Integer);
66        }
67
68        if let SchemaType::Object(obj) = &float_schema.root_type {
69            assert_eq!(obj.fields[0].field_type, SchemaType::Number);
70        }
71    }
72
73    #[test]
74    fn test_parse_array() {
75        let json = r#"{"tags": ["a", "b", "c"]}"#;
76        let schema = parse_json_string(json, "Test", true).unwrap();
77
78        if let SchemaType::Object(obj) = &schema.root_type {
79            if let SchemaType::Array(inner) = &obj.fields[0].field_type {
80                assert_eq!(**inner, SchemaType::String);
81            } else {
82                panic!("Expected Array type");
83            }
84        }
85    }
86
87    #[test]
88    fn test_parse_empty_array() {
89        let json = r#"{"items": []}"#;
90        let schema = parse_json_string(json, "Test", true).unwrap();
91
92        if let SchemaType::Object(obj) = &schema.root_type {
93            if let SchemaType::Array(inner) = &obj.fields[0].field_type {
94                assert_eq!(**inner, SchemaType::Any);
95            } else {
96                panic!("Expected Array type");
97            }
98        }
99    }
100
101    #[test]
102    fn test_parse_mixed_array() {
103        let json = r#"{"mixed": [1, "two", true]}"#;
104        let schema = parse_json_string(json, "Test", true).unwrap();
105
106        if let SchemaType::Object(obj) = &schema.root_type {
107            if let SchemaType::Array(inner) = &obj.fields[0].field_type {
108                assert!(matches!(**inner, SchemaType::Union(_)));
109            } else {
110                panic!("Expected Array type");
111            }
112        }
113    }
114
115    #[test]
116    fn test_nested_extraction() {
117        let json = r#"{
118            "user": {
119                "profile": {
120                    "name": "John"
121                }
122            }
123        }"#;
124        let schema = parse_json_string(json, "Root", false).unwrap();
125        assert!(!schema.nested_types.is_empty());
126    }
127
128    #[test]
129    fn test_detect_uuid() {
130        let json = r#"{"id": "550e8400-e29b-41d4-a716-446655440000"}"#;
131        let schema = parse_json_string(json, "Test", true).unwrap();
132
133        if let SchemaType::Object(obj) = &schema.root_type {
134            assert_eq!(obj.fields[0].field_type, SchemaType::Uuid);
135        } else {
136            panic!("Expected Object type");
137        }
138    }
139
140    #[test]
141    fn test_detect_datetime() {
142        let json = r#"{"created": "2023-01-15T10:30:00Z"}"#;
143        let schema = parse_json_string(json, "Test", true).unwrap();
144
145        if let SchemaType::Object(obj) = &schema.root_type {
146            assert_eq!(obj.fields[0].field_type, SchemaType::DateTime);
147        } else {
148            panic!("Expected Object type");
149        }
150    }
151
152    #[test]
153    fn test_detect_date() {
154        let json = r#"{"birthday": "2023-01-15"}"#;
155        let schema = parse_json_string(json, "Test", true).unwrap();
156
157        if let SchemaType::Object(obj) = &schema.root_type {
158            assert_eq!(obj.fields[0].field_type, SchemaType::Date);
159        } else {
160            panic!("Expected Object type");
161        }
162    }
163
164    #[test]
165    fn test_detect_email() {
166        let json = r#"{"email": "user@example.com"}"#;
167        let schema = parse_json_string(json, "Test", true).unwrap();
168
169        if let SchemaType::Object(obj) = &schema.root_type {
170            assert_eq!(obj.fields[0].field_type, SchemaType::Email);
171        } else {
172            panic!("Expected Object type");
173        }
174    }
175
176    #[test]
177    fn test_detect_url() {
178        let json = r#"{"website": "https://example.com/path"}"#;
179        let schema = parse_json_string(json, "Test", true).unwrap();
180
181        if let SchemaType::Object(obj) = &schema.root_type {
182            assert_eq!(obj.fields[0].field_type, SchemaType::Url);
183        } else {
184            panic!("Expected Object type");
185        }
186    }
187
188    #[test]
189    fn test_plain_string_not_semantic() {
190        let json = r#"{"name": "John Doe"}"#;
191        let schema = parse_json_string(json, "Test", true).unwrap();
192
193        if let SchemaType::Object(obj) = &schema.root_type {
194            assert_eq!(obj.fields[0].field_type, SchemaType::String);
195        } else {
196            panic!("Expected Object type");
197        }
198    }
199}