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:
stefan-gorules
2024-10-23 15:59:31 +02:00
committed by GitHub
co-authored by Ivan Miletic
parent 8a7e72bf53
commit 47812143a0
38 changed files with 1438 additions and 251 deletions
+87 -24
View File
@@ -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;
}
}
+8 -1
View File
@@ -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)]