Skip to main content

lmn_core/request_template/
generator.rs

1use 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
9// ── Context ───────────────────────────────────────────────────────────────────
10
11pub struct GeneratorContext {
12    pub defs: HashMap<String, TemplateDef>,
13    /// Pre-serialized JSON strings for `:global` and `ENV:` placeholders.
14    /// Values are stored as JSON literals (e.g. `"\"hello\""` for a string).
15    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}