1#[cfg(not(target_arch = "wasm32"))]
2uniffi::include_scaffolding!("cel");
3mod ast;
4mod models;
5
6use crate::ast::{ASTExecutionContext, JSONExpression};
7use crate::models::PassableValue::Function;
8use crate::models::{ExecutionContext, PassableMap, PassableValue};
9use crate::models::PassableValue::PMap;
10use crate::ExecutableType::{CompiledProgram, AST};
11use async_trait::async_trait;
12use cel_interpreter::extractors::This;
13use cel_interpreter::objects::{Key, Map, TryIntoValue};
14use cel_interpreter::{Context, ExecutionError, Expression, FunctionContext, Program, Value};
15use std::collections::HashMap;
16use std::error::Error;
17use std::fmt;
18use std::fmt::Debug;
19use std::ops::Deref;
20use std::sync::{Arc, mpsc, Mutex};
21use std::thread::spawn;
22use cel_parser::parse;
23
24#[cfg(target_arch = "wasm32")]
25use wasm_bindgen_futures::spawn_local;
26#[cfg(not(target_arch = "wasm32"))]
27use futures_lite::future::block_on;
28use uniffi::deps::log::__private_api::log;
29
30
31#[cfg(target_arch = "wasm32")]
39pub trait HostContext: Send + Sync {
40 fn computed_property(&self, name: String, args: String) -> String;
41
42 fn device_property(&self, name: String, args: String) -> String;
43}
44
45#[cfg(not(target_arch = "wasm32"))]
46#[async_trait]
47pub trait HostContext: Send + Sync {
48 async fn computed_property(&self, name: String, args: String) -> String;
49
50 async fn device_property(&self, name: String, args: String) -> String;
51}
52
53pub fn evaluate_ast_with_context(definition: String, host: Arc<dyn HostContext>) -> String {
60 let data: Result<ASTExecutionContext,_> = serde_json::from_str(definition.as_str());
61 let data = match data {
62 Ok(data) => data,
63 Err(_) => {
64 let e : Result<_, String> = Err::<ASTExecutionContext,String>("Invalid execution context JSON".to_string());
65 return serde_json::to_string(&e).unwrap()
66 }
67 };
68 let host = host.clone();
69 let res = execute_with(
70 AST(data.expression.into()),
71 data.variables,
72 data.computed,
73 data.device,
74 host,
75 ).map(|val| val.to_passable())
76 .map_err(|err| err.to_string());
77 serde_json::to_string(&res).unwrap()
78}
79
80pub fn evaluate_ast(ast: String) -> String {
86 let data: Result<JSONExpression,_> = serde_json::from_str(ast.as_str());
87 let data : JSONExpression = match data {
88 Ok(data) => data,
89 Err(_) => {
90 let e : Result<_, String> = Err::<JSONExpression,String>("Invalid definition for AST Execution".to_string());
91 return serde_json::to_string(&e).unwrap()
92 }
93 };
94 let ctx = Context::default();
95 let res = ctx.resolve(&data.into())
96 .map(|val| DisplayableValue(val.clone()).to_passable())
97 .map_err(|err| DisplayableError(err).to_string());
98 serde_json::to_string(&res).unwrap()
99}
100
101pub fn evaluate_with_context(definition: String, host: Arc<dyn HostContext>) -> String {
109 let data: Result<ExecutionContext,_> = serde_json::from_str(definition.as_str());
110 let data: ExecutionContext = match data {
111 Ok(data) => data,
112 Err(_) => {
113 let e : Result<ExecutionContext, String> = Err("Invalid execution context JSON".to_string());
114 return serde_json::to_string(&e).unwrap()
115 }
116 };
117 let compiled = Program::compile(data.expression.as_str())
118 .map(|program| CompiledProgram(program));
119 let result = match compiled {
120 Ok(compiled) => {
121 execute_with(
122 compiled,
123 data.variables,
124 data.computed,
125 data.device,
126 host,
127 ).map(|val| val.to_passable())
128 .map_err(|err| err.to_string())
129
130 }
131 Err(e) =>
132 Err("Failed to compile expression".to_string())
133 };
134 serde_json::to_string(&result).unwrap()
135}
136
137pub fn parse_to_ast(expression: String) -> String {
143 let ast: Result<JSONExpression, _> = parse(expression.as_str()).map(|expr| expr.into());
144 let ast = ast
145 .map_err(|err| err.to_string());
146 serde_json::to_string(&ast.unwrap()).unwrap()
147}
148
149enum ExecutableType {
153 AST(Expression),
154 CompiledProgram(Program),
155}
156
157fn execute_with(
165 executable: ExecutableType,
166 variables: PassableMap,
167 computed: Option<HashMap<String, Vec<PassableValue>>>,
168 device: Option<HashMap<String, Vec<PassableValue>>>,
169 host: Arc<dyn HostContext + 'static>,
170) -> Result<DisplayableValue, DisplayableError> {
171 let host = host.clone();
172 let host = Arc::new(Mutex::new(host));
173 let mut ctx = Context::default();
174 let device_map = variables.clone();
176 let device_map = device_map.map.get("device").clone().unwrap_or(&PMap(HashMap::new())).clone();
177
178 variables
180 .map
181 .iter()
182 .for_each(|it| {
183 let _ = ctx.add_variable(it.0.as_str(), it.1.to_cel());
184 });
185 ctx.add_function("maybe", maybe);
187
188 enum PropType {
193 Computed,
194 Device,
195 }
196 #[cfg(not(target_arch = "wasm32"))]
197 fn prop_for(
198 prop_type: PropType,
199 name: Arc<String>,
200 args: Option<Vec<PassableValue>>,
201 ctx: &Arc<dyn HostContext>,
202 ) -> Result<PassableValue, String> {
203 let val = futures_lite::future::block_on(async move {
205 let ctx = ctx.clone();
206 let args = if let Some(args) = args {
207 serde_json::to_string(&args)
208 } else {
209 serde_json::to_string::<Vec<PassableValue>>(&vec![])
210 };
211 match args {
212 Ok(args) => {
213 match prop_type {
214 PropType::Computed => Ok(ctx.computed_property(
215 name.clone().to_string(),
216 args,
217 ).await),
218 PropType::Device => Ok(ctx.device_property(
219 name.clone().to_string(),
220 args,
221 ).await),
222 }
223 }
224 Err(e) => {
225 Err(ExecutionError::UndeclaredReference(name).to_string())
226 }
227 }
228 });
229 let passable: Result<PassableValue, String> =
231 val.map(|val| serde_json::from_str(val.as_str()).unwrap_or(PassableValue::Null))
232 .map_err(|err| err.to_string());
233
234 passable
235 }
236
237 #[cfg(target_arch = "wasm32")]
238 fn prop_for(
239 prop_type: PropType,
240 name: Arc<String>,
241 args: Option<Vec<PassableValue>>,
242 ctx: &Arc<dyn HostContext>,
243 ) -> Option<PassableValue> {
244 let ctx = ctx.clone();
245
246 let val = match prop_type {
247 PropType::Computed => ctx.computed_property(
248 name.clone().to_string(),
249 serde_json::to_string(&args).expect("Failed to serialize args for computed property"),
250 ),
251 PropType::Device => ctx.device_property(
252 name.clone().to_string(),
253 serde_json::to_string(&args).expect("Failed to serialize args for computed property"),
254 ),
255 };
256 let passable: Option<PassableValue> = serde_json::from_str(val.as_str()).unwrap_or(Some(PassableValue::Null));
258
259 passable
260 }
261
262 let computed = computed.unwrap_or(HashMap::new()).clone();
263
264 let computed_host_properties: HashMap<Key, Value> = computed
266 .iter()
267 .map(|it| {
268 let args = it.1.clone();
269 let args = if args.is_empty() {
270 None
271 } else {
272 Some(Box::new(PassableValue::List(args)))
273 };
274 let name = it.0.clone();
275 (
276 Key::String(Arc::new(name.clone())),
277 Function(name, args).to_cel(),
278 )
279 })
280 .collect();
281
282 let device = device.unwrap_or(HashMap::new()).clone();
283
284
285 let total_device_properties = if let PMap(map) = device_map {
287 map
288 } else {
289 HashMap::new()
290 };
291
292 let device_host_properties: HashMap<Key, Value> = device
294 .iter()
295 .map(|it| {
296 let args = it.1.clone();
297 let args = if args.is_empty() {
298 None
299 } else {
300 Some(Box::new(PassableValue::List(args)))
301 };
302 let name = it.0.clone();
303 (
304 Key::String(Arc::new(name.clone())),
305 Function(name, args).to_cel(),
306 )
307 })
308 .chain(total_device_properties.iter().map(|(k, v)| (Key::String(Arc::new(k.clone())), v.to_cel().clone())))
309 .collect();
310
311 let _ = ctx.add_variable(
313 "computed",
314 Value::Map(Map {
315 map: Arc::new(computed_host_properties),
316 }),
317 );
318
319 let _ = ctx.add_variable(
321 "device",
322 Value::Map(Map {
323 map: Arc::new(device_host_properties),
324 }),
325 );
326
327
328 let binding = device.clone();
329 let host_properties = binding
331 .iter()
332 .chain(computed.iter())
333 .map(|(k, v)| (k.clone(), v.clone()))
334 .into_iter();
335
336 let mut device_properties_clone = device.clone().clone();
337 for it in host_properties {
339 let mut value = device_properties_clone.clone();
340 let key = it.0.clone();
341 let host_clone = Arc::clone(&host); let key_str = key.clone(); ctx.add_function(
344 key_str.as_str(),
345 move |ftx: &FunctionContext| -> Result<Value, ExecutionError> {
346 let device = value.clone();
347 let fx = ftx.clone();
348 let name = fx.name.clone(); let args = fx.args.clone(); let host = host_clone.lock(); match host {
352 Ok(host) => {
353 prop_for(
354 if device.contains_key(&it.0)
355 { PropType::Device } else { PropType::Computed },
356 name.clone(),
357 Some(
358 args.iter()
359 .map(|expression| {
360 DisplayableValue(ftx.ptx.resolve(expression).unwrap()).to_passable()
361 })
362 .collect(),
363 ),
364 &*host,
365 )
366 .map_or(Err(ExecutionError::UndeclaredReference(name)), |v| {
367 Ok(v.to_cel())
368 })
369 }
370 Err(e) => {
371 let e = e.to_string();
372 let name = name.clone().to_string();
373 let error = ExecutionError::FunctionError { function: name, message: e };
374 Err(error)
375 }
376 }
377 },
378 );
379 }
380
381 let val = match executable {
382 AST(ast) => &ctx.resolve(&ast),
383 CompiledProgram(program) => &program.execute(&ctx),
384 };
385
386 val.clone().map(|val| DisplayableValue(val.clone()))
387 .map_err(|err| DisplayableError(err))
388}
389
390pub fn maybe(
391 ftx: &FunctionContext,
392 This(_this): This<Value>,
393 left: Expression,
394 right: Expression,
395) -> Result<Value, ExecutionError> {
396 return ftx.ptx.resolve(&left).or_else(|_| ftx.ptx.resolve(&right));
397}
398
399pub struct DisplayableValue(Value);
401
402pub struct DisplayableError(ExecutionError);
403
404impl fmt::Display for DisplayableValue {
405 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406 match &self.0 {
407 Value::Int(i) => write!(f, "{}", i),
408 Value::Float(x) => write!(f, "{}", x),
409 Value::String(s) => write!(f, "{}", s),
410 Value::UInt(i) => write!(f, "{}", i),
412 Value::Bytes(_) => {
413 write!(f, "{}", "bytes go here")
414 }
415 Value::Bool(b) => write!(f, "{}", b),
416 Value::Duration(d) => write!(f, "{}", d),
417 Value::Timestamp(t) => write!(f, "{}", t),
418 Value::Null => write!(f, "{}", "null"),
419 Value::Function(name, _) => write!(f, "{}", name),
420 Value::Map(map) => {
421 let res: HashMap<String, String> = map
422 .map
423 .iter()
424 .map(|(k, v)| {
425 let key = DisplayableValue(k.try_into_value().unwrap().clone()).to_string();
426 let value = DisplayableValue(v.clone()).to_string().replace("\\", "");
427 (key, value)
428 })
429 .collect();
430 let map = serde_json::to_string(&res).unwrap();
431 write!(f, "{}", map)
432 }
433 Value::List(list) => write!(
434 f,
435 "{}",
436 list.iter()
437 .map(|v| {
438 let key = DisplayableValue(v.clone());
439 return key.to_string();
440 })
441 .collect::<Vec<_>>()
442 .join(",\n ")
443 ),
444 }
445 }
446}
447
448impl fmt::Display for DisplayableError {
449 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
450 write!(f, "{}", self.0.to_string().as_str())
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 struct TestContext {
459 map: HashMap<String, String>,
460 }
461
462 #[async_trait]
463 impl HostContext for TestContext {
464 async fn computed_property(&self, name: String, args: String) -> String {
465 self.map.get(&name).unwrap().to_string()
466 }
467
468 async fn device_property(&self, name: String, args: String) -> String {
469 self.map.get(&name).unwrap().to_string()
470 }
471 }
472
473 #[tokio::test]
474 async fn test_variables() {
475 let ctx = Arc::new(TestContext {
476 map: HashMap::new(),
477 });
478 let res = evaluate_with_context(
479 r#"
480 {
481 "variables": {
482 "map" : {
483 "foo": {"type": "int", "value": 100}
484 }},
485 "expression": "foo == 100"
486 }
487
488 "#
489 .to_string(),
490 ctx,
491 );
492 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
493 }
494
495 #[tokio::test]
496 async fn test_execution_with_ctx() {
497 let ctx = Arc::new(TestContext {
498 map: HashMap::new(),
499 });
500 let res = evaluate_with_context(
501 r#"
502 {
503 "variables": {
504 "map" : {
505 "foo": {"type": "int", "value": 100},
506 "bar": {"type": "int", "value": 42}
507 }},
508 "expression": "foo + bar == 142"
509 }
510
511 "#
512 .to_string(),
513 ctx,
514 );
515 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
516 }
517
518 #[test]
519 fn test_unknown_function_with_arg_fails_with_undeclared_ref() {
520 let ctx = Arc::new(TestContext {
521 map: HashMap::new(),
522 });
523
524 let res = evaluate_with_context(
525 r#"
526 {
527 "variables": {
528 "map" : {
529 "foo": {"type": "int", "value": 100}
530 }},
531 "expression": "test_custom_func(foo) == 101"
532 }
533
534 "#
535 .to_string(),
536 ctx,
537 );
538 assert_eq!(res, "{\"Err\":\"Undeclared reference to 'test_custom_func'\"}");
539 }
540
541 #[test]
542 fn test_list_contains() {
543 let ctx = Arc::new(TestContext {
544 map: HashMap::new(),
545 });
546 let res = evaluate_with_context(
547 r#"
548 {
549 "variables": {
550 "map" : {
551 "numbers": {
552 "type" : "list",
553 "value" : [
554 {"type": "int", "value": 1},
555 {"type": "int", "value": 2},
556 {"type": "int", "value": 3}
557 ]
558 }
559 }
560 },
561 "expression": "numbers.contains(2)"
562 }
563
564 "#
565 .to_string(),
566 ctx,
567 );
568 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
569 }
570
571 #[tokio::test]
572 async fn test_execution_with_map() {
573 let ctx = Arc::new(TestContext {
574 map: HashMap::new(),
575 });
576 let res = evaluate_with_context(
577 r#"
578 {
579 "variables": {
580 "map": {
581 "user": {
582 "type": "map",
583 "value": {
584 "should_display": {
585 "type": "bool",
586 "value": true
587 },
588 "some_value": {
589 "type": "uint",
590 "value": 13
591 }
592 }
593 }
594 }
595 },
596 "expression": "user.should_display == true && user.some_value > 12"
597 }
598
599 "#
600 .to_string(),
601 ctx,
602 );
603 println!("{}", res.clone());
604 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
605 }
606
607 #[tokio::test]
608 async fn test_execution_with_failure() {
609 let ctx = Arc::new(TestContext {
610 map: HashMap::new(),
611 });
612 let res = evaluate_with_context(
613 r#"
614 {
615 "variables": {
616 "map": {
617 "user": {
618 "type": "map",
619 "value": {
620 "some_value": {
621 "type": "uint",
622 "value": 13
623 }
624 }
625 }
626 }
627 },
628 "expression": "user.should_display == true && user.some_value > 12"
629 }
630
631 "#
632 .to_string(),
633 ctx,
634 );
635 println!("{}", res.clone());
636 assert_eq!(res, "{\"Err\":\"No such key: should_display\"}");
637 }
638
639 #[tokio::test]
640 async fn test_execution_with_null() {
641 let ctx = Arc::new(TestContext {
642 map: HashMap::new(),
643 });
644 let res = evaluate_with_context(
645 r#"
646 {
647 "variables": {
648 "map": {
649 "user": {
650 "type": "map",
651 "value": {
652 "some_value": {
653 "type": "Null",
654 "value": null
655 }
656 }
657 }
658 }
659 },
660 "expression": "user.should_display == true && user.some_value > 12"
661 }
662
663 "#
664 .to_string(),
665 ctx,
666 );
667 println!("{}", res.clone());
668 assert_eq!(res, "{\"Err\":\"No such key: should_display\"}");
669 }
670 #[tokio::test]
671 async fn test_execution_with_platform_computed_reference() {
672 let days_since = PassableValue::UInt(7);
673 let days_since = serde_json::to_string(&days_since).unwrap();
674 let ctx = Arc::new(TestContext {
675 map: [("minutesSince".to_string(), days_since)]
676 .iter()
677 .cloned()
678 .collect(),
679 });
680 let res = evaluate_with_context(
681 r#"
682 {
683 "variables": {
684 "map": {}
685 },
686 "expression": "device.minutesSince('app_launch') == computed.minutesSince('app_install')",
687 "computed": {
688 "daysSince": [
689 {
690 "type": "string",
691 "value": "event_name"
692 }
693 ],
694 "minutesSince": [
695 {
696 "type": "string",
697 "value": "event_name"
698 }
699 ],
700 "hoursSince": [
701 {
702 "type": "string",
703 "value": "event_name"
704 }
705 ],
706 "monthsSince": [
707 {
708 "type": "string",
709 "value": "event_name"
710 }
711 ]
712 },
713 "device": {
714 "daysSince": [
715 {
716 "type": "string",
717 "value": "event_name"
718 }
719 ],
720 "minutesSince": [
721 {
722 "type": "string",
723 "value": "event_name"
724 }
725 ],
726 "hoursSince": [
727 {
728 "type": "string",
729 "value": "event_name"
730 }
731 ],
732 "monthsSince": [
733 {
734 "type": "string",
735 "value": "event_name"
736 }
737 ]
738 }
739 }"#.to_string(),
740 ctx,
741 );
742 println!("{}", res.clone());
743 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
744 }
745
746 #[tokio::test]
747 async fn test_execution_with_platform_device_function_and_property() {
748 let days_since = PassableValue::UInt(7);
749 let days_since = serde_json::to_string(&days_since).unwrap();
750 let ctx = Arc::new(TestContext {
751 map: [("minutesSince".to_string(), days_since)]
752 .iter()
753 .cloned()
754 .collect(),
755 });
756 let res = evaluate_with_context(
757 r#"
758 {
759 "variables": {
760 "map": {
761 "device": {
762 "type": "map",
763 "value": {
764 "trial_days": {
765 "type": "uint",
766 "value": 7
767 }
768 }
769 }
770 }
771 },
772 "expression": "computed.minutesSince('app_launch') == device.trial_days",
773 "computed": {
774 "daysSince": [
775 {
776 "type": "string",
777 "value": "event_name"
778 }
779 ],
780 "minutesSince": [
781 {
782 "type": "string",
783 "value": "event_name"
784 }
785 ],
786 "hoursSince": [
787 {
788 "type": "string",
789 "value": "event_name"
790 }
791 ],
792 "monthsSince": [
793 {
794 "type": "string",
795 "value": "event_name"
796 }
797 ]
798 },
799 "device": {
800 "daysSince": [
801 {
802 "type": "string",
803 "value": "event_name"
804 }
805 ],
806 "minutesSince": [
807 {
808 "type": "string",
809 "value": "event_name"
810 }
811 ],
812 "hoursSince": [
813 {
814 "type": "string",
815 "value": "event_name"
816 }
817 ],
818 "monthsSince": [
819 {
820 "type": "string",
821 "value": "event_name"
822 }
823 ]
824 }
825 }"#.to_string(),
826 ctx,
827 );
828 println!("{}", res.clone());
829 assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}");
830 }
831
832
833 #[test]
834 fn test_parse_to_ast() {
835 let expression = "device.daysSince(app_install) == 3";
836 let ast_json = parse_to_ast(expression.to_string());
837 println!("\nSerialized AST:");
838 println!("{}", ast_json);
839 let deserialized_json_expr: JSONExpression = serde_json::from_str(&ast_json).unwrap();
841
842 let deserialized_expr: Expression = deserialized_json_expr.into();
844
845 println!("\nDeserialized Expression:");
846 println!("{:?}", deserialized_expr);
847
848 let parsed_expression = parse(expression).unwrap();
849 assert_eq!(parsed_expression, deserialized_expr);
850 println!("\nOriginal and deserialized expressions are equal!");
851 }
852}