use crate::visitor::{VisitDecision, VisitorContext};
use crate::{Document, Item, MatrixList, Node, Value};
use std::collections::BTreeMap;
pub trait VisitorMut {
fn begin_document_mut(
&mut self,
_doc: &mut Document,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn end_document_mut(
&mut self,
_doc: &mut Document,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn visit_scalar_mut(
&mut self,
_key: &str,
_value: &mut Value,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn begin_object_mut(
&mut self,
_key: &str,
_obj: &mut BTreeMap<String, Item>,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn end_object_mut(
&mut self,
_key: &str,
_obj: &mut BTreeMap<String, Item>,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn begin_list_mut(
&mut self,
_key: &str,
_list: &mut MatrixList,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn end_list_mut(
&mut self,
_key: &str,
_list: &mut MatrixList,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn visit_node_mut(&mut self, _node: &mut Node, _ctx: &VisitorContext<'_>) -> VisitDecision {
VisitDecision::Continue
}
fn begin_node_children_mut(
&mut self,
_node: &mut Node,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn end_node_children_mut(
&mut self,
_node: &mut Node,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn visit_reference_mut(
&mut self,
_reference: &mut crate::Reference,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn visit_expression_mut(
&mut self,
_expr: &mut crate::lex::Expression,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
fn visit_tensor_mut(
&mut self,
_tensor: &mut crate::lex::Tensor,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
VisitDecision::Continue
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoOpVisitorMut;
impl VisitorMut for NoOpVisitorMut {}
#[test]
fn test_default_implementations() {
let mut visitor = NoOpVisitorMut;
let mut doc = Document::new((2, 0));
let temp_doc = Document::new((2, 0));
let ctx = VisitorContext::new(&temp_doc);
assert_eq!(
visitor.begin_document_mut(&mut doc, &ctx),
VisitDecision::Continue
);
assert_eq!(
visitor.end_document_mut(&mut doc, &ctx),
VisitDecision::Continue
);
}
struct ValueModifier;
impl VisitorMut for ValueModifier {
fn visit_scalar_mut(
&mut self,
_key: &str,
value: &mut Value,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
if let Value::Int(n) = value {
*n *= 2;
}
VisitDecision::Continue
}
}
#[test]
fn test_mutable_visitor_can_modify_values() {
let mut visitor = ValueModifier;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let mut value = Value::Int(21);
visitor.visit_scalar_mut("key", &mut value, &ctx);
assert_eq!(value, Value::Int(42));
}
struct NodeIdModifier;
impl VisitorMut for NodeIdModifier {
fn visit_node_mut(&mut self, node: &mut Node, _ctx: &VisitorContext<'_>) -> VisitDecision {
node.id = format!("modified_{}", node.id);
VisitDecision::Continue
}
}
#[test]
fn test_mutable_visitor_can_modify_nodes() {
let mut visitor = NodeIdModifier;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let mut node = Node::new("User", "alice", vec![]);
visitor.visit_node_mut(&mut node, &ctx);
assert_eq!(node.id, "modified_alice");
}
struct SkipEmptyLists;
impl VisitorMut for SkipEmptyLists {
fn begin_list_mut(
&mut self,
_key: &str,
list: &mut MatrixList,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
if list.rows.is_empty() {
VisitDecision::SkipChildren
} else {
VisitDecision::Continue
}
}
}
#[test]
fn test_mutable_visitor_can_skip_empty_lists() {
let mut visitor = SkipEmptyLists;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let mut empty_list = MatrixList::new("User", vec!["id".to_string()]);
assert_eq!(
visitor.begin_list_mut("users", &mut empty_list, &ctx),
VisitDecision::SkipChildren
);
let mut non_empty_list = MatrixList::new("User", vec!["id".to_string()]);
non_empty_list.add_row(Node::new("User", "1", vec![]));
assert_eq!(
visitor.begin_list_mut("users", &mut non_empty_list, &ctx),
VisitDecision::Continue
);
}
struct ObjectCleaner;
impl VisitorMut for ObjectCleaner {
fn end_object_mut(
&mut self,
_key: &str,
obj: &mut BTreeMap<String, Item>,
_ctx: &VisitorContext<'_>,
) -> VisitDecision {
obj.retain(|_, item| !matches!(item, Item::Scalar(Value::Null)));
VisitDecision::Continue
}
}
#[test]
fn test_mutable_visitor_can_clean_objects() {
let mut visitor = ObjectCleaner;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let mut obj = BTreeMap::new();
obj.insert("a".to_string(), Item::Scalar(Value::Int(1)));
obj.insert("b".to_string(), Item::Scalar(Value::Null));
obj.insert("c".to_string(), Item::Scalar(Value::String("test".into())));
visitor.end_object_mut("obj", &mut obj, &ctx);
assert_eq!(obj.len(), 2);
assert!(obj.contains_key("a"));
assert!(!obj.contains_key("b"));
assert!(obj.contains_key("c"));
}
}