4 releases (breaking)
new 0.4.0 | Jan 9, 2025 |
---|---|
0.3.0 | Jan 5, 2025 |
0.2.0 | Dec 31, 2024 |
0.1.0 | Dec 30, 2024 |
#131 in Programming languages
412 downloads per month
330KB
9K
SLoC
Cellang
Cellang is an implementation of the CEL language interpreter in Rust.
Motivation
Motivation behind this project is to provide a way to evaluate CEL expressions in Rust, while allowing easier way to provide custom functions. This project is built for BountyHub project, but is open-source and can be used by anyone.
There is a great rust project called CEL Interpreter which I initially used.
However, I found that the project is not flexible enough for my needs. I needed to be able to:
- Inspect the AST of the program during validations
- Add slightly more complex functions on types.
Therefore, the library exposes lower-level primitives that would allow you to do that.
Getting started
This library aims to be as simple as possible to use. You build up an environment, and then you evaluate the expression with it.
The environment is built using environment builder. The reason is that you can mutate it. Once the
environment is done, you can build()
it. Build takes the reference to the builder, so it is tied
to it.
Let's show more complicated example (user_role). Check-out the examples directory for more examples, or consider contributing one!
use std::sync::Arc;
use cellang::{Environment, EnvironmentBuilder, TokenTree, Value};
use miette::Error;
use serde::{Deserialize, Serialize};
fn main() {
// Creates a root environment
let mut env = EnvironmentBuilder::default();
// Fetches the required variables from the database
let users = list_users().unwrap();
// Adds the users to the environment
env.set_variable("users", users).unwrap();
// Add a custom function to the environment
env.set_function("has_role", Arc::new(has_role));
// Let's say the program tries to get the number of users with particular role
let program = "size(users.filter(u, u.has_role(role)))";
// Now, we want to calculate users with role 'admin'
env.set_variable("role", "admin").unwrap();
// Get number of admin users
let n: i64 = cellang::eval(&env.build(), program)
.expect("Failed to evaluate the expression")
.into();
println!("Number of admin users: {}", n);
// Or role 'user'
env.set_variable("role", "user").unwrap();
// Get number of admin users
let n: i64 = cellang::eval(&env.build(), program)
.expect("Failed to evaluate the expression")
.into();
println!("Number of users: {}", n);
}
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub name: String,
pub roles: Vec<String>,
}
fn list_users() -> Result<Vec<User>, Error> {
Ok(vec![
User {
name: "Alice".into(),
roles: vec!["admin".into()],
},
User {
name: "Bob".into(),
roles: vec!["user".into()],
},
User {
name: "Charlie".into(),
roles: vec!["admin".into(), "user".into()],
},
User {
name: "David".into(),
roles: vec!["user".into()],
},
])
}
fn has_role(env: &Environment, tokens: &[TokenTree]) -> Result<Value, Error> {
if tokens.len() != 2 {
miette::bail!("Expected 2 arguments, got {}", tokens.len());
}
let user: User = match cellang::eval_ast(env, &tokens[0])?.to_value()? {
Value::Map(m) => m.try_into()?,
_ => miette::bail!("Expected a map, got something else"),
};
let role = match cellang::eval_ast(env, &tokens[1])?.to_value()? {
Value::String(s) => s,
_ => miette::bail!("Expected a string, got something else"),
};
Ok(user.roles.contains(&role).into())
}
Dependencies
~5–6.5MB
~118K SLoC