use crate::opcode::OpCode;
use regex::Regex;
use serde_json::Value;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub enum PathSegment {
Field(Box<str>),
Index(usize),
FieldOrIndex(Box<str>, usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReduceHint {
None,
Current,
Accumulator,
CurrentPath,
AccumulatorPath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetadataHint {
None,
Index,
Key,
}
#[derive(Debug, Clone)]
pub struct CustomOperatorData {
pub name: String,
pub args: Box<[CompiledNode]>,
}
#[derive(Debug, Clone)]
pub struct StructuredObjectData {
pub fields: Box<[(String, CompiledNode)]>,
}
#[derive(Debug, Clone)]
pub struct CompiledExistsData {
pub scope_level: u32,
pub segments: Box<[PathSegment]>,
}
#[derive(Debug, Clone)]
pub struct CompiledSplitRegexData {
pub args: Box<[CompiledNode]>,
pub regex: Arc<Regex>,
pub capture_names: Box<[Box<str>]>,
}
#[derive(Debug, Clone)]
pub enum CompiledNode {
Value { value: Value },
Array { nodes: Box<[CompiledNode]> },
BuiltinOperator {
opcode: OpCode,
args: Box<[CompiledNode]>,
},
CustomOperator(Box<CustomOperatorData>),
StructuredObject(Box<StructuredObjectData>),
CompiledVar {
scope_level: u32,
segments: Box<[PathSegment]>,
reduce_hint: ReduceHint,
metadata_hint: MetadataHint,
default_value: Option<Box<CompiledNode>>,
},
CompiledExists(Box<CompiledExistsData>),
CompiledSplitRegex(Box<CompiledSplitRegexData>),
CompiledThrow(Box<Value>),
}
#[derive(Debug, Clone)]
pub struct CompiledLogic {
pub root: CompiledNode,
}
impl CompiledLogic {
pub fn new(root: CompiledNode) -> Self {
Self { root }
}
pub fn is_static(&self) -> bool {
node_is_static(&self.root)
}
}
pub(crate) fn node_is_static(node: &CompiledNode) -> bool {
match node {
CompiledNode::Value { .. } => true,
CompiledNode::Array { nodes, .. } => nodes.iter().all(node_is_static),
CompiledNode::BuiltinOperator { opcode, args, .. } => opcode_is_static(opcode, args),
CompiledNode::CustomOperator(_) => false,
CompiledNode::CompiledVar { .. } | CompiledNode::CompiledExists(_) => false,
CompiledNode::CompiledSplitRegex(data) => data.args.iter().all(node_is_static),
CompiledNode::CompiledThrow(_) => false,
CompiledNode::StructuredObject(data) => {
data.fields.iter().all(|(_, node)| node_is_static(node))
}
}
}
pub(crate) fn opcode_is_static(opcode: &OpCode, args: &[CompiledNode]) -> bool {
use OpCode::*;
let args_static = || args.iter().all(node_is_static);
match opcode {
Var | Val | Missing | MissingSome | Exists => false,
Map | Filter | Reduce | All | Some | None => false,
Try | Throw => false,
Now => false,
Preserve => false,
Merge | Min | Max => false,
Type | StartsWith | EndsWith | Upper | Lower | Trim | Split | Datetime | Timestamp
| ParseDate | FormatDate | DateDiff | Abs | Ceil | Floor | Add | Subtract | Multiply
| Divide | Modulo | Equals | StrictEquals | NotEquals | StrictNotEquals | GreaterThan
| GreaterThanEqual | LessThan | LessThanEqual | Not | DoubleNot | And | Or | Ternary
| If | Cat | Substr | In | Length | Sort | Slice | Coalesce | Switch => args_static(),
}
}
pub(crate) fn segments_to_dot_path(segments: &[PathSegment]) -> String {
segments
.iter()
.map(|seg| match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => s.to_string(),
PathSegment::Index(i) => i.to_string(),
})
.collect::<Vec<_>>()
.join(".")
}
pub(crate) fn segment_to_value(seg: &PathSegment) -> Value {
match seg {
PathSegment::Field(s) | PathSegment::FieldOrIndex(s, _) => Value::String(s.to_string()),
PathSegment::Index(i) => Value::Number((*i as u64).into()),
}
}
pub(crate) fn node_to_value(node: &CompiledNode) -> Value {
match node {
CompiledNode::Value { value, .. } => value.clone(),
CompiledNode::Array { nodes, .. } => {
Value::Array(nodes.iter().map(node_to_value).collect())
}
CompiledNode::BuiltinOperator { opcode, args, .. } => {
let mut obj = serde_json::Map::new();
let args_value = if args.len() == 1 {
node_to_value(&args[0])
} else {
Value::Array(args.iter().map(node_to_value).collect())
};
obj.insert(opcode.as_str().into(), args_value);
Value::Object(obj)
}
CompiledNode::CustomOperator(data) => {
let mut obj = serde_json::Map::new();
let args_value = if data.args.len() == 1 {
node_to_value(&data.args[0])
} else {
Value::Array(data.args.iter().map(node_to_value).collect())
};
obj.insert(data.name.clone(), args_value);
Value::Object(obj)
}
CompiledNode::StructuredObject(data) => {
let mut obj = serde_json::Map::new();
for (key, node) in data.fields.iter() {
obj.insert(key.clone(), node_to_value(node));
}
Value::Object(obj)
}
CompiledNode::CompiledVar {
scope_level,
segments,
default_value,
..
} => {
let mut obj = serde_json::Map::new();
if *scope_level == 0 {
let path = segments_to_dot_path(segments);
match default_value {
Some(def) => {
obj.insert(
"var".into(),
Value::Array(vec![Value::String(path), node_to_value(def)]),
);
}
None => {
obj.insert("var".into(), Value::String(path));
}
}
} else {
let mut arr: Vec<Value> = vec![Value::Array(vec![Value::Number(
(*scope_level as u64).into(),
)])];
for seg in segments.iter() {
arr.push(segment_to_value(seg));
}
obj.insert("val".into(), Value::Array(arr));
}
Value::Object(obj)
}
CompiledNode::CompiledExists(data) => {
let mut obj = serde_json::Map::new();
if data.segments.len() == 1 {
obj.insert("exists".into(), segment_to_value(&data.segments[0]));
} else {
let arr: Vec<Value> = data.segments.iter().map(segment_to_value).collect();
obj.insert("exists".into(), Value::Array(arr));
}
Value::Object(obj)
}
CompiledNode::CompiledSplitRegex(data) => {
let mut obj = serde_json::Map::new();
let mut arr = vec![node_to_value(&data.args[0])];
arr.push(Value::String(data.regex.as_str().to_string()));
obj.insert("split".into(), Value::Array(arr));
Value::Object(obj)
}
CompiledNode::CompiledThrow(error_obj) => {
let mut obj = serde_json::Map::new();
if let Value::Object(err_map) = error_obj.as_ref() {
if let Some(Value::String(s)) = err_map.get("type") {
obj.insert("throw".into(), Value::String(s.clone()));
} else {
obj.insert("throw".into(), error_obj.as_ref().clone());
}
} else {
obj.insert("throw".into(), error_obj.as_ref().clone());
}
Value::Object(obj)
}
}
}