use crate::visitor::{VisitDecision, VisitorContext};
use crate::{Document, MatrixList, Node, Value};
pub trait FallibleVisitor {
type Error;
fn begin_document(
&mut self,
_doc: &Document,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn end_document(
&mut self,
_doc: &Document,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn visit_scalar(
&mut self,
_key: &str,
_value: &Value,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn begin_object(
&mut self,
_key: &str,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn end_object(
&mut self,
_key: &str,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn begin_list(
&mut self,
_key: &str,
_list: &MatrixList,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn end_list(
&mut self,
_key: &str,
_list: &MatrixList,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn visit_node(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn begin_node_children(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn end_node_children(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn visit_reference(
&mut self,
_reference: &crate::Reference,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn visit_expression(
&mut self,
_expr: &crate::lex::Expression,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
fn visit_tensor(
&mut self,
_tensor: &crate::lex::Tensor,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
Ok(VisitDecision::Continue)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoOpFallibleVisitor;
impl FallibleVisitor for NoOpFallibleVisitor {
type Error = String;
}
#[test]
fn test_default_implementations_return_ok_continue() {
let mut visitor = NoOpFallibleVisitor;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
assert_eq!(
visitor.begin_document(&doc, &ctx).unwrap(),
VisitDecision::Continue
);
assert_eq!(
visitor.end_document(&doc, &ctx).unwrap(),
VisitDecision::Continue
);
assert_eq!(
visitor.visit_scalar("key", &Value::Null, &ctx).unwrap(),
VisitDecision::Continue
);
}
#[derive(Debug, PartialEq)]
enum TestError {
NullNotAllowed,
EmptyId,
}
struct StrictValidator;
impl FallibleVisitor for StrictValidator {
type Error = TestError;
fn visit_scalar(
&mut self,
_key: &str,
value: &Value,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
if matches!(value, Value::Null) {
Err(TestError::NullNotAllowed)
} else {
Ok(VisitDecision::Continue)
}
}
fn visit_node(
&mut self,
node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
if node.id.is_empty() {
Err(TestError::EmptyId)
} else {
Ok(VisitDecision::Continue)
}
}
}
#[test]
fn test_fallible_visitor_returns_error_on_null() {
let mut visitor = StrictValidator;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let result = visitor.visit_scalar("key", &Value::Null, &ctx);
assert_eq!(result, Err(TestError::NullNotAllowed));
}
#[test]
fn test_fallible_visitor_accepts_non_null() {
let mut visitor = StrictValidator;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let result = visitor.visit_scalar("key", &Value::Int(42), &ctx);
assert_eq!(result, Ok(VisitDecision::Continue));
}
#[test]
fn test_fallible_visitor_validates_node_ids() {
let mut visitor = StrictValidator;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let empty_id_node = Node::new("User", "", vec![]);
let result = visitor.visit_node(&empty_id_node, &ctx);
assert_eq!(result, Err(TestError::EmptyId));
let valid_node = Node::new("User", "alice", vec![]);
let result = visitor.visit_node(&valid_node, &ctx);
assert_eq!(result, Ok(VisitDecision::Continue));
}
struct EarlyStopOnError;
impl FallibleVisitor for EarlyStopOnError {
type Error = String;
fn visit_node(
&mut self,
node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
if node.type_name == "BadType" {
Ok(VisitDecision::Stop)
} else {
Ok(VisitDecision::Continue)
}
}
}
#[test]
fn test_fallible_visitor_can_stop_early() {
let mut visitor = EarlyStopOnError;
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let normal_node = Node::new("User", "1", vec![]);
assert_eq!(
visitor.visit_node(&normal_node, &ctx).unwrap(),
VisitDecision::Continue
);
let bad_node = Node::new("BadType", "1", vec![]);
assert_eq!(
visitor.visit_node(&bad_node, &ctx).unwrap(),
VisitDecision::Stop
);
}
struct CountingValidator {
count: usize,
max_count: usize,
}
impl FallibleVisitor for CountingValidator {
type Error = String;
fn visit_node(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<VisitDecision, Self::Error> {
self.count += 1;
if self.count > self.max_count {
Err(format!("Too many nodes: {}", self.count))
} else {
Ok(VisitDecision::Continue)
}
}
}
#[test]
fn test_fallible_visitor_error_propagation() {
let mut visitor = CountingValidator {
count: 0,
max_count: 2,
};
let doc = Document::new((2, 0));
let ctx = VisitorContext::new(&doc);
let node = Node::new("User", "1", vec![]);
assert!(visitor.visit_node(&node, &ctx).is_ok());
assert!(visitor.visit_node(&node, &ctx).is_ok());
assert!(visitor.visit_node(&node, &ctx).is_err());
}
}