use serde_json::Value as JsonValue;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum EvalValue {
Missing,
Value(JsonValue),
}
impl EvalValue {
pub fn is_missing(&self) -> bool {
matches!(self, EvalValue::Missing)
}
pub fn into_value(self) -> Option<JsonValue> {
match self {
EvalValue::Value(v) => Some(v),
EvalValue::Missing => None,
}
}
pub fn as_value(&self) -> Option<&JsonValue> {
match self {
EvalValue::Value(v) => Some(v),
EvalValue::Missing => None,
}
}
}
#[derive(Clone)]
pub struct EvalItem<'a> {
pub value: &'a JsonValue,
pub index: usize,
}
#[derive(Clone)]
pub struct V2EvalContext<'a> {
pipe_value: Option<EvalValue>,
let_bindings: HashMap<String, EvalValue>,
item: Option<EvalItem<'a>>,
acc: Option<&'a JsonValue>,
precomputed_op_args: Option<(String, Vec<EvalValue>)>,
}
impl<'a> V2EvalContext<'a> {
pub fn new() -> Self {
Self {
pipe_value: None,
let_bindings: HashMap::new(),
item: None,
acc: None,
precomputed_op_args: None,
}
}
pub fn with_pipe_value(mut self, value: EvalValue) -> Self {
self.pipe_value = Some(value);
self
}
pub fn with_let_binding(mut self, name: String, value: EvalValue) -> Self {
self.let_bindings.insert(name, value);
self
}
pub fn with_let_bindings(mut self, bindings: Vec<(String, EvalValue)>) -> Self {
for (name, value) in bindings {
self.let_bindings.insert(name, value);
}
self
}
pub fn with_item(mut self, item: EvalItem<'a>) -> Self {
self.item = Some(item);
self
}
pub fn with_acc(mut self, acc: &'a JsonValue) -> Self {
self.acc = Some(acc);
self
}
pub(crate) fn with_precomputed_op_args(
mut self,
base_path: impl Into<String>,
values: Vec<EvalValue>,
) -> Self {
self.precomputed_op_args = Some((base_path.into(), values));
self
}
pub fn get_pipe_value(&self) -> Option<&EvalValue> {
self.pipe_value.as_ref()
}
pub fn resolve_local(&self, name: &str) -> Option<&EvalValue> {
self.let_bindings.get(name)
}
pub fn get_item(&self) -> Option<&EvalItem<'a>> {
self.item.as_ref()
}
pub fn get_acc(&self) -> Option<&JsonValue> {
self.acc
}
pub fn has_item_scope(&self) -> bool {
self.item.is_some()
}
pub fn has_acc_scope(&self) -> bool {
self.acc.is_some()
}
pub(super) fn precomputed_arg_for_path(&self, path: &str) -> Option<EvalValue> {
let (base_path, values) = self.precomputed_op_args.as_ref()?;
let suffix = path.strip_prefix(base_path)?;
let index_text = suffix.strip_prefix(".args[")?.strip_suffix(']')?;
if index_text.contains(|ch: char| !ch.is_ascii_digit()) {
return None;
}
let index = index_text.parse::<usize>().ok()?;
values.get(index).cloned()
}
pub(super) fn let_bindings(&self) -> impl Iterator<Item = (&String, &EvalValue)> {
self.let_bindings.iter()
}
}
impl<'a> Default for V2EvalContext<'a> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
include!("context/tests.rs");
}