зеркало из https://github.com/mozilla/jexl-rs.git
Adds filtering collections
This commit is contained in:
Родитель
b9b1fe6805
Коммит
848181fab7
|
@ -29,6 +29,8 @@ pub enum EvaluationError<'a> {
|
|||
JSONError(#[from] serde_json::Error),
|
||||
#[error("Custom error: {0}")]
|
||||
CustomError(#[from] anyhow::Error),
|
||||
#[error("Invalid filter")]
|
||||
InvalidFilter,
|
||||
}
|
||||
|
||||
impl<'a> From<ParseError<usize, Token<'a>, &'a str>> for EvaluationError<'a> {
|
||||
|
|
|
@ -177,6 +177,22 @@ impl<'a> Evaluator<'a> {
|
|||
|
||||
Expression::IndexOperation { subject, index } => {
|
||||
let subject = self.eval_ast(*subject, context)?;
|
||||
if let Expression::Filter { ident, op, right } = *index {
|
||||
let subject_arr = subject.as_array().ok_or(EvaluationError::InvalidFilter)?;
|
||||
let right = self.eval_ast(*right, context)?;
|
||||
let filtered = subject_arr
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let left = e.get(&ident).unwrap_or(&value!(null));
|
||||
// returns false if any members fail the op, could happen if array members are missing the identifier
|
||||
Self::apply_op(op, left.clone(), right.clone())
|
||||
.unwrap_or(value!(false))
|
||||
.is_truthy()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return Ok(value!(filtered));
|
||||
}
|
||||
|
||||
let index = self.eval_ast(*index, context)?;
|
||||
match index {
|
||||
Value::String(inner) => {
|
||||
|
@ -197,46 +213,7 @@ impl<'a> Evaluator<'a> {
|
|||
} => {
|
||||
let left = self.eval_ast(*left, context)?;
|
||||
let right = self.eval_ast(*right, context)?;
|
||||
match (operation, left, right) {
|
||||
(OpCode::And, a, b) => Ok(if a.is_truthy() { b } else { a }),
|
||||
(OpCode::Or, a, b) => Ok(if a.is_truthy() { a } else { b }),
|
||||
|
||||
(op, Value::Number(a), Value::Number(b)) => {
|
||||
let left = a.as_f64().unwrap();
|
||||
let right = b.as_f64().unwrap();
|
||||
Ok(match op {
|
||||
OpCode::Add => value!(left + right),
|
||||
OpCode::Subtract => value!(left - right),
|
||||
OpCode::Multiply => value!(left * right),
|
||||
OpCode::Divide => value!(left / right),
|
||||
OpCode::FloorDivide => value!((left / right).floor()),
|
||||
OpCode::Modulus => value!(left % right),
|
||||
OpCode::Exponent => value!(left.powf(right)),
|
||||
OpCode::Less => value!(left < right),
|
||||
OpCode::Greater => value!(left > right),
|
||||
OpCode::LessEqual => value!(left <= right),
|
||||
OpCode::GreaterEqual => value!(left >= right),
|
||||
OpCode::Equal => value!((left - right).abs() < EPSILON),
|
||||
OpCode::NotEqual => value!((left - right).abs() > EPSILON),
|
||||
OpCode::In => value!(false),
|
||||
OpCode::And | OpCode::Or => {
|
||||
unreachable!("Covered by previous case in parent match")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(OpCode::Add, Value::String(a), Value::String(b)) => {
|
||||
Ok(value!(format!("{}{}", a, b)))
|
||||
}
|
||||
(OpCode::In, Value::String(a), Value::String(b)) => Ok(value!(b.contains(&a))),
|
||||
(OpCode::In, left, Value::Array(v)) => Ok(value!(v.contains(&left))),
|
||||
(OpCode::Equal, Value::String(a), Value::String(b)) => Ok(value!(a == b)),
|
||||
(operation, left, right) => Err(EvaluationError::InvalidBinaryOp {
|
||||
operation,
|
||||
left,
|
||||
right,
|
||||
}),
|
||||
}
|
||||
Self::apply_op(operation, left, right)
|
||||
}
|
||||
Expression::Transform {
|
||||
name,
|
||||
|
@ -269,6 +246,57 @@ impl<'a> Evaluator<'a> {
|
|||
self.eval_ast(*falsy, context)
|
||||
}
|
||||
}
|
||||
|
||||
Expression::Filter {
|
||||
ident: _,
|
||||
op: _,
|
||||
right: _,
|
||||
} => {
|
||||
// Filters shouldn't be evaluated individually
|
||||
// instead, they are evaluated as a part of an IndexOperation
|
||||
return Err(EvaluationError::InvalidFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_op<'b>(operation: OpCode, left: Value, right: Value) -> Result<'b, Value> {
|
||||
match (operation, left, right) {
|
||||
(OpCode::And, a, b) => Ok(if a.is_truthy() { b } else { a }),
|
||||
(OpCode::Or, a, b) => Ok(if a.is_truthy() { a } else { b }),
|
||||
|
||||
(op, Value::Number(a), Value::Number(b)) => {
|
||||
let left = a.as_f64().unwrap();
|
||||
let right = b.as_f64().unwrap();
|
||||
Ok(match op {
|
||||
OpCode::Add => value!(left + right),
|
||||
OpCode::Subtract => value!(left - right),
|
||||
OpCode::Multiply => value!(left * right),
|
||||
OpCode::Divide => value!(left / right),
|
||||
OpCode::FloorDivide => value!((left / right).floor()),
|
||||
OpCode::Modulus => value!(left % right),
|
||||
OpCode::Exponent => value!(left.powf(right)),
|
||||
OpCode::Less => value!(left < right),
|
||||
OpCode::Greater => value!(left > right),
|
||||
OpCode::LessEqual => value!(left <= right),
|
||||
OpCode::GreaterEqual => value!(left >= right),
|
||||
OpCode::Equal => value!((left - right).abs() < EPSILON),
|
||||
OpCode::NotEqual => value!((left - right).abs() > EPSILON),
|
||||
OpCode::In => value!(false),
|
||||
OpCode::And | OpCode::Or => {
|
||||
unreachable!("Covered by previous case in parent match")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(OpCode::Add, Value::String(a), Value::String(b)) => Ok(value!(format!("{}{}", a, b))),
|
||||
(OpCode::In, Value::String(a), Value::String(b)) => Ok(value!(b.contains(&a))),
|
||||
(OpCode::In, left, Value::Array(v)) => Ok(value!(v.contains(&left))),
|
||||
(OpCode::Equal, Value::String(a), Value::String(b)) => Ok(value!(a == b)),
|
||||
(operation, left, right) => Err(EvaluationError::InvalidBinaryOp {
|
||||
operation,
|
||||
left,
|
||||
right,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,7 +378,6 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_context_filter_arrays() {
|
||||
let context = value!({
|
||||
"foo": {
|
||||
|
@ -606,4 +633,22 @@ mod tests {
|
|||
panic!("Should have returned a Custom error!")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_collections_many_returned() {
|
||||
let evaluator = Evaluator::new();
|
||||
let context = value!({
|
||||
"foo": [
|
||||
{"bobo": 50, "fofo": 100},
|
||||
{"bobo": 60, "baz": 90},
|
||||
{"bobo": 10, "bar": 83},
|
||||
{"bobo": 20, "yam": 12},
|
||||
]
|
||||
});
|
||||
let exp = "foo[.bobo >= 50]";
|
||||
assert_eq!(
|
||||
evaluator.eval_in_context(exp, context).unwrap(),
|
||||
value!([{"bobo": 50, "fofo": 100}, {"bobo": 60, "baz": 90}])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@ pub enum Expression {
|
|||
truthy: Box<Expression>,
|
||||
falsy: Box<Expression>,
|
||||
},
|
||||
|
||||
Filter {
|
||||
ident: String,
|
||||
op: OpCode,
|
||||
right: Box<Expression>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
|
|
|
@ -132,7 +132,8 @@ Identifier: String = {
|
|||
}
|
||||
|
||||
Index: Box<Expression> = {
|
||||
"[" <Expression> "]"
|
||||
"[" "." <ident: Identifier> <op: Op20> <right: Expr80> "]" => Box::new(Expression::Filter {ident, op, right}),
|
||||
"[" <Expression> "]",
|
||||
}
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче