clarkmcc / cel-rust Goto Github PK
View Code? Open in Web Editor NEWCommon Expression Language interpreter written in Rust
Home Page: https://crates.io/crates/cel-interpreter
License: MIT License
Common Expression Language interpreter written in Rust
Home Page: https://crates.io/crates/cel-interpreter
License: MIT License
fn main() {
let program = Program::compile("headers[\"Content-Type\"].contains(\"application/json\")").unwrap();
let mut context = Context::default();
let mut headers = HashMap::new();
headers.insert("Content-Type","application/json".to_string());
println!("{}",headers["Content-Type"]);
context.add_variable("headers", headers);
let value = program.execute(&context).unwrap();
assert_eq!(value, true.into());
}
headers["Content-Type"] 这样会报错,thread 'main' panicked at 'not implemented'
该如何实现呢,我知道headers.status这种写法可以,但是需要修改表达式
Benefits of using chumsky for parsing:
High level plan:
&&
, ||
, in
and ternary operations'1'.double()
or 10.double()
Do you want to keep both parsers? If so, how should the API work to pick between them? Assume it wouldn't be too tricky to add unsigned ints and un-escaped strings to the current lalrpop version?
At the moment, the functions
module is only available within the crate. I think it would be useful to make those public so that people can opt into/out of specific functions in this crate when initializing their Context
(for example if I only want contains
, has
, and exists
in my context).
I'm happy to PR this.
#[derive(Serialize)]
struct MidType<'a> {
body: &'a [u8],
raw: &'a [u8]
}
fn main() {
let program = Program::compile("foo.body.contains(1)").unwrap();
let mut context = Context::default();
context.add_variable("foo", MidType {
body: &[1,2,3],
raw: &[]
}).unwrap();
let v = program.execute(&context).unwrap();
println!("{:?}",v);
}
the body will be serialized as List[UInt], but the number 1 is Int type. output: Bool(false)
The official cel-spec has some additional macros / functions that might be useful for some of our use cases like: exists
or exists_one
.
Unfortunately I am not able to implement them by myself because: Context.clone()
is pub(crate)
and not pub
:
cel-rust/interpreter/src/context.rs
Line 115 in f4fa854
Am I missing something or wouldn't it be useful to provide the clone functionality and context shadowing also for custom extension functions?
I've been playing around with this CEL implementation and I noticed one odd thing with the following expressions:
b && (c == "string")
b && c == "string"
c == "string" && b
Given this context
{"b": True, "c": "string"}
they should all evaluate to true
, but this is not what's happening:
True <= b && (c == "string")
False <= b && c == "string"
True <= c == "string" && b
Here's a simple reproducer:
use cel_interpreter::{Context,Program, Value};
fn main() {
let expressions = [
"b && (c == \"string\")",
"b && c == \"string\"",
"c == \"string\" && b",
];
for expression in expressions {
let program = Program::compile(expression).unwrap();
let mut context = Context::default();
context.add_variable("b", Value::Bool(true));
context.add_variable("c", Value::String(String::from("string").into()));
let result = program.execute(&context);
println!("{:?} <= {}", result, expression)
}
}
It produces the following output:
Ok(Bool(true)) <= b && (c == "string")
Ok(Bool(false)) <= b && c == "string"
Ok(Bool(true)) <= c == "string" && b
It seems like in the case of b && c == "string"
the interpreter effectively evaluates this expression
(b && c) == "string"
I'm also using a Python version of CEL interpreter and it evaluates it properly:
import celpy
expressions = [
'b && (c == "string")',
'b && c == "string"',
'c == "string" && b',
]
for expression in expressions:
env = celpy.Environment()
ast = env.compile(expression)
prgm = env.program(ast)
activation = celpy.json_to_cel({"a": 1, "b": True, "c": "string"})
result = prgm.evaluate(activation)
print(f"{result} <= {expression}")
Produces
True <= b && (c == "string")
True <= b && c == "string"
True <= c == "string" && b
Would be nice if you could dynamically values in case your data set is too large to provide all upfront via context variables.
I'm think of something like:
// You can resolve dynamic values by providing a custom resolver function.
let external_values = Arc::new(HashMap::from([("hello".to_string(), "world".to_string())]));
let resolver = Some(move |ident: Arc<String>| {
let name: String = ident.to_string();
match external_values.get(name.as_str()) {
Some(v) => Ok(Value::String(v.clone().into())),
None => Err(ExecutionError::UndeclaredReference(name.into())),
}
});
let mut ctx = Context::default();
ctx.set_dynamic_resolver(resolver);
assert_eq!(test_script("hello == 'world'", Some(ctx)), Ok(true.into()));
I think the above should be doable without too many changes to the current apis and allow you to dynamically resolve simple variables.
But I don't think that enough, it would be nice if you could also write expression like this:
assert_eq!(test_script("github.orgs['clarkmcc'].projects['cel-rust'].license == 'MIT'", Some(ctx)), Ok(true.into()));
For that to work I think the resolver would need to return a Value that is treated like a Map but whose members are dynamic but it's data is backed by a user defined data type. I think I'm saying the above may need Value to carry a generic variant for custom data types. Thoughts?
I’m fairly new to Rust and am using this project. I’d like to convert the executed Value into a String so I can serialize it to JSON.
Does Value need to implement From for this to work?
I'm experimenting with writing a Python extension for this library using pyo3
and running into issues when it comes down to concurrency. I'm not really well-versed in Rust, but I asked about it here. As far as I understand it boils down to using Arc
instead of Rc
.
I'm currently using a Python version of CEL interpreter, but its performance leaves a lot to be desired, so I'm looking for an alternatives. I use CEL for feature flags so we have multiple compiled expressions which are evaluated from different threads.
What are your thoughts about it? What would it take to make the interpreter thread-safe?
I'm willing to help, but my Rust knowledge if very very limited :)
We should be able to add any type that implements Serialize as a variable to the context
I would expect that no expression can panic the interpreter.
unable to compare String("50") with Function("size", None)
thread 'limit::tests::cel::size_function_and_size_var' panicked at /Users/chirino/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cel-interpreter-0.7.0/src/objects.rs:270:23:
unable to compare String("50") with Function("size", None)
The library supports traversing maps using dot notation, but index notation is not supported
// Dot notation
foo.bar
// Index notation
foo["bar"]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.