datafake_rs/
config.rs

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