lmn_core/request_template/
generator.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::request_template::definition::{ObjectDef, TemplateDef};
5use crate::request_template::generators::Generate;
6use rand::Rng;
7use serde_json::Value;
8
9pub struct GeneratorContext {
12 pub defs: HashMap<String, TemplateDef>,
13 pub resolved: HashMap<String, Arc<str>>,
16}
17
18impl GeneratorContext {
19 pub fn new(defs: HashMap<String, TemplateDef>) -> Self {
20 Self {
21 defs,
22 resolved: HashMap::new(),
23 }
24 }
25
26 pub(crate) fn with_resolved(mut self, resolved: HashMap<String, Arc<str>>) -> Self {
27 self.resolved.extend(resolved);
28 self
29 }
30
31 pub(crate) fn generate_by_name(&self, name: &str, rng: &mut impl Rng) -> Value {
32 match self.defs.get(name) {
33 Some(def) => self.generate_def(def, rng),
34 None => Value::Null,
35 }
36 }
37
38 pub fn generate_def(&self, def: &TemplateDef, rng: &mut impl Rng) -> Value {
39 match def {
40 TemplateDef::String(d) => d.generate(rng),
41 TemplateDef::Float(d) => d.generate(rng),
42 TemplateDef::Object(d) => self.generate_object(d, rng),
43 }
44 }
45
46 fn generate_object(&self, def: &ObjectDef, rng: &mut impl Rng) -> Value {
47 let map = def
48 .composition
49 .iter()
50 .map(|(field, ref_name)| (field.clone(), self.generate_by_name(ref_name, rng)))
51 .collect();
52 Value::Object(map)
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::request_template::definition::{FloatDef, FloatStrategy, ObjectDef, TemplateDef};
60
61 fn float_exact(v: f64) -> TemplateDef {
62 TemplateDef::Float(FloatDef {
63 strategy: FloatStrategy::Exact(v),
64 decimals: 0,
65 })
66 }
67
68 #[test]
69 fn generate_by_name_returns_null_for_unknown() {
70 let ctx = GeneratorContext::new(HashMap::new());
71 let val = ctx.generate_by_name("unknown", &mut rand::rng());
72 assert_eq!(val, Value::Null);
73 }
74
75 #[test]
76 fn generate_by_name_returns_value_for_known() {
77 let mut defs = HashMap::new();
78 defs.insert("price".to_string(), float_exact(10.0));
79 let ctx = GeneratorContext::new(defs);
80 let val = ctx.generate_by_name("price", &mut rand::rng());
81 assert!(val.is_number());
82 }
83
84 #[test]
85 fn generate_object_composes_fields() {
86 let mut defs = HashMap::new();
87 defs.insert("price".to_string(), float_exact(42.0));
88 let ctx = GeneratorContext::new(defs);
89 let obj = ObjectDef {
90 composition: [("amount".to_string(), "price".to_string())]
91 .into_iter()
92 .collect(),
93 };
94 let val = ctx.generate_object(&obj, &mut rand::rng());
95 assert!(val["amount"].is_number());
96 }
97
98 #[test]
99 fn generate_object_unknown_field_returns_null() {
100 let ctx = GeneratorContext::new(HashMap::new());
101 let obj = ObjectDef {
102 composition: [("field".to_string(), "unknown".to_string())]
103 .into_iter()
104 .collect(),
105 };
106 let val = ctx.generate_object(&obj, &mut rand::rng());
107 assert_eq!(val["field"], Value::Null);
108 }
109
110 #[test]
111 fn with_resolved_merges_entries() {
112 let ctx = GeneratorContext::new(HashMap::new()).with_resolved(
113 [("key".to_string(), Arc::from("\"value\""))]
114 .into_iter()
115 .collect(),
116 );
117 assert!(ctx.resolved.contains_key("key"));
118 }
119}