1use crate::value::Value;
14use cel_interpreter::objects::{Key, Map as CelMap, Value as CelValue};
15use cel_interpreter::{Context, ExecutionError, Program};
16use std::collections::{BTreeMap, HashMap};
17use std::sync::Arc;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum CelError {
21 DivByZero,
22 Type(String),
23 Other(String),
24}
25
26impl std::fmt::Display for CelError {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 CelError::DivByZero => write!(f, "division by zero"),
30 CelError::Type(m) => write!(f, "type error: {m}"),
31 CelError::Other(m) => write!(f, "{m}"),
32 }
33 }
34}
35impl std::error::Error for CelError {}
36
37#[derive(Debug, Clone, Default)]
40pub struct Env {
41 pub bindings: BTreeMap<String, Value>,
42}
43
44impl Env {
45 pub fn new() -> Self {
46 Self {
47 bindings: BTreeMap::new(),
48 }
49 }
50 pub fn with(mut self, k: impl Into<String>, v: Value) -> Self {
51 self.bindings.insert(k.into(), v);
52 self
53 }
54 pub fn get(&self, k: &str) -> Option<&Value> {
55 self.bindings.get(k)
56 }
57}
58
59pub fn eval(src: &str, env: &Env) -> Result<Value, CelError> {
61 let program = Program::compile(src)
62 .map_err(|e| CelError::Other(format!("CEL parse error: {e}")))?;
63 let mut ctx = Context::default();
64 for (k, v) in &env.bindings {
65 let _ = ctx.add_variable(k.clone(), to_cel(v));
66 }
67 let result = program.execute(&ctx).map_err(map_exec_error)?;
68 Ok(from_cel(&result))
69}
70
71pub fn eval_bool(src: &str, env: &Env) -> Result<bool, CelError> {
73 match eval(src, env)? {
74 Value::Bool(b) => Ok(b),
75 other => Ok(other.truthy()),
76 }
77}
78
79fn map_exec_error(e: ExecutionError) -> CelError {
80 match e {
81 ExecutionError::DivisionByZero(_) | ExecutionError::RemainderByZero(_) => CelError::DivByZero,
82 ExecutionError::UnexpectedType { .. } => CelError::Type(e.to_string()),
83 other => CelError::Other(other.to_string()),
84 }
85}
86
87fn to_cel(v: &Value) -> CelValue {
91 match v {
92 Value::Null => CelValue::Null,
93 Value::Bool(b) => CelValue::Bool(*b),
94 Value::Int(i) => CelValue::Int(*i),
95 Value::Float(f) => CelValue::Float(*f),
96 Value::Str(s) => CelValue::String(Arc::new(s.clone())),
97 Value::List(l) => {
98 CelValue::List(Arc::new(l.iter().map(to_cel).collect()))
99 }
100 Value::Map(m) => {
101 let mut hm: HashMap<String, CelValue> = HashMap::new();
102 for (k, vv) in m {
103 hm.insert(k.clone(), to_cel(vv));
104 }
105 CelValue::Map(CelMap::from(hm))
106 }
107 }
108}
109
110fn from_cel(v: &CelValue) -> Value {
112 match v {
113 CelValue::Null => Value::Null,
114 CelValue::Bool(b) => Value::Bool(*b),
115 CelValue::Int(i) => Value::Int(*i),
116 CelValue::UInt(u) => Value::Int(*u as i64),
117 CelValue::Float(f) => Value::Float(*f),
118 CelValue::String(s) => Value::Str(s.as_ref().clone()),
119 CelValue::List(l) => Value::List(l.iter().map(from_cel).collect()),
120 CelValue::Map(m) => {
121 let mut out = BTreeMap::new();
122 for (k, vv) in m.map.iter() {
123 out.insert(key_to_string(k), from_cel(vv));
124 }
125 Value::Map(out)
126 }
127 CelValue::Bytes(_) | CelValue::Duration(_) | CelValue::Timestamp(_) | CelValue::Function(..) => {
129 Value::Null
130 }
131 }
132}
133
134fn key_to_string(k: &Key) -> String {
135 match k {
136 Key::String(s) => s.as_ref().clone(),
137 Key::Int(i) => i.to_string(),
138 Key::Bool(b) => b.to_string(),
139 other => format!("{other:?}"),
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn env() -> Env {
148 Env::new()
149 .with("x", Value::Int(10))
150 .with(
151 "event",
152 Value::Map(
153 [(
154 "payload".to_string(),
155 Value::Map([("n".to_string(), Value::Int(5))].into_iter().collect()),
156 )]
157 .into_iter()
158 .collect(),
159 ),
160 )
161 .with("s", Value::Str("hello".into()))
162 .with("items", Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]))
163 }
164
165 #[test]
166 fn basics() {
167 assert_eq!(eval("1 + 2", &Env::new()).unwrap(), Value::Int(3));
168 assert_eq!(eval("x + 5", &env()).unwrap(), Value::Int(15));
169 assert_eq!(eval("event.payload.n > 3", &env()).unwrap(), Value::Bool(true));
170 assert_eq!(eval("event.payload.n > 10", &env()).unwrap(), Value::Bool(false));
171 assert_eq!(eval("s + '!' == \"hello!\"", &env()).unwrap(), Value::Bool(true));
172 assert_eq!(eval("'a' in ['a','b']", &Env::new()).unwrap(), Value::Bool(true));
173 }
174
175 #[test]
176 fn div_zero() {
177 assert!(matches!(eval("10 / 0", &Env::new()), Err(CelError::DivByZero)));
178 }
179
180 #[test]
181 fn list_concat_and_map_literal() {
182 assert_eq!(
183 eval("[1,2] + ['c']", &Env::new()).unwrap(),
184 Value::List(vec![Value::Int(1), Value::Int(2), Value::Str("c".into())])
185 );
186 let m = eval("{'k': 2}", &Env::new()).unwrap();
187 assert_eq!(m, Value::Map([("k".to_string(), Value::Int(2))].into_iter().collect()));
188 }
189
190 #[test]
193 fn ternary() {
194 assert_eq!(eval("x > 0 ? 1 : 2", &env()).unwrap(), Value::Int(1));
195 let e = Env::new().with("x", Value::Int(-3));
196 assert_eq!(eval("x > 0 ? 1 : 2", &e).unwrap(), Value::Int(2));
197 }
198
199 #[test]
200 fn exists_macro() {
201 assert_eq!(eval("items.exists(i, i > 2)", &env()).unwrap(), Value::Bool(true));
202 assert_eq!(eval("items.exists(i, i > 9)", &env()).unwrap(), Value::Bool(false));
203 assert_eq!(eval("items.all(i, i > 0)", &env()).unwrap(), Value::Bool(true));
204 }
205
206 #[test]
207 fn functions() {
208 assert_eq!(eval("size(items)", &env()).unwrap(), Value::Int(3));
209 assert_eq!(eval("s.contains(\"ell\")", &env()).unwrap(), Value::Bool(true));
210 assert_eq!(eval("s.startsWith(\"he\")", &env()).unwrap(), Value::Bool(true));
211 }
212}