1use crate::json_node::{JsonNode, KeyValuePair};
25use crate::types::{Diagnostic, Span};
26use std::collections::HashMap;
27
28#[derive(Default)]
30struct GeneratorState {
31 variables: HashMap<String, f64>,
32}
33
34pub struct GeneratorOptions {
36 pub target_size_bytes: Option<usize>,
39 pub target_count: Option<usize>,
42}
43
44pub fn generate_from_schema(
57 schema: &JsonNode,
58 options: &GeneratorOptions,
59) -> Result<JsonNode, Vec<Diagnostic>> {
60 let mut diagnostics = Vec::new();
61 let mut state = GeneratorState::default();
62
63 if let Some(target_size) = options.target_size_bytes {
64 let mut items = Vec::new();
65 let mut current_size = 0;
66
67 while current_size < target_size {
68 let item = evaluate_node(schema, &mut state, &mut diagnostics);
69 current_size += estimate_size(&item);
70 items.push(item);
71
72 if items.len() > 1000000 {
73 break;
74 }
75 }
76
77 if !diagnostics.is_empty() {
78 Err(diagnostics)
79 } else {
80 Ok(JsonNode::Array(items, Span::default()))
81 }
82 } else if let Some(target_count) = options.target_count {
83 let mut items = Vec::with_capacity(target_count);
84 for _ in 0..target_count {
85 items.push(evaluate_node(schema, &mut state, &mut diagnostics));
86 }
87
88 if !diagnostics.is_empty() {
89 Err(diagnostics)
90 } else {
91 Ok(JsonNode::Array(items, Span::default()))
92 }
93 } else {
94 let result = evaluate_node(schema, &mut state, &mut diagnostics);
95 if !diagnostics.is_empty() {
96 Err(diagnostics)
97 } else {
98 Ok(result)
99 }
100 }
101}
102
103fn evaluate_node(
115 node: &JsonNode,
116 state: &mut GeneratorState,
117 diagnostics: &mut Vec<Diagnostic>,
118) -> JsonNode {
119 match node {
120 JsonNode::Object(pairs, span) => {
121 if let Some(type_pair) = pairs.iter().find(|p| p.key == "@type") {
123 #[allow(clippy::collapsible_if)]
124 if let JsonNode::String(t, _) = &type_pair.value {
125 return evaluate_instruction(t, pairs, state, diagnostics, span.clone());
126 }
127 }
128
129 let mut new_pairs = Vec::with_capacity(pairs.len());
131 for pair in pairs {
132 let key = if pair.key.starts_with("@@") {
134 pair.key[1..].to_string()
135 } else {
136 pair.key.clone()
137 };
138
139 new_pairs.push(KeyValuePair {
140 key,
141 value: evaluate_node(&pair.value, state, diagnostics),
142 });
143 }
144 JsonNode::Object(new_pairs, span.clone())
145 }
146 JsonNode::Array(items, span) => {
147 let new_items = items
148 .iter()
149 .map(|item| evaluate_node(item, state, diagnostics))
150 .collect();
151 JsonNode::Array(new_items, span.clone())
152 }
153 _ => node.clone(),
155 }
156}
157
158fn evaluate_instruction(
172 instruction_type: &str,
173 pairs: &[KeyValuePair],
174 state: &mut GeneratorState,
175 diagnostics: &mut Vec<Diagnostic>,
176 span: Span,
177) -> JsonNode {
178 match instruction_type {
179 "uuid" => {
180 let lcg = state.variables.entry("lcg".to_string()).or_insert(1.0);
183 *lcg = (*lcg * 1664525.0 + 1013904223.0) % 4294967296.0;
184 let val = *lcg as u32;
185 JsonNode::String(
186 format!("uuid-{:08x}-1234-5678-abcd-123456789012", val),
187 span,
188 )
189 }
190 "integer" => {
191 let mut start = 0.0;
192 let mut step = 1.0;
193 for pair in pairs {
194 #[allow(clippy::collapsible_if)]
195 if pair.key == "@start" {
196 if let JsonNode::Number(n, _, _) = pair.value {
197 start = n;
198 }
199 } else if pair.key == "@step" {
200 if let JsonNode::Number(n, _, _) = pair.value {
201 step = n;
202 }
203 }
204 }
205 let state_key = format!("int_{}", span.start);
206 let current = state.variables.entry(state_key).or_insert(start);
207 let val = *current;
208 *current += step;
209 JsonNode::Number(val, val.to_string(), span)
210 }
211 "float" => {
212 let mut min = 0.0;
213 let mut max = 1.0;
214 for pair in pairs {
215 #[allow(clippy::collapsible_if)]
216 if pair.key == "@min" {
217 if let JsonNode::Number(n, _, _) = pair.value {
218 min = n;
219 }
220 } else if pair.key == "@max" {
221 if let JsonNode::Number(n, _, _) = pair.value {
222 max = n;
223 }
224 }
225 }
226 let lcg = state.variables.entry("lcg".to_string()).or_insert(1.0);
227 *lcg = (*lcg * 1664525.0 + 1013904223.0) % 4294967296.0;
228 let val = min + (*lcg / 4294967296.0) * (max - min);
229 JsonNode::Number(val, format!("{:.4}", val), span)
230 }
231 "bool" => {
232 let lcg = state.variables.entry("lcg".to_string()).or_insert(1.0);
233 *lcg = (*lcg * 1664525.0 + 1013904223.0) % 4294967296.0;
234 JsonNode::Bool(*lcg > 2147483648.0, span)
235 }
236 "string" => {
237 let mut template = "".to_string();
238 let mut pool = Vec::new();
239 let mut vars_schema = None;
240
241 for pair in pairs {
242 #[allow(clippy::collapsible_if)]
243 if pair.key == "@template" {
244 if let JsonNode::String(s, _) = &pair.value {
245 template = s.clone();
246 }
247 } else if pair.key == "@pool" {
248 if let JsonNode::Array(items, _) = &pair.value {
249 for item in items {
250 if let JsonNode::String(s, _) = item {
251 pool.push(s.clone());
252 }
253 }
254 }
255 } else if pair.key == "@vars" {
256 vars_schema = Some(&pair.value);
257 }
258 }
259
260 if !pool.is_empty() {
261 let lcg = state.variables.entry("lcg".to_string()).or_insert(1.0);
262 *lcg = (*lcg * 1664525.0 + 1013904223.0) % 4294967296.0;
263 let idx = (*lcg as usize) % pool.len();
264 JsonNode::String(pool[idx].clone(), span)
265 } else if !template.is_empty() {
266 let mut output = template;
267 if let Some(JsonNode::Object(var_pairs, _)) = vars_schema {
268 for var_pair in var_pairs {
269 let var_val = evaluate_node(&var_pair.value, state, diagnostics);
270 let var_str = match var_val {
271 JsonNode::String(s, _) => s,
272 JsonNode::Number(_, raw, _) => raw,
273 JsonNode::Bool(b, _) => b.to_string(),
274 _ => "".to_string(),
275 };
276 let placeholder = format!("{{{}}}", var_pair.key);
277 output = output.replace(&placeholder, &var_str);
278 }
279 }
280 JsonNode::String(output, span)
281 } else {
282 JsonNode::String("dummy".to_string(), span)
283 }
284 }
285 "array" => {
286 let mut count = 1;
287 let mut item_schema = &JsonNode::Null(span.clone());
288 for pair in pairs {
289 if pair.key == "@count" {
290 if let JsonNode::Number(n, _, _) = pair.value {
291 count = n as usize;
292 }
293 } else if pair.key == "@item" {
294 item_schema = &pair.value;
295 }
296 }
297
298 let mut items = Vec::with_capacity(count);
299 for _ in 0..count {
300 items.push(evaluate_node(item_schema, state, diagnostics));
301 }
302 JsonNode::Array(items, span)
303 }
304 _ => {
305 diagnostics.push(Diagnostic {
306 span: span.clone(),
307 message: format!("Unknown generator instruction: {}", instruction_type),
308 });
309 JsonNode::Null(span)
310 }
311 }
312}
313
314fn estimate_size(node: &JsonNode) -> usize {
324 match node {
325 JsonNode::Null(_) => 4,
326 JsonNode::Bool(_, _) => 4,
327 JsonNode::Number(_, raw, _) => raw.len(),
328 JsonNode::String(s, _) => s.len() + 2,
329 JsonNode::Array(items, _) => {
330 items.iter().map(estimate_size).sum::<usize>() + items.len() + 2
331 }
332 JsonNode::Object(pairs, _) => {
333 pairs
334 .iter()
335 .map(|p| p.key.len() + 4 + estimate_size(&p.value))
336 .sum::<usize>()
337 + 2
338 }
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use crate::parser::parse;
346
347 #[test]
359 fn test_generator_evaluates_integer_and_uuid() {
360 let schema_str = r#"{
361 "id": { "@type": "uuid" },
362 "index": { "@type": "integer", "@start": 5, "@step": 2 }
363 }"#;
364 let schema = parse(schema_str).unwrap();
365 let opts = GeneratorOptions {
366 target_size_bytes: None,
367 target_count: Some(2),
368 };
369
370 let result = generate_from_schema(&schema, &opts).unwrap();
371 if let JsonNode::Array(items, _) = result {
372 assert_eq!(items.len(), 2);
373 if let JsonNode::Object(pairs, _) = &items[0] {
374 let index_node = pairs.iter().find(|p| p.key == "index").unwrap();
375 if let JsonNode::Number(val, _, _) = index_node.value {
376 assert_eq!(val, 5.0);
377 } else {
378 panic!("Expected Number");
379 }
380 }
381 if let JsonNode::Object(pairs, _) = &items[1] {
382 let index_node = pairs.iter().find(|p| p.key == "index").unwrap();
383 if let JsonNode::Number(val, _, _) = index_node.value {
384 assert_eq!(val, 7.0);
385 } else {
386 panic!("Expected Number");
387 }
388 }
389 } else {
390 panic!("Expected array");
391 }
392 }
393
394 #[test]
405 fn test_generator_unknown_instruction_returns_diagnostic() {
406 let schema_str = r#"{ "field": { "@type": "fake_type" } }"#;
407 let schema = parse(schema_str).unwrap();
408 let opts = GeneratorOptions {
409 target_size_bytes: None,
410 target_count: Some(1),
411 };
412
413 let result = generate_from_schema(&schema, &opts);
414 assert!(result.is_err());
415 let diags = result.unwrap_err();
416 assert_eq!(diags.len(), 1);
417 assert!(diags[0].message.contains("Unknown generator instruction"));
418 }
419
420 #[test]
431 fn test_generator_escaped_keys() {
432 let schema_str = r#"{ "@@type": "User" }"#;
433 let schema = parse(schema_str).unwrap();
434 let opts = GeneratorOptions {
435 target_size_bytes: None,
436 target_count: None,
437 };
438
439 let result = generate_from_schema(&schema, &opts).unwrap();
440 if let JsonNode::Object(pairs, _) = result {
441 assert_eq!(pairs[0].key, "@type");
442 assert!(matches!(pairs[0].value, JsonNode::String(_, _)));
443 } else {
444 panic!("Expected object");
445 }
446 }
447}