icydb_model/
application.rs1use crate::{normalize, validate, visitor::Visitable, visitor::VisitorError};
7
8pub trait NormalizeAndValidate: Visitable + Sized {
15 fn normalize_and_validate(mut self) -> Result<Self, VisitorError> {
23 normalize(&mut self)?;
24 validate(&self)?;
25 Ok(self)
26 }
27}
28
29impl<T> NormalizeAndValidate for T where T: Visitable {}
30
31#[cfg(test)]
32mod tests {
33 use std::{cell::Cell, rc::Rc};
34
35 use crate::visitor::{
36 ApplicationOperation, NormalizeAuto, NormalizeCustom, ValidateAuto, ValidateCustom,
37 Visitable, VisitorContext,
38 };
39
40 use super::NormalizeAndValidate as _;
41
42 #[derive(Debug)]
43 struct Probe {
44 value: String,
45 validation_calls: Rc<Cell<u32>>,
46 normalization_fails: bool,
47 }
48
49 impl Visitable for Probe {}
50
51 impl NormalizeAuto for Probe {
52 fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
53 self.value.make_ascii_lowercase();
54 if self.normalization_fails {
55 ctx.issue("normalization rejected");
56 }
57 }
58 }
59
60 impl NormalizeCustom for Probe {}
61
62 impl ValidateAuto for Probe {
63 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
64 self.validation_calls
65 .set(self.validation_calls.get().saturating_add(1));
66 if self.value != "accepted" {
67 ctx.issue("validation rejected");
68 }
69 }
70 }
71
72 impl ValidateCustom for Probe {}
73
74 #[test]
75 fn composition_normalizes_before_validation_without_cloning() {
76 let validation_calls = Rc::new(Cell::new(0));
77 let probe = Probe {
78 value: "ACCEPTED".to_string(),
79 validation_calls: Rc::clone(&validation_calls),
80 normalization_fails: false,
81 };
82
83 let normalized = probe
84 .normalize_and_validate()
85 .expect("normalized value should validate");
86
87 assert_eq!(normalized.value, "accepted");
88 assert_eq!(validation_calls.get(), 1);
89 }
90
91 #[test]
92 fn composition_stops_before_validation_when_normalization_fails() {
93 let validation_calls = Rc::new(Cell::new(0));
94 let probe = Probe {
95 value: "ACCEPTED".to_string(),
96 validation_calls: Rc::clone(&validation_calls),
97 normalization_fails: true,
98 };
99
100 let error = probe
101 .normalize_and_validate()
102 .expect_err("normalization issue should stop composition");
103
104 assert_eq!(error.operation(), ApplicationOperation::Normalize);
105 assert_eq!(validation_calls.get(), 0);
106 }
107
108 #[test]
109 fn composition_reports_validation_as_the_failing_stage() {
110 let validation_calls = Rc::new(Cell::new(0));
111 let probe = Probe {
112 value: "REJECTED".to_string(),
113 validation_calls: Rc::clone(&validation_calls),
114 normalization_fails: false,
115 };
116
117 let error = probe
118 .normalize_and_validate()
119 .expect_err("normalized rejected value should fail validation");
120
121 assert_eq!(error.operation(), ApplicationOperation::Validate);
122 assert_eq!(validation_calls.get(), 1);
123 }
124}