1use std::collections::HashMap;
4
5use serde_json::Value;
6
7use super::data_model::DataModel;
8use crate::protocol::common_types::{
9 DynamicBoolean, DynamicBooleanCondition, DynamicNumber, DynamicString,
10 DynamicValue, FunctionCall,
11};
12
13pub struct DataContext<'a> {
20 data_model: &'a DataModel,
21 base_path: String,
22 functions: &'a HashMap<String, Box<dyn crate::catalog::function_api::FunctionImplementation>>,
23 template_index: Option<usize>,
32}
33
34impl<'a> DataContext<'a> {
35 pub fn new(
37 data_model: &'a DataModel,
38 functions: &'a HashMap<String, Box<dyn crate::catalog::function_api::FunctionImplementation>>,
39 ) -> Self {
40 Self {
41 data_model,
42 base_path: String::new(),
43 functions,
44 template_index: None,
45 }
46 }
47
48 pub fn nested(&self, relative_path: &str) -> DataContext<'a> {
54 let new_base = if self.base_path.is_empty() {
55 format!("/{}", relative_path)
56 } else {
57 format!("{}/{}", self.base_path, relative_path)
58 };
59 DataContext {
60 data_model: self.data_model,
61 base_path: new_base,
62 functions: self.functions,
63 template_index: None,
64 }
65 }
66
67 pub fn template_index(&self) -> Option<usize> {
69 self.template_index
70 }
71
72 pub fn with_template_index(mut self, index: Option<usize>) -> Self {
77 self.template_index = index;
78 self
79 }
80
81 pub fn set_template_index(&mut self, index: Option<usize>) {
83 self.template_index = index;
84 }
85
86 fn resolve_index(&self, args: &HashMap<String, Value>) -> Option<Value> {
93 self.template_index.map(|idx| {
94 let offset = args
95 .get("offset")
96 .map(|v| self.resolve_arg_value(v).as_f64().unwrap_or(0.0))
97 .unwrap_or(0.0);
98 serde_json::json!(idx as f64 + offset)
99 })
100 }
101
102 pub fn base_path(&self) -> &str {
104 &self.base_path
105 }
106
107 pub fn resolve_pointer(&self, path: &str) -> String {
109 if path.starts_with('/') {
110 path.to_string()
111 } else if path.is_empty() {
112 self.base_path.clone()
113 } else if self.base_path.is_empty() {
114 format!("/{}", path)
115 } else {
116 format!("{}/{}", self.base_path, path)
117 }
118 }
119
120 pub fn get(&self, path: &str) -> Option<Value> {
122 let pointer = self.resolve_pointer(path);
123 self.data_model.get(&pointer).cloned()
124 }
125
126 pub fn resolve_dynamic_string(&self, ds: &DynamicString) -> String {
128 match ds {
129 DynamicString::Literal(s) => s.clone(),
130 DynamicString::Binding(b) => self.resolve_binding_to_string(&b.path),
131 DynamicString::Function(fc) => {
132 let result = self.execute_function(fc);
133 value_to_string(&result)
134 }
135 }
136 }
137
138 pub fn resolve_dynamic_number(&self, dn: &DynamicNumber) -> f64 {
140 match dn {
141 DynamicNumber::Literal(n) => *n,
142 DynamicNumber::Binding(b) => self
143 .resolve_binding(&b.path)
144 .and_then(|v| v.as_f64())
145 .unwrap_or(0.0),
146 DynamicNumber::Function(fc) => {
147 let result = self.execute_function(fc);
148 result.as_f64().unwrap_or(0.0)
149 }
150 }
151 }
152
153 pub fn resolve_dynamic_boolean(&self, db: &DynamicBoolean) -> bool {
155 match db {
156 DynamicBoolean::Literal(b) => *b,
157 DynamicBoolean::Binding(b) => self
158 .resolve_binding(&b.path)
159 .and_then(|v| v.as_bool())
160 .unwrap_or(false),
161 DynamicBoolean::Function(fc) => {
162 let result = self.execute_function(fc);
163 result.as_bool().unwrap_or(false)
164 }
165 }
166 }
167
168 pub fn resolve_dynamic_boolean_condition(&self, db: &DynamicBooleanCondition) -> bool {
170 match db {
171 DynamicBooleanCondition::Literal(b) => *b,
172 DynamicBooleanCondition::Binding(b) => self
173 .resolve_binding(&b.path)
174 .and_then(|v| v.as_bool())
175 .unwrap_or(false),
176 DynamicBooleanCondition::Function(fc) => {
177 let result = self.execute_function(fc);
178 result.as_bool().unwrap_or(false)
179 }
180 }
181 }
182
183 pub fn resolve_dynamic_value(&self, dv: &DynamicValue) -> Value {
185 match dv {
186 DynamicValue::String(s) => Value::String(s.clone()),
187 DynamicValue::Number(n) => serde_json::json!(*n),
188 DynamicValue::Boolean(b) => Value::Bool(*b),
189 DynamicValue::Array(arr) => Value::Array(arr.clone()),
190 DynamicValue::Binding(b) => self.resolve_binding(&b.path).unwrap_or(Value::Null),
191 DynamicValue::Function(fc) => self.execute_function(fc),
192 }
193 }
194
195 fn resolve_binding(&self, path: &str) -> Option<Value> {
197 let pointer = self.resolve_pointer(path);
198 self.data_model.get(&pointer).cloned()
199 }
200
201 fn resolve_binding_to_string(&self, path: &str) -> String {
203 self.resolve_binding(path)
204 .map(|v| value_to_string(&v))
205 .unwrap_or_default()
206 }
207
208 pub fn call_function_by_name(
212 &self,
213 name: &str,
214 args: &HashMap<String, Value>,
215 ) -> Option<Value> {
216 if name == "@index" {
218 return self.resolve_index(args);
219 }
220 let func = self.functions.get(name)?;
221 let mut resolved_args = HashMap::new();
223 for (key, val) in args {
224 let resolved = self.resolve_arg_value(val);
225 resolved_args.insert(key.clone(), resolved);
226 }
227 func.execute(&resolved_args, self).ok()
228 }
229
230 fn execute_function(&self, fc: &FunctionCall) -> Value {
232 if fc.call == "@index" {
234 return self.resolve_index(&fc.args).unwrap_or(Value::Null);
235 }
236 let Some(func) = self.functions.get(&fc.call) else {
237 return Value::Null;
238 };
239
240 let mut resolved_args = HashMap::new();
242 for (key, val) in &fc.args {
243 let resolved = self.resolve_arg_value(val);
245 resolved_args.insert(key.clone(), resolved);
246 }
247
248 match func.execute(&resolved_args, self) {
249 Ok(v) => v,
250 Err(_) => Value::Null,
251 }
252 }
253
254 fn resolve_arg_value(&self, val: &Value) -> Value {
256 if let Some(obj) = val.as_object() {
257 if let Some(path) = obj.get("path").and_then(|v| v.as_str()) {
258 return self.resolve_binding(path).unwrap_or(Value::Null);
259 }
260 if let Some(call) = obj.get("call").and_then(|v| v.as_str()) {
261 let args = obj
262 .get("args")
263 .and_then(|v| v.as_object())
264 .map(|m| {
265 m.iter()
266 .map(|(k, v)| (k.clone(), self.resolve_arg_value(v)))
267 .collect::<HashMap<_, _>>()
268 })
269 .unwrap_or_default();
270 let fc = FunctionCall {
271 call: call.to_string(),
272 args,
273 };
274 return self.execute_function(&fc);
275 }
276 }
277 val.clone()
278 }
279}
280
281pub fn value_to_string(v: &Value) -> String {
283 match v {
284 Value::String(s) => s.clone(),
285 Value::Number(n) => n.to_string(),
286 Value::Bool(b) => b.to_string(),
287 Value::Null => String::new(),
288 Value::Array(_) | Value::Object(_) => v.to_string(),
289 }
290}
291
292#[cfg(test)]
293mod index_tests {
294 use super::*;
295 use crate::model::data_model::DataModel;
296 use crate::protocol::common_types::{DynamicNumber, FunctionCall};
297 use serde_json::json;
298
299 fn ctx(idx: Option<usize>) -> DataContext<'static> {
302 let dm = Box::leak(Box::new(DataModel::new()));
303 let fns = Box::leak(Box::new(HashMap::new()));
304 DataContext::new(dm, fns).with_template_index(idx)
305 }
306
307 fn index_call(args: &[(&str, Value)]) -> FunctionCall {
308 let mut map = HashMap::new();
309 for (k, v) in args {
310 map.insert((*k).to_string(), v.clone());
311 }
312 FunctionCall {
313 call: "@index".to_string(),
314 args: map,
315 }
316 }
317
318 #[test]
319 fn at_index_without_template_context_is_null() {
320 let ctx = ctx(None);
323 let dn = DynamicNumber::Function(index_call(&[]));
324 assert_eq!(ctx.resolve_dynamic_number(&dn), 0.0);
325 assert_eq!(ctx.call_function_by_name("@index", &HashMap::new()), None);
327 }
328
329 #[test]
330 fn at_index_returns_zero_based_index() {
331 let ctx = ctx(Some(2));
332 let dn = DynamicNumber::Function(index_call(&[]));
333 assert_eq!(ctx.resolve_dynamic_number(&dn), 2.0);
334 }
335
336 #[test]
337 fn at_index_with_literal_offset() {
338 let ctx = ctx(Some(2));
339 let dn = DynamicNumber::Function(index_call(&[("offset", json!(1))]));
340 assert_eq!(ctx.resolve_dynamic_number(&dn), 3.0);
341 }
342
343 #[test]
344 fn at_index_with_bound_offset() {
345 let dm = Box::leak(Box::new(DataModel::from_value(json!({"step": 10}))));
346 let fns = Box::leak(Box::new(HashMap::new()));
347 let ctx = DataContext::new(dm, fns).with_template_index(Some(0));
348 let dn = DynamicNumber::Function(index_call(&[("offset", json!({"path": "/step"}))]));
349 assert_eq!(ctx.resolve_dynamic_number(&dn), 10.0); }
351
352 #[test]
353 fn at_index_via_call_function_by_name() {
354 let ctx = ctx(Some(4));
355 let mut args = HashMap::new();
356 args.insert("offset".to_string(), json!(1));
357 assert_eq!(ctx.call_function_by_name("@index", &args), Some(json!(5.0)));
358 }
359
360 #[test]
361 fn at_index_zero_plus_offset() {
362 let ctx = ctx(Some(0));
364 let dn = DynamicNumber::Function(index_call(&[("offset", json!(1))]));
365 assert_eq!(ctx.resolve_dynamic_number(&dn), 1.0);
366 }
367}