mirror of
https://github.com/gorules/zen.git
synced 2026-10-07 08:02:21 +00:00
feat: passthrough nodes (#261)
* feat: passthrough nodes * update bindings * update model * update exclusion * fix merge strategy * fix * fix merge * add tests * fix walk * fix merge * fix * fix: add tests * fix types --------- Co-authored-by: Ivan Miletic <vnmiletic@gmail.com>
This commit is contained in:
co-authored by
Ivan Miletic
parent
8a7e72bf53
commit
47812143a0
@@ -166,11 +166,18 @@ impl Variable {
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, patch: &Variable) -> Variable {
|
||||
merge_variables(self, patch, true);
|
||||
let _ = merge_variables(self, patch, true, MergeStrategy::InPlace);
|
||||
|
||||
self.shallow_clone()
|
||||
}
|
||||
|
||||
pub fn merge_clone(&mut self, patch: &Variable) -> Variable {
|
||||
let mut new_self = self.shallow_clone();
|
||||
|
||||
let _ = merge_variables(&mut new_self, patch, true, MergeStrategy::CloneOnWrite);
|
||||
new_self
|
||||
}
|
||||
|
||||
pub fn shallow_clone(&self) -> Self {
|
||||
match self {
|
||||
Variable::Null => Variable::Null,
|
||||
@@ -228,40 +235,96 @@ impl Clone for Variable {
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_variables(doc: &mut Variable, patch: &Variable, top_level: bool) {
|
||||
if !patch.is_object() && !patch.is_array() && top_level {
|
||||
return;
|
||||
#[derive(Copy, Clone)]
|
||||
enum MergeStrategy {
|
||||
InPlace,
|
||||
CloneOnWrite,
|
||||
}
|
||||
|
||||
fn merge_variables(
|
||||
doc: &mut Variable,
|
||||
patch: &Variable,
|
||||
top_level: bool,
|
||||
strategy: MergeStrategy,
|
||||
) -> bool {
|
||||
if patch.is_array() && top_level {
|
||||
*doc = patch.shallow_clone();
|
||||
return true;
|
||||
}
|
||||
|
||||
if !patch.is_object() && top_level {
|
||||
return false;
|
||||
}
|
||||
|
||||
if doc.is_object() && patch.is_object() {
|
||||
let map_ref = doc.as_object().unwrap();
|
||||
let doc_ref = doc.as_object().unwrap();
|
||||
let patch_ref = patch.as_object().unwrap();
|
||||
if Rc::ptr_eq(&map_ref, &patch_ref) {
|
||||
return;
|
||||
if Rc::ptr_eq(&doc_ref, &patch_ref) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut map = map_ref.borrow_mut();
|
||||
let patch = patch_ref.borrow();
|
||||
for (key, value) in patch.deref() {
|
||||
if value == &Variable::Null {
|
||||
map.remove(key.as_str());
|
||||
} else {
|
||||
let entry = map.entry(key.to_string()).or_insert(Variable::Null);
|
||||
merge_variables(entry, value, false)
|
||||
match strategy {
|
||||
MergeStrategy::InPlace => {
|
||||
let mut map = doc_ref.borrow_mut();
|
||||
for (key, value) in patch.deref() {
|
||||
if value == &Variable::Null {
|
||||
map.remove(key.as_str());
|
||||
} else {
|
||||
let entry = map.entry(key.to_string()).or_insert(Variable::Null);
|
||||
merge_variables(entry, value, false, strategy);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
MergeStrategy::CloneOnWrite => {
|
||||
let mut changed = false;
|
||||
let mut new_map = None;
|
||||
|
||||
for (key, value) in patch.deref() {
|
||||
// Get or create the new map if we haven't yet
|
||||
let map = if let Some(ref mut m) = new_map {
|
||||
m
|
||||
} else {
|
||||
let m = doc_ref.borrow().clone();
|
||||
new_map = Some(m);
|
||||
new_map.as_mut().unwrap()
|
||||
};
|
||||
|
||||
if value == &Variable::Null {
|
||||
// Remove null values
|
||||
if map.remove(key.as_str()).is_some() {
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
// Handle nested merging
|
||||
let entry = map.entry(key.to_string()).or_insert(Variable::Null);
|
||||
if merge_variables(entry, value, false, strategy) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only update doc if changes were made
|
||||
if changed {
|
||||
if let Some(new_map) = new_map {
|
||||
*doc = Variable::Object(Rc::new(RefCell::new(new_map)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if doc.is_array() && patch.is_array() {
|
||||
let arr_ref = doc.as_array().unwrap();
|
||||
let patch_ref = patch.as_array().unwrap();
|
||||
if Rc::ptr_eq(&arr_ref, &patch_ref) {
|
||||
return;
|
||||
} else {
|
||||
let new_value = patch.shallow_clone();
|
||||
if *doc != new_value {
|
||||
*doc = new_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut arr = arr_ref.borrow_mut();
|
||||
let patch = patch_ref.borrow();
|
||||
arr.extend(patch.iter().map(|s| s.shallow_clone()));
|
||||
} else {
|
||||
*doc = patch.shallow_clone();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl VariableType {
|
||||
(VariableType::Bool, VariableType::Bool) => true,
|
||||
(VariableType::String, VariableType::String) => true,
|
||||
(VariableType::Number, VariableType::Number) => true,
|
||||
(VariableType::Array(a1), VariableType::Array(a2)) => a1 == a2,
|
||||
(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))),
|
||||
@@ -149,6 +149,13 @@ impl VariableType {
|
||||
(_, _) => VariableType::Any,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
match self {
|
||||
VariableType::Null => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user