use serde_json::Value;
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use crate::config::EvaluationConfig;
use crate::operators::variable;
use crate::trace::{ExpressionNode, TraceCollector, TracedResult};
use crate::{CompiledLogic, CompiledNode, ContextStack, Error, Evaluator, Operator, Result};
pub struct DataLogic {
custom_operators: HashMap<String, Box<dyn Operator>>,
preserve_structure: bool,
config: EvaluationConfig,
}
impl Default for DataLogic {
fn default() -> Self {
Self::new()
}
}
impl DataLogic {
pub fn new() -> Self {
Self {
custom_operators: HashMap::new(),
preserve_structure: false,
config: EvaluationConfig::default(),
}
}
pub fn with_preserve_structure() -> Self {
Self {
custom_operators: HashMap::new(),
preserve_structure: true,
config: EvaluationConfig::default(),
}
}
pub fn with_config(config: EvaluationConfig) -> Self {
Self {
custom_operators: HashMap::new(),
preserve_structure: false,
config,
}
}
pub fn with_config_and_structure(config: EvaluationConfig, preserve_structure: bool) -> Self {
Self {
custom_operators: HashMap::new(),
preserve_structure,
config,
}
}
pub fn config(&self) -> &EvaluationConfig {
&self.config
}
pub fn preserve_structure(&self) -> bool {
self.preserve_structure
}
pub fn add_operator(&mut self, name: String, operator: Box<dyn Operator>) {
self.custom_operators.insert(name, operator);
}
pub fn has_custom_operator(&self, name: &str) -> bool {
self.custom_operators.contains_key(name)
}
pub fn compile(&self, logic: &Value) -> Result<Arc<CompiledLogic>> {
let compiled = CompiledLogic::compile_with_static_eval(logic, self)?;
Ok(Arc::new(compiled))
}
pub fn evaluate(&self, compiled: &CompiledLogic, data: Arc<Value>) -> Result<Value> {
let mut context = ContextStack::new(data);
self.evaluate_node(&compiled.root, &mut context)
}
pub fn evaluate_owned(&self, compiled: &CompiledLogic, data: Value) -> Result<Value> {
self.evaluate(compiled, Arc::new(data))
}
pub fn evaluate_json(&self, logic: &str, data: &str) -> Result<Value> {
let logic_value: Value = serde_json::from_str(logic)?;
let data_value: Value = serde_json::from_str(data)?;
let data_arc = Arc::new(data_value);
let compiled = self.compile(&logic_value)?;
self.evaluate(&compiled, data_arc)
}
#[inline]
pub fn evaluate_node(&self, node: &CompiledNode, context: &mut ContextStack) -> Result<Value> {
match node {
CompiledNode::Value { value, .. } => Ok(value.clone()),
CompiledNode::Array { nodes, .. } => {
let mut results = Vec::with_capacity(nodes.len());
for node in nodes.iter() {
results.push(self.evaluate_node(node, context)?);
}
Ok(Value::Array(results))
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
opcode.evaluate_direct(args, context, self)
}
CompiledNode::CustomOperator(data) => {
let operator = self
.custom_operators
.get(&data.name)
.ok_or_else(|| Error::InvalidOperator(data.name.clone()))?;
let arg_values: Vec<Value> = data.args.iter().map(node_to_value).collect();
let evaluator = SimpleEvaluator::new(self);
operator.evaluate(&arg_values, context, &evaluator)
}
CompiledNode::StructuredObject(data) => {
let mut result = serde_json::Map::new();
for (key, node) in data.fields.iter() {
let value = self.evaluate_node(node, context)?;
result.insert(key.clone(), value);
}
Ok(Value::Object(result))
}
CompiledNode::CompiledVar {
scope_level,
segments,
reduce_hint,
metadata_hint,
default_value,
} => variable::evaluate_compiled_var(
*scope_level,
segments,
*reduce_hint,
*metadata_hint,
default_value.as_deref(),
context,
self,
),
CompiledNode::CompiledExists(data) => {
variable::evaluate_compiled_exists(data.scope_level, &data.segments, context)
}
CompiledNode::CompiledSplitRegex(data) => {
use crate::operators::string;
string::evaluate_split_with_regex(
&data.args,
context,
self,
&data.regex,
&data.capture_names,
)
}
CompiledNode::CompiledThrow(error_obj) => {
Err(Error::Thrown(error_obj.as_ref().clone()))
}
}
}
#[inline]
pub fn evaluate_node_cow<'a>(
&self,
node: &'a CompiledNode,
context: &mut ContextStack,
) -> Result<Cow<'a, Value>> {
match node {
CompiledNode::Value { value, .. } => Ok(Cow::Borrowed(value)),
_ => self.evaluate_node(node, context).map(Cow::Owned),
}
}
pub fn evaluate_json_with_trace(&self, logic: &str, data: &str) -> Result<TracedResult> {
let logic_value: Value = serde_json::from_str(logic)?;
let data_value: Value = serde_json::from_str(data)?;
let data_arc = Arc::new(data_value);
let compiled = Arc::new(CompiledLogic::compile_for_trace(
&logic_value,
self.preserve_structure(),
)?);
let (expression_tree, node_id_map) = ExpressionNode::build_from_compiled(&compiled.root);
let mut context = ContextStack::new(data_arc);
let mut collector = TraceCollector::new();
let result =
self.evaluate_node_traced(&compiled.root, &mut context, &mut collector, &node_id_map);
match result {
Ok(value) => Ok(TracedResult {
result: value,
expression_tree,
steps: collector.into_steps(),
error: None,
}),
Err(e) => {
Ok(TracedResult {
result: Value::Null,
expression_tree,
steps: collector.into_steps(),
error: Some(e.to_string()),
})
}
}
}
pub fn evaluate_node_traced(
&self,
node: &CompiledNode,
context: &mut ContextStack,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
let node_ptr = node as *const CompiledNode as usize;
let node_id = node_id_map.get(&node_ptr).copied().unwrap_or(0);
let current_context = context.current().data().clone();
match node {
CompiledNode::Value { value, .. } => {
Ok(value.clone())
}
CompiledNode::Array { nodes, .. } => {
let mut results = Vec::with_capacity(nodes.len());
for node in nodes.iter() {
match self.evaluate_node_traced(node, context, collector, node_id_map) {
Ok(val) => results.push(val),
Err(err) => {
collector.record_error(node_id, current_context, err.to_string());
return Err(err);
}
}
}
let result = Value::Array(results);
collector.record_step(node_id, current_context, result.clone());
Ok(result)
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
match opcode.evaluate_traced(args, context, self, collector, node_id_map) {
Ok(result) => {
collector.record_step(node_id, current_context, result.clone());
Ok(result)
}
Err(err) => {
collector.record_error(node_id, current_context, err.to_string());
Err(err)
}
}
}
CompiledNode::CustomOperator(data) => {
let operator = self
.custom_operators
.get(&data.name)
.ok_or_else(|| Error::InvalidOperator(data.name.clone()))?;
let arg_values: Vec<Value> = data.args.iter().map(node_to_value).collect();
let evaluator = SimpleEvaluator::new(self);
match operator.evaluate(&arg_values, context, &evaluator) {
Ok(result) => {
collector.record_step(node_id, current_context, result.clone());
Ok(result)
}
Err(err) => {
collector.record_error(node_id, current_context, err.to_string());
Err(err)
}
}
}
CompiledNode::StructuredObject(data) => {
let mut result = serde_json::Map::new();
for (key, node) in data.fields.iter() {
match self.evaluate_node_traced(node, context, collector, node_id_map) {
Ok(value) => {
result.insert(key.clone(), value);
}
Err(err) => {
collector.record_error(node_id, current_context, err.to_string());
return Err(err);
}
}
}
let result = Value::Object(result);
collector.record_step(node_id, current_context, result.clone());
Ok(result)
}
CompiledNode::CompiledVar { .. }
| CompiledNode::CompiledExists(_)
| CompiledNode::CompiledSplitRegex(_)
| CompiledNode::CompiledThrow(_) => match self.evaluate_node(node, context) {
Ok(result) => {
collector.record_step(node_id, current_context, result.clone());
Ok(result)
}
Err(err) => {
collector.record_error(node_id, current_context, err.to_string());
Err(err)
}
},
}
}
}
use crate::node::node_to_value;
struct SimpleEvaluator<'e> {
engine: &'e DataLogic,
}
impl<'e> SimpleEvaluator<'e> {
fn new(engine: &'e DataLogic) -> Self {
Self { engine }
}
}
impl Evaluator for SimpleEvaluator<'_> {
fn evaluate(&self, logic: &Value, context: &mut ContextStack) -> Result<Value> {
match logic {
Value::Object(obj) if obj.len() == 1 => {
let compiled = CompiledLogic::compile_with_static_eval(logic, self.engine)?;
self.engine.evaluate_node(&compiled.root, context)
}
Value::Object(obj) if obj.len() > 1 && self.engine.preserve_structure => {
let compiled = CompiledLogic::compile_with_static_eval(logic, self.engine)?;
self.engine.evaluate_node(&compiled.root, context)
}
Value::Array(_) => {
let compiled = CompiledLogic::compile_with_static_eval(logic, self.engine)?;
self.engine.evaluate_node(&compiled.root, context)
}
_ => Ok(logic.clone()),
}
}
}