Skip to content

Commit

Permalink
Merge pull request #4 from codetiger/main
Browse files Browse the repository at this point in the history
New array operators, configurable auto-traversal for Operators, and updated test code
  • Loading branch information
codetiger authored Dec 1, 2024
2 parents ce8a2ff + 2cd1967 commit cfd5155
Show file tree
Hide file tree
Showing 10 changed files with 710 additions and 91 deletions.
53 changes: 53 additions & 0 deletions examples/specific.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use datalogic_rs::JsonLogic;
use serde_json::json;

fn main() {
let logic = JsonLogic::new();

let rule = json!({
"filter": [
{
"var": "locales"
},
{
"!==": [
{
"var": "code"
},
{
"var": "../../locale"
}
]
}
]
});
let data = json!({
"locale": "pl",
"locales": [
{
"name": "Israel",
"code": "he",
"flag": "🇮🇱",
"iso": "he-IL",
"dir": "rtl"
},
{
"name": "українська",
"code": "ue",
"flag": "🇺🇦",
"iso": "uk-UA",
"dir": "ltr"
},
{
"name": "Polski",
"code": "pl",
"flag": "🇵🇱",
"iso": "pl-PL",
"dir": "ltr"
}
]
});

let result = logic.apply(&rule, &data).unwrap();
println!("Result: {}", result);
}
34 changes: 31 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use operators::{
arithmetic::*,
string::*,
array::*,
missing::*,
array_ops::*,
};
use serde_json::Value;
use std::collections::HashMap;
Expand Down Expand Up @@ -70,17 +72,43 @@ impl JsonLogic {
self.operators.insert("merge".into(), Arc::new(MergeOperator));

self.operators.insert("if".into(), Arc::new(IfOperator));

self.operators.insert("missing".into(), Arc::new(MissingOperator));
self.operators.insert("missing_some".into(), Arc::new(MissingSomeOperator));

self.operators.insert("filter".into(), Arc::new(FilterOperator));
self.operators.insert("map".into(), Arc::new(MapOperator));
self.operators.insert("reduce".into(), Arc::new(ReduceOperator));
self.operators.insert("all".into(), Arc::new(AllOperator));
self.operators.insert("none".into(), Arc::new(NoneOperator));
self.operators.insert("some".into(), Arc::new(SomeOperator));


}

pub fn apply(&self, logic: &Value, data: &Value) -> JsonLogicResult {
match logic {
Value::Object(map) if map.len() == 1 => {
let (op, args) = map.iter().next().unwrap();
self.operators
let operator = self.operators
.get(op)
.ok_or(Error::UnknownOperator(op.clone()))?
.apply(self, args, data)
.ok_or(Error::UnknownOperator(op.clone()))?;

// Handle automatic traversal
if operator.auto_traverse() {
match args {
Value::Array(values) => {
let evaluated = values
.iter()
.map(|v| self.apply(v, data))
.collect::<Result<Vec<_>, _>>()?;
operator.apply(self, &Value::Array(evaluated), data)
}
_ => operator.apply(self, args, data)
}
} else {
operator.apply(self, args, data)
}
}
Value::Array(values) => {
// Recursively evaluate each array element
Expand Down
283 changes: 283 additions & 0 deletions src/operators/array_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// src/operators/array_ops.rs
use crate::operators::operator::Operator;
use crate::{JsonLogic, JsonLogicResult};
use serde_json::{json, Value};

pub struct FilterOperator;

impl FilterOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 2 {
return Some((&values[0], &values[1]));
}
}
None
}

fn get_array(logic: &JsonLogic, source: &Value, data: &Value) -> JsonLogicResult {
match logic.apply(source, data)? {
Value::Array(arr) => Ok(Value::Array(arr)),
_ => Ok(Value::Array(vec![]))
}
}

fn test_condition(logic: &JsonLogic, condition: &Value, item: &Value) -> JsonLogicResult {
let result = logic.apply(condition, item)?;
Ok(Value::Bool(crate::operators::logic::is_truthy(&result)))
}
}

impl Operator for FilterOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, condition) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Array(vec![]))
};

let array = Self::get_array(logic, source, data)?;

if let Value::Array(items) = array {
let result = items
.into_iter()
.filter(|item| {
Self::test_condition(logic, condition, item)
.map(|v| crate::operators::logic::is_truthy(&v))
.unwrap_or(false)
})
.collect::<Vec<_>>();

Ok(Value::Array(result))
} else {
Ok(Value::Array(vec![]))
}
}
}

pub struct MapOperator;

impl MapOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 2 {
return Some((&values[0], &values[1]));
}
}
None
}
}

impl Operator for MapOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, mapper) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Array(vec![])),
};

let array = match logic.apply(source, data)? {
Value::Array(arr) => arr,
_ => return Ok(Value::Array(vec![])),
};

let result = array
.into_iter()
.map(|item| logic.apply(mapper, &item))
.collect::<Result<Vec<_>, _>>()?;

Ok(Value::Array(result))
}
}

pub struct ReduceOperator;

impl ReduceOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 3 {
return Some((&values[0], &values[1], &values[2]));
}
}
None
}
}

impl Operator for ReduceOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, reducer, initial) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Null),
};

// Evaluate initial value in data context
let initial_value = logic.apply(initial, data)?;

// Get array from source
let array = match logic.apply(source, data)? {
Value::Array(arr) => arr,
_ => return Ok(initial_value),
};

// Handle empty array case
if array.is_empty() {
return Ok(initial_value);
}

// Fold with proper context for accumulator and current
array.into_iter().fold(Ok(initial_value), |acc, current| {
let accumulator = acc?;
let context = json!({
"current": current,
"accumulator": accumulator
});
logic.apply(reducer, &context)
})
}
}

pub struct AllOperator;

impl AllOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 2 {
return Some((&values[0], &values[1]));
}
}
None
}
}

impl Operator for AllOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, condition) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Bool(false)),
};

let array = match logic.apply(source, data)? {
Value::Array(arr) => arr,
_ => return Ok(Value::Bool(false)),
};

// Empty array returns false
if array.is_empty() {
return Ok(Value::Bool(false));
}

let result = array
.iter()
.all(|item| {
logic.apply(condition, item)
.map(|v| crate::operators::logic::is_truthy(&v))
.unwrap_or(false)
});

Ok(Value::Bool(result))
}
}

pub struct NoneOperator;

impl NoneOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 2 {
return Some((&values[0], &values[1]));
}
}
None
}
}

impl Operator for NoneOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, condition) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Bool(true)),
};

let array = match logic.apply(source, data)? {
Value::Array(arr) => arr,
_ => return Ok(Value::Bool(true)),
};

if array.is_empty() {
return Ok(Value::Bool(true));
}

let result = !array
.iter()
.any(|item| {
logic.apply(condition, item)
.map(|v| crate::operators::logic::is_truthy(&v))
.unwrap_or(false)
});

Ok(Value::Bool(result))
}
}

pub struct SomeOperator;

impl SomeOperator {
fn validate_args(args: &Value) -> Option<(&Value, &Value)> {
if let Value::Array(values) = args {
if values.len() == 2 {
return Some((&values[0], &values[1]));
}
}
None
}
}

impl Operator for SomeOperator {
fn auto_traverse(&self) -> bool {
false
}

fn apply(&self, logic: &JsonLogic, args: &Value, data: &Value) -> JsonLogicResult {
let (source, condition) = match Self::validate_args(args) {
Some(args) => args,
None => return Ok(Value::Bool(false)),
};

let array = match logic.apply(source, data)? {
Value::Array(arr) => arr,
_ => return Ok(Value::Bool(false)),
};

if array.is_empty() {
return Ok(Value::Bool(false));
}

let result = array
.iter()
.any(|item| {
logic.apply(condition, item)
.map(|v| crate::operators::logic::is_truthy(&v))
.unwrap_or(false)
});

Ok(Value::Bool(result))
}
}
Loading

0 comments on commit cfd5155

Please sign in to comment.