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