extern crate core;
use std::convert::TryFrom;
use std::sync::Arc;
use thiserror::Error;
mod macros;
pub mod context;
pub use cel_parser::ast::IdedExpr;
use cel_parser::ast::SelectExpr;
use cel_parser::{Expression, ExpressionReferences, Parser};
pub use cel_parser::{ParseError, ParseErrors};
pub use context::Context;
pub use functions::FunctionContext;
pub use objects::{ResolveResult, Value};
pub mod functions;
mod magic;
pub mod objects;
mod resolvers;
#[cfg(feature = "chrono")]
mod duration;
#[cfg(feature = "chrono")]
pub use ser::{Duration, Timestamp};
mod ser;
pub use ser::to_value;
pub use ser::SerializationError;
#[cfg(feature = "json")]
mod json;
#[cfg(feature = "json")]
pub use json::ConvertToJsonError;
use magic::FromContext;
pub mod extractors {
pub use crate::magic::{Arguments, Identifier, This};
}
#[derive(Error, Clone, Debug, PartialEq)]
#[non_exhaustive]
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("Unsupported unary operator '{0}': {1:?}")]
UnsupportedUnaryOperator(&'static str, Value),
#[error("Unsupported binary operator '{0}': {1:?}, {2:?}")]
UnsupportedBinaryOperator(&'static str, Value, Value),
#[error("Cannot use value as map index: {0:?}")]
UnsupportedMapIndex(Value),
#[error("Cannot use value as list index: {0:?}")]
UnsupportedListIndex(Value),
#[error("Cannot use value {0:?} to index {1:?}")]
UnsupportedIndex(Value, Value),
#[error("Unsupported function call identifier type: {0:?}")]
UnsupportedFunctionCallIdentifierType(Expression),
#[error("Unsupported fields construction: {0:?}")]
UnsupportedFieldsConstruction(SelectExpr),
#[error("Error executing function '{function}': {message}")]
FunctionError { function: String, message: String },
#[error("Division by zero of {0:?}")]
DivisionByZero(Value),
#[error("Remainder by zero of {0:?}")]
RemainderByZero(Value),
#[error("Overflow from binary operator '{0}': {1:?}, {2:?}")]
Overflow(&'static str, Value, Value),
}
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, ParseErrors> {
let parser = Parser::default();
parser
.parse(source)
.map(|expression| Program { expression })
}
pub fn execute(&self, context: &Context) -> ResolveResult {
Value::resolve(&self.expression, context)
}
pub fn references(&self) -> ExpressionReferences {
self.expression.references()
}
}
impl TryFrom<&str> for Program {
type Error = ParseErrors;
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::{ExecutionError, Program};
use std::collections::HashMap;
use std::convert::TryInto;
pub(crate) fn test_script(script: &str, ctx: Option<Context>) -> ResolveResult {
let program = match Program::compile(script) {
Ok(p) => p,
Err(e) => panic!("{}", e),
};
program.execute(&ctx.unwrap_or_default())
}
#[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()));
}
#[test]
fn references() {
let p = Program::compile("[1, 1].map(x, x * 2)").unwrap();
assert!(p.references().has_variable("x"));
assert_eq!(p.references().variables().len(), 1);
}
#[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"),
),
(
"undeclared method",
"1.missing()",
ExecutionError::undeclared_reference("missing"),
),
(
"undeclared function",
"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}");
}
}
}