1use serde_json::Value;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub struct StepResult {
10 pub output: Value,
11 pub confidence: f32,
12}
13
14pub struct ExprContext {
16 pub input: Value,
17 pub steps: HashMap<String, StepResult>,
18}
19
20impl ExprContext {
21 pub fn new(input: Value) -> Self {
22 Self {
23 input,
24 steps: HashMap::new(),
25 }
26 }
27
28 pub fn eval(&self, expr: &str) -> Result<Value, ExprError> {
33 let trimmed = expr.trim();
34 if let Some(path) = trimmed.strip_prefix("{{") {
35 let path = path
36 .strip_suffix("}}")
37 .ok_or_else(|| ExprError::Syntax(expr.to_string()))?;
38 self.resolve_path(path.trim())
39 } else {
40 Ok(Value::String(expr.to_string()))
41 }
42 }
43
44 pub fn eval_f32(&self, expr: &str) -> Result<f32, ExprError> {
46 let value = self.eval(expr)?;
47 match value {
48 Value::Number(n) => n.as_f64().map(|f| f as f32).ok_or(ExprError::NotNumeric),
49 _ => Err(ExprError::NotNumeric),
50 }
51 }
52
53 fn resolve_path(&self, path: &str) -> Result<Value, ExprError> {
54 if path == "input" {
55 return Ok(self.input.clone());
56 }
57
58 let parts: Vec<&str> = path.splitn(3, '.').collect();
59 match parts.as_slice() {
60 ["steps", name, "output"] => self
61 .steps
62 .get(*name)
63 .map(|r| r.output.clone())
64 .ok_or_else(|| ExprError::UnknownStep((*name).to_string())),
65 ["steps", name, "confidence"] => self
66 .steps
67 .get(*name)
68 .map(|r| Value::Number(serde_json::Number::from_f64(r.confidence as f64).unwrap()))
69 .ok_or_else(|| ExprError::UnknownStep((*name).to_string())),
70 _ => Err(ExprError::UnknownPath(path.to_string())),
71 }
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
76pub enum ExprError {
77 #[error("syntax error in expression: {0}")]
78 Syntax(String),
79 #[error("unknown step: {0}")]
80 UnknownStep(String),
81 #[error("unknown path: {0}")]
82 UnknownPath(String),
83 #[error("value is not numeric")]
84 NotNumeric,
85}