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 if map.contains_key("fake") || map.contains_key("var") {
62 Self::validate_jsonlogic_expression(schema)?;
63 } else {
64 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}