extern crate core;
use cel_parser::parse;
use std::convert::TryFrom;
use std::sync::Arc;
use thiserror::Error;
mod macros;
pub mod context;
pub use cel_parser::Expression;
pub use context::Context;
pub use functions::FunctionContext;
pub use objects::{ResolveResult, Value};
mod duration;
mod functions;
mod magic;
pub mod objects;
mod resolvers;
mod ser;
pub use ser::to_value;
mod testing;
use magic::FromContext;
pub mod extractors {
pub use crate::magic::{Arguments, Identifier, This};
}
#[derive(Error, Debug)]
#[error("Error parsing {msg}")]
pub struct ParseError {
msg: String,
}
#[derive(Error, Debug, PartialEq, Clone)]
pub enum ExecutionError {
#[error("Invalid argument count: expected {expected}, got {actual}")]
InvalidArgumentCount { expected: usize, actual: usize },
#[error("Invalid argument type: {:?}", .target)]
UnsupportedTargetType { target: Value },
#[error("Method '{method}' not supported on type '{target:?}'")]
NotSupportedAsMethod { method: String, target: Value },
#[error("Unable to use value '{0:?}' as a key")]
UnsupportedKeyType(Value),
#[error("Unexpected type: got '{got}', want '{want}'")]
UnexpectedType { got: String, want: String },
#[error("No such key: {0}")]
NoSuchKey(Arc<String>),
#[error("Undeclared reference to '{0}'")]
UndeclaredReference(Arc<String>),
#[error("Missing argument or target")]
MissingArgumentOrTarget,
#[error("{0:?} can not be compared to {1:?}")]
ValuesNotComparable(Value, Value),
#[error("Error executing function '{function}': {message}")]
FunctionError { function: String, message: String },
}
impl ExecutionError {
pub fn no_such_key(name: &str) -> Self {
ExecutionError::NoSuchKey(Arc::new(name.to_string()))
}
pub fn undeclared_reference(name: &str) -> Self {
ExecutionError::UndeclaredReference(Arc::new(name.to_string()))
}
pub fn invalid_argument_count(expected: usize, actual: usize) -> Self {
ExecutionError::InvalidArgumentCount { expected, actual }
}
pub fn function_error<E: ToString>(function: &str, error: E) -> Self {
ExecutionError::FunctionError {
function: function.to_string(),
message: error.to_string(),
}
}
pub fn unsupported_target_type(target: Value) -> Self {
ExecutionError::UnsupportedTargetType { target }
}
pub fn not_supported_as_method(method: &str, target: Value) -> Self {
ExecutionError::NotSupportedAsMethod {
method: method.to_string(),
target,
}
}
pub fn unsupported_key_type(value: Value) -> Self {
ExecutionError::UnsupportedKeyType(value)
}
pub fn missing_argument_or_target() -> Self {
ExecutionError::MissingArgumentOrTarget
}
}
#[derive(Debug)]
pub struct Program {
expression: Expression,
}
impl Program {
pub fn compile(source: &str) -> Result<Program, ParseError> {
match parse(source) {
Ok(expression) => Ok(Program { expression }),
Err(e) => Err(ParseError {
msg: format!("{}", e),
}),
}
}
pub fn execute(&self, context: &Context) -> ResolveResult {
Value::resolve(&self.expression, context)
}
}
impl TryFrom<&str> for Program {
type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Program::compile(value)
}
}
#[cfg(test)]
mod tests {
use crate::context::Context;
use crate::objects::{ResolveResult, Value};
use crate::testing::test_script;
use crate::{ExecutionError, Program};
use std::collections::HashMap;
use std::convert::TryInto;
#[test]
fn parse() {
Program::compile("1 + 1").unwrap();
}
#[test]
fn from_str() {
let input = "1.1";
let _p: Program = input.try_into().unwrap();
}
#[test]
fn variables() {
fn assert_output(script: &str, expected: ResolveResult) {
let mut ctx = Context::default();
ctx.add_variable_from_value("foo", HashMap::from([("bar", 1i64)]));
ctx.add_variable_from_value("arr", vec![1i64, 2, 3]);
ctx.add_variable_from_value("str", "foobar".to_string());
assert_eq!(test_script(script, Some(ctx)), expected);
}
assert_output("size([1, 2, 3]) == 3", Ok(true.into()));
assert_output("size([]) == 3", Ok(false.into()));
assert_output("foo.bar == 1", Ok(true.into()));
assert_output("arr[0] == 1", Ok(true.into()));
assert_output("str[0] == 'f'", Ok(true.into()));
assert_output(
"{'a': 1} + {'a': 2, 'b': 3}",
Ok(HashMap::from([("a", 2), ("b", 3)]).into()),
);
}
#[test]
fn test_contains() {
let tests = vec![
("list", "[1, 2, 3].contains(3) == true"),
("map", "{1: true, 2: true, 3: true}.contains(3) == true"),
("string", "'foobar'.contains('bar') == true"),
("bytes", "b'foobar'.contains(b'o') == true"),
];
for (name, script) in tests {
assert_eq!(test_script(script, None), Ok(true.into()), "{}", name);
}
}
#[test]
fn test_execution_errors() {
let tests = vec![
(
"no such key",
"foo.baz.bar == 1",
ExecutionError::no_such_key("baz"),
),
(
"undeclared reference",
"missing == 1",
ExecutionError::undeclared_reference("missing"),
),
(
"unsupported key type",
"{null: true}",
ExecutionError::unsupported_key_type(Value::Null),
),
];
for (name, script, error) in tests {
let mut ctx = Context::default();
ctx.add_variable_from_value("foo", HashMap::from([("bar", 1)]));
let res = test_script(script, Some(ctx));
assert_eq!(res, error.into(), "{}", name);
}
}
}