diff --git a/Cargo.toml b/Cargo.toml index f0621dd..e308ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,32 @@ [package] -name = "cel-eval" -version = "0.2.1" +name = "superscript" +version = "0.2.3" edition = "2021" +description = "A Common Expression Language (CEL) interpreter for Rust" +license = "MIT" +repository = "https://github.com/superwall/superscript" +homepage = "https://github.com/superwall/superscript" +documentation = "https://github.com/superwall/superscript" +include = [ + "/src/**/*", + "/Cargo.toml", + "/build.rs", + "/uniffi.toml", + "/README.md", + "/LICENSE", +] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.htmlž [lib] -name = "cel_eval" -crate-type = ["staticlib","cdylib", "rlib"] +name = "superscript" +crate-type = ["staticlib", "cdylib", "rlib"] path = "src/lib.rs" [dependencies] cel-interpreter = "0.8.1" cel-parser = "0.7.1" -uniffi = { version = "0.28" } +uniffi = { version = "0.28", features = ["cli"] } serde = { version = "1.0", features = ["serde_derive"] } serde_json = { version = "1.0" } async-trait = "0.1.81" @@ -23,16 +37,16 @@ futures-lite = "2.3.0" [dev-dependencies] tokio = { version = "^1.20", features = ["rt-multi-thread", "macros"] } [build-dependencies] -uniffi = { version = "0.28", features = [ "build" ] } +uniffi = { version = "0.28", features = ["build"] } [[bin]] name = "uniffi-bindgen" path = "uniffi-bindgen.rs" [profile.release] -opt-level = "z" # Optimize for size. +opt-level = "z" # Optimize for size. lto = "fat" -debug=false +debug = false incremental = false overflow-checks = false codegen-units = 1 diff --git a/src/lib.rs b/src/lib.rs index e68e381..4023b18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,32 +1,31 @@ #[cfg(not(target_arch = "wasm32"))] uniffi::include_scaffolding!("cel"); -mod ast; -mod models; +pub mod ast; +pub mod models; use crate::ast::{ASTExecutionContext, JSONExpression}; use crate::models::PassableValue::Function; -use crate::models::{ExecutionContext, PassableMap, PassableValue}; use crate::models::PassableValue::PMap; +pub use crate::models::{ExecutionContext, PassableMap, PassableValue}; use crate::ExecutableType::{CompiledProgram, AST}; use async_trait::async_trait; use cel_interpreter::extractors::This; use cel_interpreter::objects::{Key, Map, TryIntoValue}; use cel_interpreter::{Context, ExecutionError, Expression, FunctionContext, Program, Value}; +use cel_parser::parse; use std::collections::HashMap; use std::error::Error; use std::fmt; use std::fmt::Debug; use std::ops::Deref; -use std::sync::{Arc, mpsc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::thread::spawn; -use cel_parser::parse; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen_futures::spawn_local; #[cfg(not(target_arch = "wasm32"))] use futures_lite::future::block_on; use uniffi::deps::log::__private_api::log; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_futures::spawn_local; /** * Host context trait that defines the methods that the host context should implement, @@ -57,12 +56,13 @@ pub trait HostContext: Send + Sync { * @return The result of the evaluation, either "true" or "false" */ pub fn evaluate_ast_with_context(definition: String, host: Arc) -> String { - let data: Result = serde_json::from_str(definition.as_str()); + let data: Result = serde_json::from_str(definition.as_str()); let data = match data { Ok(data) => data, Err(_) => { - let e : Result<_, String> = Err::("Invalid execution context JSON".to_string()); - return serde_json::to_string(&e).unwrap() + let e: Result<_, String> = + Err::("Invalid execution context JSON".to_string()); + return serde_json::to_string(&e).unwrap(); } }; let host = host.clone(); @@ -72,8 +72,9 @@ pub fn evaluate_ast_with_context(definition: String, host: Arc) data.computed, data.device, host, - ).map(|val| val.to_passable()) - .map_err(|err| err.to_string()); + ) + .map(|val| val.to_passable()) + .map_err(|err| err.to_string()); serde_json::to_string(&res).unwrap() } @@ -83,16 +84,18 @@ pub fn evaluate_ast_with_context(definition: String, host: Arc) * @return The result of the evaluation, either "true" or "false" */ pub fn evaluate_ast(ast: String) -> String { - let data: Result = serde_json::from_str(ast.as_str()); - let data : JSONExpression = match data { + let data: Result = serde_json::from_str(ast.as_str()); + let data: JSONExpression = match data { Ok(data) => data, Err(_) => { - let e : Result<_, String> = Err::("Invalid definition for AST Execution".to_string()); - return serde_json::to_string(&e).unwrap() + let e: Result<_, String> = + Err::("Invalid definition for AST Execution".to_string()); + return serde_json::to_string(&e).unwrap(); } }; let ctx = Context::default(); - let res = ctx.resolve(&data.into()) + let res = ctx + .resolve(&data.into()) .map(|val| DisplayableValue(val.clone()).to_passable()) .map_err(|err| DisplayableError(err).to_string()); serde_json::to_string(&res).unwrap() @@ -106,30 +109,22 @@ pub fn evaluate_ast(ast: String) -> String { */ pub fn evaluate_with_context(definition: String, host: Arc) -> String { - let data: Result = serde_json::from_str(definition.as_str()); + let data: Result = serde_json::from_str(definition.as_str()); let data: ExecutionContext = match data { Ok(data) => data, Err(_) => { - let e : Result = Err("Invalid execution context JSON".to_string()); - return serde_json::to_string(&e).unwrap() + let e: Result = + Err("Invalid execution context JSON".to_string()); + return serde_json::to_string(&e).unwrap(); } }; - let compiled = Program::compile(data.expression.as_str()) - .map(|program| CompiledProgram(program)); + let compiled = + Program::compile(data.expression.as_str()).map(|program| CompiledProgram(program)); let result = match compiled { - Ok(compiled) => { - execute_with( - compiled, - data.variables, - data.computed, - data.device, - host, - ).map(|val| val.to_passable()) - .map_err(|err| err.to_string()) - - } - Err(e) => - Err("Failed to compile expression".to_string()) + Ok(compiled) => execute_with(compiled, data.variables, data.computed, data.device, host) + .map(|val| val.to_passable()) + .map_err(|err| err.to_string()), + Err(e) => Err("Failed to compile expression".to_string()), }; serde_json::to_string(&result).unwrap() } @@ -141,8 +136,7 @@ pub fn evaluate_with_context(definition: String, host: Arc) -> */ pub fn parse_to_ast(expression: String) -> String { let ast: Result = parse(expression.as_str()).map(|expr| expr.into()); - let ast = ast - .map_err(|err| err.to_string()); + let ast = ast.map_err(|err| err.to_string()); serde_json::to_string(&ast.unwrap()).unwrap() } @@ -173,15 +167,17 @@ fn execute_with( let mut ctx = Context::default(); // Isolate device to re-bind later let device_map = variables.clone(); - let device_map = device_map.map.get("device").clone().unwrap_or(&PMap(HashMap::new())).clone(); + let device_map = device_map + .map + .get("device") + .clone() + .unwrap_or(&PMap(HashMap::new())) + .clone(); // Add predefined variables locally to the context - variables - .map - .iter() - .for_each(|it| { - let _ = ctx.add_variable(it.0.as_str(), it.1.to_cel()); - }); + variables.map.iter().for_each(|it| { + let _ = ctx.add_variable(it.0.as_str(), it.1.to_cel()); + }); // Add maybe function ctx.add_function("maybe", maybe); @@ -209,27 +205,21 @@ fn execute_with( serde_json::to_string::>(&vec![]) }; match args { - Ok(args) => { - match prop_type { - PropType::Computed => Ok(ctx.computed_property( - name.clone().to_string(), - args, - ).await), - PropType::Device => Ok(ctx.device_property( - name.clone().to_string(), - args, - ).await), + Ok(args) => match prop_type { + PropType::Computed => { + Ok(ctx.computed_property(name.clone().to_string(), args).await) } - } - Err(e) => { - Err(ExecutionError::UndeclaredReference(name).to_string()) - } + PropType::Device => { + Ok(ctx.device_property(name.clone().to_string(), args).await) + } + }, + Err(e) => Err(ExecutionError::UndeclaredReference(name).to_string()), } }); // Deserialize the value - let passable: Result = - val.map(|val| serde_json::from_str(val.as_str()).unwrap_or(PassableValue::Null)) - .map_err(|err| err.to_string()); + let passable: Result = val + .map(|val| serde_json::from_str(val.as_str()).unwrap_or(PassableValue::Null)) + .map_err(|err| err.to_string()); passable } @@ -246,15 +236,18 @@ fn execute_with( let val = match prop_type { PropType::Computed => ctx.computed_property( name.clone().to_string(), - serde_json::to_string(&args).expect("Failed to serialize args for computed property"), + serde_json::to_string(&args) + .expect("Failed to serialize args for computed property"), ), PropType::Device => ctx.device_property( name.clone().to_string(), - serde_json::to_string(&args).expect("Failed to serialize args for computed property"), + serde_json::to_string(&args) + .expect("Failed to serialize args for computed property"), ), }; // Deserialize the value - let passable: Option = serde_json::from_str(val.as_str()).unwrap_or(Some(PassableValue::Null)); + let passable: Option = + serde_json::from_str(val.as_str()).unwrap_or(Some(PassableValue::Null)); passable } @@ -281,7 +274,6 @@ fn execute_with( let device = device.unwrap_or(HashMap::new()).clone(); - // From defined properties the device properties let total_device_properties = if let PMap(map) = device_map { map @@ -305,7 +297,11 @@ fn execute_with( Function(name, args).to_cel(), ) }) - .chain(total_device_properties.iter().map(|(k, v)| (Key::String(Arc::new(k.clone())), v.to_cel().clone()))) + .chain( + total_device_properties + .iter() + .map(|(k, v)| (Key::String(Arc::new(k.clone())), v.to_cel().clone())), + ) .collect(); // Add the map to the `computed` object @@ -324,7 +320,6 @@ fn execute_with( }), ); - let binding = device.clone(); // Combine the device and computed properties let host_properties = binding @@ -349,28 +344,33 @@ fn execute_with( let args = fx.args.clone(); // Clone the arguments let host = host_clone.lock(); // Lock the host for safe access match host { - Ok(host) => { - prop_for( - if device.contains_key(&it.0) - { PropType::Device } else { PropType::Computed }, - name.clone(), - Some( - args.iter() - .map(|expression| { - DisplayableValue(ftx.ptx.resolve(expression).unwrap()).to_passable() - }) - .collect(), - ), - &*host, - ) - .map_or(Err(ExecutionError::UndeclaredReference(name)), |v| { - Ok(v.to_cel()) - }) - } + Ok(host) => prop_for( + if device.contains_key(&it.0) { + PropType::Device + } else { + PropType::Computed + }, + name.clone(), + Some( + args.iter() + .map(|expression| { + DisplayableValue(ftx.ptx.resolve(expression).unwrap()) + .to_passable() + }) + .collect(), + ), + &*host, + ) + .map_or(Err(ExecutionError::UndeclaredReference(name)), |v| { + Ok(v.to_cel()) + }), Err(e) => { let e = e.to_string(); let name = name.clone().to_string(); - let error = ExecutionError::FunctionError { function: name, message: e }; + let error = ExecutionError::FunctionError { + function: name, + message: e, + }; Err(error) } } @@ -383,7 +383,8 @@ fn execute_with( CompiledProgram(program) => &program.execute(&ctx), }; - val.clone().map(|val| DisplayableValue(val.clone())) + val.clone() + .map(|val| DisplayableValue(val.clone())) .map_err(|err| DisplayableError(err)) } @@ -486,7 +487,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}"); @@ -509,7 +510,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}"); @@ -532,10 +533,13 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); - assert_eq!(res, "{\"Err\":\"Undeclared reference to 'test_custom_func'\"}"); + assert_eq!( + res, + "{\"Err\":\"Undeclared reference to 'test_custom_func'\"}" + ); } #[test] @@ -562,7 +566,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}"); @@ -597,7 +601,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); println!("{}", res.clone()); @@ -629,7 +633,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); println!("{}", res.clone()); @@ -661,7 +665,7 @@ mod tests { } "# - .to_string(), + .to_string(), ctx, ); println!("{}", res.clone()); @@ -736,7 +740,8 @@ mod tests { } ] } - }"#.to_string(), + }"# + .to_string(), ctx, ); println!("{}", res.clone()); @@ -822,14 +827,14 @@ mod tests { } ] } - }"#.to_string(), + }"# + .to_string(), ctx, ); println!("{}", res.clone()); assert_eq!(res, "{\"Ok\":{\"type\":\"bool\",\"value\":true}}"); } - #[test] fn test_parse_to_ast() { let expression = "device.daysSince(app_install) == 3"; diff --git a/src/models.rs b/src/models.rs index 588768f..b4e731f 100644 --- a/src/models.rs +++ b/src/models.rs @@ -6,11 +6,11 @@ use std::collections::HashMap; use std::sync::Arc; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub(crate) struct ExecutionContext { - pub(crate) variables: PassableMap, - pub(crate) expression: String, - pub(crate) computed: Option>>, - pub(crate) device: Option>> +pub struct ExecutionContext { + pub variables: PassableMap, + pub expression: String, + pub computed: Option>>, + pub device: Option>>, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index c9dbe7e..8ebaa80 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "superscript" +name = "superscript-wasm" version = "0.2.1" edition = "2021" publish = false @@ -9,7 +9,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -cel-eval = { path = ".." } +superscript = { path = ".." } wasm-bindgen = "0.2.93" wasm-bindgen-futures = "0.4.43" futures = "0.3.30" @@ -17,7 +17,7 @@ console_error_panic_hook = "0.1.7" [profile.release] lto = true -opt-level = "z" # Optimize for size. +opt-level = "z" # Optimize for size. codegen-units = 1 panic = "abort" -strip=true +strip = true diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 3c50511..32f58a7 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -1,9 +1,8 @@ -use std::sync::{Arc}; +use std::sync::Arc; -use wasm_bindgen::JsValue; +pub use superscript::HostContext; use wasm_bindgen::prelude::wasm_bindgen; -pub use cel_eval::HostContext; - +use wasm_bindgen::JsValue; /** * This is the definition for the JS Host module contracts. @@ -18,16 +17,24 @@ extern "C" { //fn log(s: &str); /** - Defines the Rust type and method signatures of the JS Host context. - */ + Defines the Rust type and method signatures of the JS Host context. + */ #[wasm_bindgen(typescript_type = "WasmHostContext")] pub type JsHostContext; #[wasm_bindgen(method, catch)] - fn computed_property(this: &JsHostContext, name: String, args: String) -> Result; + fn computed_property( + this: &JsHostContext, + name: String, + args: String, + ) -> Result; #[wasm_bindgen(method, catch)] - fn device_property(this: &JsHostContext, name: String, args: String) -> Result; + fn device_property( + this: &JsHostContext, + name: String, + args: String, + ) -> Result; } @@ -57,7 +64,6 @@ impl HostContextAdapter { } impl HostContext for HostContextAdapter { - /** * This method is used to call the computed property method on the JS Host context. * It proxies evaluator calls for `platform.something(arg)` to the JS Host context itself. @@ -65,54 +71,68 @@ impl HostContext for HostContextAdapter { fn computed_property(&self, name: String, args: String) -> String { let context = Arc::clone(&self.context); let promise = context.computed_property(name.clone(), args.clone()); - let result = promise.expect("Did not receive the proper result from computed").as_string(); - result - .clone() - .expect( - format!("Could not deserialize the result from computed property - Is some: {}", result.is_some()).as_str()) + let result = promise + .expect("Did not receive the proper result from computed") + .as_string(); + result.clone().expect( + format!( + "Could not deserialize the result from computed property - Is some: {}", + result.is_some() + ) + .as_str(), + ) } fn device_property(&self, name: String, args: String) -> String { let context = Arc::clone(&self.context); let promise = context.device_property(name.clone(), args.clone()); - let result = promise.expect("Did not receive the proper result from computed").as_string(); - result - .clone() - .expect( - format!("Could not deserialize the result from computed property - Is some: {}", result.is_some()).as_str()) + let result = promise + .expect("Did not receive the proper result from computed") + .as_string(); + result.clone().expect( + format!( + "Could not deserialize the result from computed property - Is some: {}", + result.is_some() + ) + .as_str(), + ) } - } unsafe impl Send for HostContextAdapter {} unsafe impl Sync for HostContextAdapter {} - #[wasm_bindgen] -pub async fn evaluate_with_context(definition: String, context: JsHostContext) -> Result { +pub async fn evaluate_with_context( + definition: String, + context: JsHostContext, +) -> Result { let adapter = Arc::new(HostContextAdapter::new(context)); - Ok(cel_eval::evaluate_with_context(definition, adapter)) + Ok(superscript::evaluate_with_context(definition, adapter)) } #[wasm_bindgen] -pub async fn evaluate_ast_with_context(definition: String, context: JsHostContext) -> Result { +pub async fn evaluate_ast_with_context( + definition: String, + context: JsHostContext, +) -> Result { let adapter = Arc::new(HostContextAdapter::new(context)); - Ok(cel_eval::evaluate_ast_with_context(definition, adapter)) + Ok(superscript::evaluate_ast_with_context(definition, adapter)) } #[wasm_bindgen] pub async fn evaluate_ast(ast: String) -> Result { - Ok(cel_eval::evaluate_ast(ast)) + Ok(superscript::evaluate_ast(ast)) } #[wasm_bindgen] pub async fn parse_into_ast(expression: String) -> Result { - Ok(cel_eval::parse_to_ast(expression)) + Ok(superscript::parse_to_ast(expression)) } #[cfg(test)] mod tests { #[test] fn test() {} -} \ No newline at end of file +}