diff --git a/src/lambda/list.rs b/src/lambda/list.rs deleted file mode 100644 index 8390b9e367..0000000000 --- a/src/lambda/list.rs +++ /dev/null @@ -1,76 +0,0 @@ -use core::future::Future; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; -use futures_util::future::join_all; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum List { - Concat(Vec), -} - -impl Eval for List { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - match self { - List::Concat(list) => { - join_all(list.iter().map(|expr| expr.eval(ctx.clone(), conc))) - .await - .into_iter() - .try_fold(async_graphql::Value::List(vec![]), |acc, result| { - match (acc, result?) { - (ConstValue::List(mut lhs), ConstValue::List(rhs)) => { - lhs.extend(rhs); - Ok(ConstValue::List(lhs)) - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - } - }) - } - } - }) - } -} - -impl Eval for T -where - T: AsRef<[Expression]> + Send + Sync, - C: FromIterator, -{ - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - let future_iter = self - .as_ref() - .iter() - .map(|expr| expr.eval(ctx.clone(), conc)); - match *conc { - Concurrent::Parallel => join_all(future_iter) - .await - .into_iter() - .collect::>(), - Concurrent::Sequential => { - let mut results = Vec::with_capacity(self.as_ref().len()); - for future in future_iter { - results.push(future.await?); - } - Ok(results.into_iter().collect()) - } - } - }) - } -} diff --git a/src/lambda/logic.rs b/src/lambda/logic.rs deleted file mode 100644 index 6552f492e1..0000000000 --- a/src/lambda/logic.rs +++ /dev/null @@ -1,141 +0,0 @@ -use core::future::Future; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; - -use super::{Concurrent, Eval, EvaluationContext, Expression, ResolverContextLike}; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Logic { - If { - cond: Box, - then: Box, - els: Box, - }, - And(Vec), - Or(Vec), - Cond(Vec<(Box, Box)>), - DefaultTo(Box, Box), - IsEmpty(Box), - Not(Box), -} - -impl Eval for Logic { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Logic::Or(list) => { - let future_iter = list.iter().map(|expr| { - let ctx = ctx.clone(); - async move { expr.eval(ctx.clone(), conc).await } - }); - - conc.fold(future_iter, false, |acc, val| Ok(acc || is_truthy(&val?))) - .await - .map(ConstValue::from)? - } - Logic::Cond(list) => { - for (cond, expr) in list.iter() { - if is_truthy(&cond.eval(ctx.clone(), conc).await?) { - return expr.eval(ctx, conc).await; - } - } - ConstValue::Null - } - Logic::DefaultTo(value, default) => { - let result = value.eval(ctx.clone(), conc).await?; - if is_empty(&result) { - default.eval(ctx, conc).await? - } else { - result - } - } - Logic::IsEmpty(expr) => is_empty(&expr.eval(ctx, conc).await?).into(), - Logic::Not(expr) => (!is_truthy(&expr.eval(ctx, conc).await?)).into(), - - Logic::And(list) => { - let future_iter = list.iter().map(|expr| { - let ctx = ctx.clone(); - async move { expr.eval(ctx, conc).await } - }); - - conc.fold(future_iter, true, |acc, val| Ok(acc && is_truthy(&val?))) - .await - .map(ConstValue::from)? - } - Logic::If { cond, then, els } => { - let cond = cond.eval(ctx.clone(), conc).await?; - if is_truthy(&cond) { - then.eval(ctx, conc).await? - } else { - els.eval(ctx, conc).await? - } - } - }) - }) - } -} - -/// Check if a value is truthy -/// -/// Special cases: -/// 1. An empty string is considered falsy -/// 2. A collection of bytes is truthy, even if the value in those bytes is 0. -/// An empty collection is falsy. -pub fn is_truthy(value: &async_graphql::Value) -> bool { - use async_graphql::{Number, Value}; - use hyper::body::Bytes; - - match value { - &Value::Null => false, - &Value::Enum(_) => true, - &Value::List(_) => true, - &Value::Object(_) => true, - Value::String(s) => !s.is_empty(), - &Value::Boolean(b) => b, - Value::Number(n) => n != &Number::from(0), - Value::Binary(b) => b != &Bytes::default(), - } -} - -fn is_empty(value: &async_graphql::Value) -> bool { - match value { - ConstValue::Null => true, - ConstValue::Number(_) | ConstValue::Boolean(_) | ConstValue::Enum(_) => false, - ConstValue::Binary(bytes) => bytes.is_empty(), - ConstValue::List(list) => list.is_empty(), - ConstValue::Object(obj) => obj.is_empty(), - ConstValue::String(string) => string.is_empty(), - } -} - -#[cfg(test)] -mod tests { - use async_graphql::{Name, Number, Value}; - use hyper::body::Bytes; - use indexmap::IndexMap; - - use crate::lambda::is_truthy; - - #[test] - fn test_is_truthy() { - assert!(is_truthy(&Value::Enum(Name::new("EXAMPLE")))); - assert!(is_truthy(&Value::List(vec![]))); - assert!(is_truthy(&Value::Object(IndexMap::default()))); - assert!(is_truthy(&Value::String("Hello".to_string()))); - assert!(is_truthy(&Value::Boolean(true))); - assert!(is_truthy(&Value::Number(Number::from(1)))); - assert!(is_truthy(&Value::Binary(Bytes::from_static(&[0, 1, 2])))); - - assert!(!is_truthy(&Value::Null)); - assert!(!is_truthy(&Value::String("".to_string()))); - assert!(!is_truthy(&Value::Boolean(false))); - assert!(!is_truthy(&Value::Number(Number::from(0)))); - assert!(!is_truthy(&Value::Binary(Bytes::default()))); - } -} diff --git a/src/lambda/math.rs b/src/lambda/math.rs deleted file mode 100644 index 6f7f88319a..0000000000 --- a/src/lambda/math.rs +++ /dev/null @@ -1,167 +0,0 @@ -use core::future::Future; -use std::ops; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; -use crate::json::JsonLike; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Math { - Mod(Box, Box), - Add(Box, Box), - Dec(Box), - Divide(Box, Box), - Inc(Box), - Multiply(Box, Box), - Negate(Box), - Product(Vec), - Subtract(Box, Box), - Sum(Vec), -} - -impl Eval for Math { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Math::Mod(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_i64_operation(&lhs, &rhs, ops::Rem::rem) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Rem::rem)) - .ok_or(EvaluationError::ExprEvalError("mod".into()))? - } - Math::Add(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Add::add) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Add::add)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Add::add)) - .ok_or(EvaluationError::ExprEvalError("add".into()))? - } - Math::Dec(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (val - 1f64).into()) - .or_else(|| val.as_u64_ok().ok().map(|val| (val - 1u64).into())) - .or_else(|| val.as_i64_ok().ok().map(|val| (val - 1i64).into())) - .ok_or(EvaluationError::ExprEvalError("dec".into()))? - } - Math::Divide(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Div::div) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Div::div)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Div::div)) - .ok_or(EvaluationError::ExprEvalError("divide".into()))? - } - Math::Inc(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (val + 1f64).into()) - .or_else(|| val.as_u64_ok().ok().map(|val| (val + 1u64).into())) - .or_else(|| val.as_i64_ok().ok().map(|val| (val + 1i64).into())) - .ok_or(EvaluationError::ExprEvalError("dec".into()))? - } - Math::Multiply(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Mul::mul) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Mul::mul)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Mul::mul)) - .ok_or(EvaluationError::ExprEvalError("multiply".into()))? - } - Math::Negate(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (-val).into()) - .or_else(|| val.as_i64_ok().ok().map(|val| (-val).into())) - .ok_or(EvaluationError::ExprEvalError("neg".into()))? - } - Math::Product(exprs) => { - let results: Vec<_> = exprs.eval(ctx, conc).await?; - - results.into_iter().try_fold(1i64.into(), |lhs, rhs| { - try_f64_operation(&lhs, &rhs, ops::Mul::mul) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Mul::mul)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Mul::mul)) - .ok_or(EvaluationError::ExprEvalError("product".into())) - })? - } - Math::Subtract(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Sub::sub) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Sub::sub)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Sub::sub)) - .ok_or(EvaluationError::ExprEvalError("subtract".into()))? - } - Math::Sum(exprs) => { - let results: Vec<_> = exprs.eval(ctx, conc).await?; - - results.into_iter().try_fold(0i64.into(), |lhs, rhs| { - try_f64_operation(&lhs, &rhs, ops::Add::add) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Add::add)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Add::add)) - .ok_or(EvaluationError::ExprEvalError("sum".into())) - })? - } - }) - }) - } -} - -fn try_f64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(f64, f64) -> f64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_f64() - .and_then(|lhs| rhs.as_f64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} - -fn try_i64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(i64, i64) -> i64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_i64() - .and_then(|lhs| rhs.as_i64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} - -fn try_u64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(u64, u64) -> u64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_u64() - .and_then(|lhs| rhs.as_u64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} diff --git a/src/lambda/mod.rs b/src/lambda/mod.rs index 529dac1010..ee0cc3d8b1 100644 --- a/src/lambda/mod.rs +++ b/src/lambda/mod.rs @@ -5,11 +5,7 @@ mod evaluation_context; mod expression; mod graphql_operation_context; mod io; -mod list; -mod logic; -mod math; mod modify; -mod relation; mod resolver_context_like; pub use cache::*; @@ -19,8 +15,4 @@ pub use evaluation_context::EvaluationContext; pub use expression::*; pub use graphql_operation_context::GraphQLOperationContext; pub use io::*; -pub use list::*; -pub use logic::*; -pub use math::*; -pub use relation::*; pub use resolver_context_like::{EmptyResolverContext, ResolverContext, ResolverContextLike}; diff --git a/src/lambda/relation.rs b/src/lambda/relation.rs deleted file mode 100644 index 313bce77f2..0000000000 --- a/src/lambda/relation.rs +++ /dev/null @@ -1,336 +0,0 @@ -use core::future::Future; -use std::cmp::Ordering; -use std::collections::HashSet; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; -use futures_util::future::join_all; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; -use crate::helpers::value::HashableConstValue; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Relation { - Intersection(Vec), - Difference(Vec, Vec), - Equals(Box, Box), - Gt(Box, Box), - Gte(Box, Box), - Lt(Box, Box), - Lte(Box, Box), - Max(Vec), - Min(Vec), - PathEq(Box, Vec, Box), - PropEq(Box, String, Box), - SortPath(Box, Vec), - SymmetricDifference(Vec, Vec), - Union(Vec, Vec), -} - -impl Eval for Relation { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Relation::Intersection(exprs) => { - let results = - join_all(exprs.iter().map(|expr| expr.eval(ctx.clone(), conc))).await; - - let mut results_iter = results.into_iter(); - - let set: HashSet<_> = match results_iter.next() { - Some(first) => match first? { - ConstValue::List(list) => { - list.into_iter().map(HashableConstValue).collect() - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - }, - None => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - }; - - let final_set = - results_iter.try_fold(set, |mut acc, result| match result? { - ConstValue::List(list) => { - let set: HashSet<_> = - list.into_iter().map(HashableConstValue).collect(); - acc = acc.intersection(&set).cloned().collect(); - Ok::<_, anyhow::Error>(acc) - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - })?; - - final_set - .into_iter() - .map(|HashableConstValue(const_value)| const_value) - .collect() - } - Relation::Difference(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.difference(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - Relation::Equals(lhs, rhs) => { - (lhs.eval(ctx.clone(), conc).await? == rhs.eval(ctx, conc).await?).into() - } - Relation::Gt(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - (compare(&lhs, &rhs) == Some(Ordering::Greater)).into() - } - Relation::Gte(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - matches!( - compare(&lhs, &rhs), - Some(Ordering::Greater) | Some(Ordering::Equal) - ) - .into() - } - Relation::Lt(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - (compare(&lhs, &rhs) == Some(Ordering::Less)).into() - } - Relation::Lte(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - matches!( - compare(&lhs, &rhs), - Some(Ordering::Less) | Some(Ordering::Equal) - ) - .into() - } - Relation::Max(exprs) => { - let mut results: Vec<_> = exprs.eval(ctx, conc).await?; - - let last = results.pop().ok_or(EvaluationError::ExprEvalError( - "`max` cannot be called on empty list".into(), - ))?; - - results.into_iter().try_fold(last, |mut largest, current| { - let ord = compare(&largest, ¤t); - largest = match ord { - Some(Ordering::Greater | Ordering::Equal) => largest, - Some(Ordering::Less) => current, - _ => Err(anyhow::anyhow!( - "`max` cannot be calculated for types that cannot be compared" - ))?, - }; - Ok::<_, anyhow::Error>(largest) - })? - } - Relation::Min(exprs) => { - let mut results: Vec<_> = exprs.eval(ctx, conc).await?; - - let last = results.pop().ok_or(EvaluationError::ExprEvalError( - "`min` cannot be called on empty list".into(), - ))?; - - results.into_iter().try_fold(last, |mut largest, current| { - let ord = compare(&largest, ¤t); - largest = match ord { - Some(Ordering::Less | Ordering::Equal) => largest, - Some(Ordering::Greater) => current, - _ => Err(anyhow::anyhow!( - "`min` cannot be calculated for types that cannot be compared" - ))?, - }; - Ok::<_, anyhow::Error>(largest) - })? - } - Relation::PathEq(lhs, path, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let lhs = get_path_for_const_value_owned(path, lhs) - .ok_or(anyhow::anyhow!("Could not find path: {path:?}"))?; - - let rhs = rhs.eval(ctx, conc).await?; - let rhs = get_path_for_const_value_owned(path, rhs) - .ok_or(anyhow::anyhow!("Could not find path: {path:?}"))?; - - (lhs == rhs).into() - } - Relation::PropEq(lhs, prop, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let lhs = get_path_for_const_value_owned(&[prop], lhs) - .ok_or(anyhow::anyhow!("Could not find path: {prop:?}"))?; - - let rhs = rhs.eval(ctx, conc).await?; - let rhs = get_path_for_const_value_owned(&[prop], rhs) - .ok_or(anyhow::anyhow!("Could not find path: {prop:?}"))?; - - (lhs == rhs).into() - } - Relation::SortPath(expr, path) => { - let value = expr.eval(ctx, conc).await?; - let values = match value { - ConstValue::List(list) => list, - _ => Err(EvaluationError::ExprEvalError( - "`sortPath` can only be applied to expressions that return list".into(), - ))?, - }; - - let is_comparable = is_list_comparable(&values); - let mut values: Vec<_> = values.into_iter().enumerate().collect(); - - if !is_comparable { - Err(anyhow::anyhow!( - "sortPath requires a list of comparable types" - ))? - } - - let value_paths: Vec<_> = values - .iter() - .filter_map(|(_, val)| get_path_for_const_value_ref(path, val)) - .cloned() - .collect(); - - if values.len() != value_paths.len() { - Err(anyhow::anyhow!( - "path is not valid for all the element in the list: {value_paths:?}" - ))? - } - - values.sort_by(|(index1, _), (index2, _)| { - compare(&value_paths[*index1], &value_paths[*index2]).unwrap() - }); - - values - .into_iter() - .map(|(_, val)| val) - .collect::>() - .into() - } - Relation::SymmetricDifference(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.symmetric_difference(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - Relation::Union(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.union(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - }) - }) - } -} - -fn is_list_comparable(list: &[ConstValue]) -> bool { - list.iter() - .zip(list.iter().skip(1)) - .all(|(lhs, rhs)| is_pair_comparable(lhs, rhs)) -} - -fn compare(lhs: &ConstValue, rhs: &ConstValue) -> Option { - Some(match (lhs, rhs) { - (ConstValue::Null, ConstValue::Null) => Ordering::Equal, - (ConstValue::Boolean(lhs), ConstValue::Boolean(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::Enum(lhs), ConstValue::Enum(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_f64() - .partial_cmp(&rhs.as_f64()) - .or(lhs.as_i64().partial_cmp(&rhs.as_i64())) - .or(lhs.as_u64().partial_cmp(&rhs.as_u64()))?, - (ConstValue::Binary(lhs), ConstValue::Binary(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::String(lhs), ConstValue::String(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::List(lhs), ConstValue::List(rhs)) => lhs - .iter() - .zip(rhs.iter()) - .find_map(|(lhs, rhs)| compare(lhs, rhs).filter(|ord| ord != &Ordering::Equal)) - .unwrap_or(lhs.len().partial_cmp(&rhs.len())?), - _ => None?, - }) -} - -fn is_pair_comparable(lhs: &ConstValue, rhs: &ConstValue) -> bool { - matches!( - (lhs, rhs), - (ConstValue::Null, ConstValue::Null) - | (ConstValue::Boolean(_), ConstValue::Boolean(_)) - | (ConstValue::Enum(_), ConstValue::Enum(_)) - | (ConstValue::Number(_), ConstValue::Number(_)) - | (ConstValue::Binary(_), ConstValue::Binary(_)) - | (ConstValue::String(_), ConstValue::String(_)) - | (ConstValue::List(_), ConstValue::List(_)) - ) -} - -#[allow(clippy::too_many_arguments)] -async fn set_operation<'a, 'b, Ctx: ResolverContextLike<'a> + Sync + Send, F>( - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - lhs: &'a [Expression], - rhs: &'a [Expression], - operation: F, -) -> Result -where - F: Fn(HashSet, HashSet) -> Vec, -{ - let (lhs, rhs) = futures_util::join!( - conc.foreach( - lhs.iter().map(|e| e.eval(ctx.clone(), conc)), - HashableConstValue - ), - conc.foreach( - rhs.iter().map(|e| e.eval(ctx.clone(), conc)), - HashableConstValue - ) - ); - Ok(operation(HashSet::from_iter(lhs?), HashSet::from_iter(rhs?)).into()) -} - -fn get_path_for_const_value_ref<'a>( - path: &[impl AsRef], - mut const_value: &'a ConstValue, -) -> Option<&'a ConstValue> { - for path in path.iter() { - const_value = match const_value { - ConstValue::Object(ref obj) => obj.get(path.as_ref())?, - _ => None?, - } - } - - Some(const_value) -} - -fn get_path_for_const_value_owned( - path: &[impl AsRef], - mut const_value: ConstValue, -) -> Option { - for path in path.iter() { - const_value = match const_value { - ConstValue::Object(mut obj) => obj.swap_remove(path.as_ref())?, - _ => None?, - } - } - - Some(const_value) -}