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