mirror of
https://github.com/gorules/zen.git
synced 2026-10-06 16:02:19 +00:00
@@ -161,6 +161,37 @@ impl Variable {
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn dot_head_detach(&self, key: &str) -> (Variable, Option<Variable>) {
|
||||
let mut parts = Vec::from_iter(key.split('.'));
|
||||
parts.pop();
|
||||
|
||||
let cloned_self = self.depth_clone(1);
|
||||
let head = parts
|
||||
.iter()
|
||||
.try_fold(cloned_self.shallow_clone(), |var, part| match var {
|
||||
Variable::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(*part)) {
|
||||
Entry::Occupied(mut occ) => {
|
||||
let var = occ.get();
|
||||
let new_obj = match var {
|
||||
Variable::Object(_) => var.depth_clone(1),
|
||||
_ => Variable::empty_object(),
|
||||
};
|
||||
|
||||
occ.insert(new_obj.shallow_clone());
|
||||
new_obj
|
||||
}
|
||||
Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
(cloned_self, head)
|
||||
}
|
||||
|
||||
pub fn dot_remove(&self, key: &str) -> Option<Variable> {
|
||||
let last_part = key.split('.').last()?;
|
||||
let head = self.dot_head(key)?;
|
||||
@@ -183,6 +214,19 @@ impl Variable {
|
||||
object.insert(Rc::from(last_part), variable)
|
||||
}
|
||||
|
||||
pub fn dot_insert_detached(&self, key: &str, variable: Variable) -> Option<Variable> {
|
||||
let last_part = key.split('.').last()?;
|
||||
let (new_var, head_opt) = self.dot_head_detach(key);
|
||||
let head = head_opt?;
|
||||
let Variable::Object(object_ref) = head else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.insert(Rc::from(last_part), variable);
|
||||
Some(new_var)
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, patch: &Variable) -> Variable {
|
||||
let _ = merge_variables(self, patch, true, MergeStrategy::InPlace);
|
||||
|
||||
@@ -403,3 +447,35 @@ impl PartialEq for Variable {
|
||||
}
|
||||
|
||||
impl Eq for Variable {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Variable;
|
||||
use rust_decimal_macros::dec;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn insert_detached() {
|
||||
let some_data: Variable = json!({ "customer": { "firstName": "John" }}).into();
|
||||
|
||||
let a_a = some_data
|
||||
.dot_insert_detached("a.a", Variable::Number(dec!(1)))
|
||||
.unwrap();
|
||||
let a_b = a_a
|
||||
.dot_insert_detached("a.b", Variable::Number(dec!(2)))
|
||||
.unwrap();
|
||||
let a_c = a_b
|
||||
.dot_insert_detached("a.c", Variable::Number(dec!(3)))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(a_a.dot("a"), Some(Variable::from(json!({ "a": 1 }))));
|
||||
assert_eq!(
|
||||
a_b.dot("a"),
|
||||
Some(Variable::from(json!({ "a": 1, "b": 2 })))
|
||||
);
|
||||
assert_eq!(
|
||||
a_c.dot("a"),
|
||||
Some(Variable::from(json!({ "a": 1, "b": 2, "c": 3 })))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::variable::types::VariableType;
|
||||
use serde_json::Value;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -23,11 +24,11 @@ impl<'a> From<Cow<'a, Value>> for VariableType {
|
||||
panic!("unexpected type of value, expected object");
|
||||
};
|
||||
|
||||
VariableType::Object(
|
||||
VariableType::Object(Rc::new(RefCell::new(
|
||||
obj.into_iter()
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), Rc::new(v.into())))
|
||||
.map(|(k, v)| (Rc::from(k.as_str()), v.into()))
|
||||
.collect(),
|
||||
)
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod conv;
|
||||
mod util;
|
||||
|
||||
use crate::variable::RcCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Write};
|
||||
@@ -17,7 +18,7 @@ pub enum VariableType {
|
||||
Date,
|
||||
Interval,
|
||||
Array(Rc<VariableType>),
|
||||
Object(HashMap<Rc<str>, Rc<VariableType>>),
|
||||
Object(RcCell<HashMap<Rc<str>, VariableType>>),
|
||||
|
||||
Const(Rc<str>),
|
||||
Enum(Option<Rc<str>>, Vec<Rc<str>>),
|
||||
@@ -97,6 +98,7 @@ impl Hash for VariableType {
|
||||
VariableType::Object(obj) => {
|
||||
10.hash(state);
|
||||
|
||||
let obj = obj.borrow();
|
||||
let mut pairs: Vec<_> = obj.iter().collect();
|
||||
pairs.sort_by_key(|i| i.0);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::variable::types::VariableType;
|
||||
use rust_decimal::prelude::Zero;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
@@ -19,12 +21,13 @@ impl VariableType {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Rc<VariableType> {
|
||||
pub fn get(&self, key: &str) -> VariableType {
|
||||
match self {
|
||||
VariableType::Object(obj) => {
|
||||
obj.get(key).cloned().unwrap_or(Rc::new(VariableType::Any))
|
||||
let obj = obj.borrow();
|
||||
obj.get(key).cloned().unwrap_or(VariableType::Any)
|
||||
}
|
||||
_ => Rc::from(VariableType::Null),
|
||||
_ => VariableType::Null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +43,13 @@ impl VariableType {
|
||||
(_, VariableType::Date) if self.widen().is_string() => true,
|
||||
(VariableType::Interval, VariableType::Interval) => true,
|
||||
(VariableType::Array(a1), VariableType::Array(a2)) => a1.satisfies(a2),
|
||||
(VariableType::Object(o1), VariableType::Object(o2)) => o1
|
||||
.iter()
|
||||
.all(|(k, v)| o2.get(k).is_some_and(|tv| v.satisfies(tv))),
|
||||
(VariableType::Object(o1), VariableType::Object(o2)) => {
|
||||
let o1 = o1.borrow();
|
||||
let o2 = o2.borrow();
|
||||
|
||||
o1.iter()
|
||||
.all(|(k, v)| o2.get(k).is_some_and(|tv| v.satisfies(tv)))
|
||||
}
|
||||
|
||||
(VariableType::Const(c1), VariableType::Const(c2)) => c1 == c2,
|
||||
(VariableType::Const(c), VariableType::Enum(_, e)) => e.iter().any(|e| e == c),
|
||||
@@ -120,6 +127,9 @@ impl VariableType {
|
||||
}
|
||||
|
||||
(VariableType::Object(o1), VariableType::Object(o2)) => {
|
||||
let o1 = o1.borrow();
|
||||
let o2 = o2.borrow();
|
||||
|
||||
let mut merged = HashMap::with_capacity(o1.len().max(o2.len()));
|
||||
for (k, v) in o1.iter() {
|
||||
merged.insert(k.clone(), v.clone());
|
||||
@@ -129,8 +139,8 @@ impl VariableType {
|
||||
match merged.entry(k.clone()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let current = entry.get();
|
||||
let merged_value = current.as_ref().merge(v.as_ref());
|
||||
entry.insert(Rc::new(merged_value));
|
||||
let merged_value = current.merge(v);
|
||||
entry.insert(merged_value);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(v.clone());
|
||||
@@ -138,7 +148,7 @@ impl VariableType {
|
||||
}
|
||||
}
|
||||
|
||||
VariableType::Object(merged)
|
||||
VariableType::Object(Rc::new(RefCell::new(merged)))
|
||||
}
|
||||
|
||||
(VariableType::Const(c), VariableType::Enum(_, values)) => {
|
||||
@@ -189,6 +199,126 @@ impl VariableType {
|
||||
(_, _) => VariableType::Any,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shallow_clone(&self) -> Self {
|
||||
match self {
|
||||
VariableType::Any => VariableType::Any,
|
||||
VariableType::Null => VariableType::Null,
|
||||
VariableType::Bool => VariableType::Bool,
|
||||
VariableType::String => VariableType::String,
|
||||
VariableType::Number => VariableType::Number,
|
||||
VariableType::Date => VariableType::Date,
|
||||
VariableType::Interval => VariableType::Interval,
|
||||
VariableType::Array(arr) => VariableType::Array(arr.clone()),
|
||||
VariableType::Object(obj) => VariableType::Object(obj.clone()),
|
||||
VariableType::Const(c) => VariableType::Const(c.clone()),
|
||||
VariableType::Enum(name, options) => VariableType::Enum(name.clone(), options.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dot_head(&self, key: &str) -> Option<Self> {
|
||||
let mut parts = Vec::from_iter(key.split('.'));
|
||||
parts.pop();
|
||||
|
||||
parts
|
||||
.iter()
|
||||
.try_fold(self.shallow_clone(), |var, part| match var {
|
||||
VariableType::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(*part)) {
|
||||
Entry::Occupied(occ) => occ.get().shallow_clone(),
|
||||
Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dot_head_detach(&self, key: &str) -> (Self, Option<Self>) {
|
||||
let mut parts = Vec::from_iter(key.split('.'));
|
||||
parts.pop();
|
||||
|
||||
let cloned_self = self.depth_clone(1);
|
||||
let head = parts
|
||||
.iter()
|
||||
.try_fold(cloned_self.shallow_clone(), |var, part| match var {
|
||||
VariableType::Object(obj) => {
|
||||
let mut obj_ref = obj.borrow_mut();
|
||||
Some(match obj_ref.entry(Rc::from(*part)) {
|
||||
Entry::Occupied(mut occ) => {
|
||||
let var = occ.get();
|
||||
let new_obj = match var {
|
||||
VariableType::Object(_) => var.depth_clone(1),
|
||||
_ => VariableType::empty_object(),
|
||||
};
|
||||
|
||||
occ.insert(new_obj.shallow_clone());
|
||||
new_obj
|
||||
}
|
||||
Entry::Vacant(vac) => vac.insert(Self::empty_object()).shallow_clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
(cloned_self, head)
|
||||
}
|
||||
|
||||
pub fn depth_clone(&self, depth: usize) -> Self {
|
||||
match depth.is_zero() {
|
||||
true => self.shallow_clone(),
|
||||
false => match self {
|
||||
VariableType::Object(o) => {
|
||||
let obj = o.borrow();
|
||||
VariableType::Object(Rc::new(RefCell::new(
|
||||
obj.iter()
|
||||
.map(|(k, v)| (k.clone(), v.depth_clone(depth - 1)))
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
_ => self.shallow_clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_object() -> Self {
|
||||
VariableType::Object(Rc::new(RefCell::new(HashMap::new())))
|
||||
}
|
||||
|
||||
pub fn dot_insert_detached(&self, key: &str, variable: Self) -> Option<Self> {
|
||||
let last_part = key.split('.').last()?;
|
||||
let (new_var, head_opt) = self.dot_head_detach(key);
|
||||
let head = head_opt?;
|
||||
let VariableType::Object(object_ref) = head else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.insert(Rc::from(last_part), variable);
|
||||
Some(new_var)
|
||||
}
|
||||
|
||||
pub fn dot_insert(&self, key: &str, variable: Self) -> Option<Self> {
|
||||
let last_part = key.split('.').last()?;
|
||||
let head = self.dot_head(key)?;
|
||||
let Self::Object(object_ref) = head else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut object = object_ref.borrow_mut();
|
||||
object.insert(Rc::from(last_part), variable)
|
||||
}
|
||||
|
||||
pub fn dot(&self, key: &str) -> Option<Self> {
|
||||
key.split('.')
|
||||
.try_fold(self.shallow_clone(), |var, part| match var {
|
||||
Self::Object(obj) => {
|
||||
let reference = obj.borrow();
|
||||
reference.get(part).map(|v| v.shallow_clone())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user