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