1use crate::error::{DataFakeError, Result};
7use crate::types::{DataFakeConfig, GenerationContext};
8use serde_json::Value;
9use std::collections::HashMap;
10
11pub struct ConfigParser;
16
17impl ConfigParser {
18 pub fn parse(json_str: &str) -> Result<DataFakeConfig> {
24 let config: DataFakeConfig = serde_json::from_str(json_str)
25 .map_err(|e| DataFakeError::ConfigParse(format!("Failed to parse JSON: {e}")))?;
26
27 Self::validate_config(&config)?;
28 Ok(config)
29 }
30
31 pub fn parse_value(json_value: Value) -> Result<DataFakeConfig> {
37 let config: DataFakeConfig = serde_json::from_value(json_value)
38 .map_err(|e| DataFakeError::ConfigParse(format!("Failed to parse JSON value: {e}")))?;
39
40 Self::validate_config(&config)?;
41 Ok(config)
42 }
43
44 fn validate_config(config: &DataFakeConfig) -> Result<()> {
45 if config.schema.is_null() {
46 return Err(DataFakeError::InvalidConfig(
47 "Schema cannot be null".to_string(),
48 ));
49 }
50
51 Self::validate_variables(&config.variables)?;
52 Self::validate_schema(&config.schema)?;
53
54 Ok(())
55 }
56
57 fn validate_variables(variables: &HashMap<String, Value>) -> Result<()> {
58 for (name, value) in variables {
59 if name.is_empty() {
60 return Err(DataFakeError::InvalidConfig(
61 "Variable name cannot be empty".to_string(),
62 ));
63 }
64
65 if value.is_null() {
66 return Err(DataFakeError::InvalidConfig(format!(
67 "Variable '{name}' cannot be null"
68 )));
69 }
70
71 Self::validate_jsonlogic_expression(value)?;
72 }
73 Ok(())
74 }
75
76 fn validate_schema(schema: &Value) -> Result<()> {
77 match schema {
78 Value::Object(map) => Self::validate_schema_object(map, schema),
79 Value::Array(arr) => Self::validate_schema_array(arr),
80 Value::Null => Err(DataFakeError::InvalidConfig(
81 "Schema values cannot be null".to_string(),
82 )),
83 _ => Ok(()),
84 }
85 }
86
87 fn validate_schema_object(map: &serde_json::Map<String, Value>, schema: &Value) -> Result<()> {
89 if map.contains_key("fake") || map.contains_key("var") {
91 return Self::validate_jsonlogic_expression(schema);
92 }
93
94 for (key, value) in map {
96 if key.is_empty() {
97 return Err(DataFakeError::InvalidConfig(
98 "Schema key cannot be empty".to_string(),
99 ));
100 }
101 Self::validate_schema(value)?;
102 }
103 Ok(())
104 }
105
106 fn validate_schema_array(arr: &[Value]) -> Result<()> {
108 for item in arr {
109 Self::validate_schema(item)?;
110 }
111 Ok(())
112 }
113
114 fn validate_jsonlogic_expression(value: &Value) -> Result<()> {
115 if let Value::Object(map) = value {
116 if let Some(fake_args) = map.get("fake") {
117 Self::validate_fake_operator(fake_args)?;
118 } else if map.contains_key("var")
119 && let Some(Value::String(var_name)) = map.get("var")
120 && var_name.is_empty()
121 {
122 return Err(DataFakeError::InvalidConfig(
123 "Variable reference cannot be empty".to_string(),
124 ));
125 }
126 }
127 Ok(())
128 }
129
130 fn validate_fake_operator(args: &Value) -> Result<()> {
131 match args {
132 Value::Array(arr) => {
133 if arr.is_empty() {
134 return Err(DataFakeError::InvalidConfig(
135 "Fake operator requires at least one argument".to_string(),
136 ));
137 }
138
139 if let Some(Value::String(method)) = arr.first() {
140 if method.is_empty() {
141 return Err(DataFakeError::InvalidConfig(
142 "Fake method name cannot be empty".to_string(),
143 ));
144 }
145
146 match method.as_str() {
147 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32"
148 | "f64" => {
149 if arr.len() == 3 {
150 let min = Self::extract_number(arr.get(1))?;
151 let max = Self::extract_number(arr.get(2))?;
152 if min > max {
153 return Err(DataFakeError::InvalidRange { min, max });
154 }
155 } else if arr.len() != 1 {
156 return Err(DataFakeError::InvalidConfig(format!(
157 "Numeric type '{method}' requires either 0 or 2 arguments (min, max)"
158 )));
159 }
160 }
161 _ => {}
162 }
163 } else {
164 return Err(DataFakeError::InvalidConfig(
165 "First argument of fake operator must be a string".to_string(),
166 ));
167 }
168 }
169 _ => {
170 return Err(DataFakeError::InvalidConfig(
171 "Fake operator arguments must be an array".to_string(),
172 ));
173 }
174 }
175 Ok(())
176 }
177
178 fn extract_number(value: Option<&Value>) -> Result<f64> {
179 match value {
180 Some(Value::Number(n)) => n
181 .as_f64()
182 .ok_or_else(|| DataFakeError::TypeConversion("Invalid number format".to_string())),
183 _ => Err(DataFakeError::TypeConversion(
184 "Expected a number".to_string(),
185 )),
186 }
187 }
188
189 pub fn create_context(config: &DataFakeConfig) -> GenerationContext {
191 GenerationContext::with_variables(config.variables.clone())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_parse_valid_config() {
201 let config_json = r#"{
202 "metadata": {
203 "name": "Test Config",
204 "version": "1.0.0"
205 },
206 "variables": {
207 "userId": {"fake": ["uuid"]}
208 },
209 "schema": {
210 "id": {"var": "userId"},
211 "name": {"fake": ["name", "en_US"]}
212 }
213 }"#;
214
215 let result = ConfigParser::parse(config_json);
216 assert!(result.is_ok());
217 let config = result.unwrap();
218 assert!(config.metadata.is_some());
219 assert_eq!(config.variables.len(), 1);
220 }
221
222 #[test]
223 fn test_parse_minimal_config() {
224 let config_json = r#"{
225 "schema": {
226 "name": {"fake": ["name"]}
227 }
228 }"#;
229
230 let result = ConfigParser::parse(config_json);
231 assert!(result.is_ok());
232 }
233
234 #[test]
235 fn test_invalid_empty_schema() {
236 let config_json = r#"{
237 "schema": null
238 }"#;
239
240 let result = ConfigParser::parse(config_json);
241 assert!(result.is_err());
242 }
243
244 #[test]
245 fn test_invalid_fake_operator_no_args() {
246 let config_json = r#"{
247 "schema": {
248 "field": {"fake": []}
249 }
250 }"#;
251
252 let result = ConfigParser::parse(config_json);
253 assert!(result.is_err());
254 }
255
256 #[test]
257 fn test_invalid_numeric_range() {
258 let config_json = r#"{
259 "schema": {
260 "age": {"fake": ["u8", 100, 0]}
261 }
262 }"#;
263
264 let result = ConfigParser::parse(config_json);
265 assert!(result.is_err());
266 }
267
268 #[test]
269 fn test_valid_numeric_range() {
270 let config_json = r#"{
271 "schema": {
272 "age": {"fake": ["u8", 0, 100]}
273 }
274 }"#;
275
276 let result = ConfigParser::parse(config_json);
277 assert!(result.is_ok());
278 }
279
280 #[test]
281 fn test_empty_variable_name() {
282 let config_json = r#"{
283 "variables": {
284 "": {"fake": ["uuid"]}
285 },
286 "schema": {}
287 }"#;
288
289 let result = ConfigParser::parse(config_json);
290 assert!(result.is_err());
291 }
292
293 #[test]
294 fn test_complex_nested_schema() {
295 let config_json = r#"{
296 "variables": {
297 "country": {"fake": ["country_code"]}
298 },
299 "schema": {
300 "users": [
301 {
302 "id": {"fake": ["uuid"]},
303 "profile": {
304 "name": {"fake": ["name", "en_US"]},
305 "address": {
306 "street": {"fake": ["street_address"]},
307 "country": {"var": "country"}
308 }
309 }
310 }
311 ]
312 }
313 }"#;
314
315 let result = ConfigParser::parse(config_json);
316 assert!(result.is_ok());
317 }
318}