1use crate::visitor::{
7 PathSegment, VisitorContext, VisitorCore, VisitorMutCore, perform_visit, perform_visit_mut,
8};
9
10pub trait Visitable: Normalize + Validate {
23 fn drive(&self, _: &mut dyn VisitorCore) {}
24 fn drive_mut(&mut self, _: &mut dyn VisitorMutCore) {}
25}
26
27impl<T: Visitable> Visitable for Option<T> {
36 fn drive(&self, visitor: &mut dyn VisitorCore) {
37 if let Some(value) = self.as_ref() {
38 perform_visit(visitor, value, PathSegment::Empty);
39 }
40 }
41
42 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
43 if let Some(value) = self.as_mut() {
44 perform_visit_mut(visitor, value, PathSegment::Empty);
45 }
46 }
47}
48
49impl<T: Visitable> Visitable for Vec<T> {
50 fn drive(&self, visitor: &mut dyn VisitorCore) {
51 for (i, value) in self.iter().enumerate() {
52 perform_visit(visitor, value, i);
53 }
54 }
55
56 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
57 for (i, value) in self.iter_mut().enumerate() {
58 perform_visit_mut(visitor, value, i);
59 }
60 }
61}
62
63impl<T: Visitable> Visitable for Box<T> {
64 fn drive(&self, visitor: &mut dyn VisitorCore) {
65 (**self).drive(visitor);
66 }
67
68 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
69 (**self).drive_mut(visitor);
70 }
71}
72
73macro_rules! impl_primitive_visitable {
75 ($($ty:ty),* $(,)?) => {
76 $(impl Visitable for $ty {})*
77 };
78}
79
80impl_primitive_visitable!(
81 i8,
82 i16,
83 i32,
84 i64,
85 i128,
86 u8,
87 u16,
88 u32,
89 u64,
90 u128,
91 f32,
92 f64,
93 bool,
94 String,
95 crate::schema::Account,
96 crate::schema::Blob,
97 crate::schema::Date,
98 crate::schema::Decimal,
99 crate::schema::Duration,
100 crate::schema::Float32,
101 crate::schema::Float64,
102 crate::schema::IntBig,
103 crate::schema::NatBig,
104 crate::schema::Principal,
105 crate::schema::Subaccount,
106 crate::schema::Timestamp,
107 crate::schema::Ulid,
108 crate::schema::Unit,
109);
110
111pub trait Normalize: NormalizeAuto + NormalizeCustom {}
119
120impl<T> Normalize for T where T: NormalizeAuto + NormalizeCustom {}
121
122pub trait NormalizeAuto {
134 fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {}
135}
136
137impl<T: NormalizeAuto> NormalizeAuto for Option<T> {}
138
139impl<T: NormalizeAuto> NormalizeAuto for Vec<T> {}
140
141impl<T: NormalizeAuto + ?Sized> NormalizeAuto for Box<T> {
142 fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
143 (**self).normalize_self(ctx);
144 }
145}
146
147impl_primitive!(NormalizeAuto);
148
149pub trait NormalizeCustom {
157 fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {}
158}
159
160impl<T: NormalizeCustom> NormalizeCustom for Option<T> {}
161
162impl<T: NormalizeCustom> NormalizeCustom for Vec<T> {}
163
164impl<T: NormalizeCustom + ?Sized> NormalizeCustom for Box<T> {
165 fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
166 (**self).normalize_custom(ctx);
167 }
168}
169
170impl_primitive!(NormalizeCustom);
171
172pub trait Validate: ValidateAuto + ValidateCustom {}
180
181impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
182
183pub trait ValidateAuto {
195 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {}
196}
197
198impl<T: ValidateAuto> ValidateAuto for Option<T> {}
199
200impl<T: ValidateAuto> ValidateAuto for Vec<T> {}
201
202impl<T: ValidateAuto + ?Sized> ValidateAuto for Box<T> {
203 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
204 (**self).validate_self(ctx);
205 }
206}
207
208impl_primitive!(ValidateAuto);
209
210pub trait ValidateCustom {
218 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {}
219}
220
221impl<T: ValidateCustom> ValidateCustom for Option<T> {}
222
223impl<T: ValidateCustom> ValidateCustom for Vec<T> {}
224
225impl<T: ValidateCustom + ?Sized> ValidateCustom for Box<T> {
226 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
227 (**self).validate_custom(ctx);
228 }
229}
230
231impl_primitive!(ValidateCustom);
232
233pub trait Normalizer<T> {
235 fn normalize(&self, value: &mut T) -> Result<(), String>;
236
237 fn normalize_with_context(
238 &self,
239 value: &mut T,
240 ctx: &mut dyn VisitorContext,
241 ) -> Result<(), String> {
242 let _ = ctx;
243
244 self.normalize(value)
245 }
246}
247
248pub trait Validator<T: ?Sized> {
250 fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::{
257 normalize::normalize,
258 validate::validate,
259 visitor::{Issue, VisitorError},
260 };
261 use std::cell::Cell;
262
263 const AUTO_NORMALIZE_ISSUE: &str = "automatic normalize";
264 const CUSTOM_NORMALIZE_ISSUE: &str = "custom normalize";
265 const AUTO_VALIDATE_ISSUE: &str = "automatic validate";
266 const CUSTOM_VALIDATE_ISSUE: &str = "custom validate";
267
268 #[derive(Default)]
269 struct HookProbe {
270 auto_normalize: u32,
271 custom_normalize: u32,
272 auto_validate: Cell<u32>,
273 custom_validate: Cell<u32>,
274 }
275
276 impl Visitable for HookProbe {}
277
278 impl NormalizeAuto for HookProbe {
279 fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
280 self.auto_normalize += 1;
281 ctx.issue(AUTO_NORMALIZE_ISSUE);
282 }
283 }
284
285 impl NormalizeCustom for HookProbe {
286 fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
287 self.custom_normalize += 1;
288 ctx.issue(CUSTOM_NORMALIZE_ISSUE);
289 }
290 }
291
292 impl ValidateAuto for HookProbe {
293 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
294 self.auto_validate.set(self.auto_validate.get() + 1);
295 ctx.issue(AUTO_VALIDATE_ISSUE);
296 }
297 }
298
299 impl ValidateCustom for HookProbe {
300 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
301 self.custom_validate.set(self.custom_validate.get() + 1);
302 ctx.issue(CUSTOM_VALIDATE_ISSUE);
303 }
304 }
305
306 fn assert_issues(error: &VisitorError, path: &str, expected: [&str; 2]) {
307 let issues = error
308 .issues()
309 .get(path)
310 .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
311 let messages = issues.iter().map(Issue::message).collect::<Vec<_>>();
312 assert_eq!(messages, expected);
313 }
314
315 #[test]
316 fn option_vec_normalize_hooks_run_once_at_each_indexed_path() {
317 let mut value = Some(vec![HookProbe::default(), HookProbe::default()]);
318
319 let error = normalize(&mut value).expect_err("probe normalizers should report issues");
320
321 let Some(probes) = value.as_ref() else {
322 panic!("normalize should preserve the populated option");
323 };
324 for probe in probes {
325 assert_eq!(probe.auto_normalize, 1);
326 assert_eq!(probe.custom_normalize, 1);
327 }
328 assert!(error.issues().get("").is_none());
329 assert_issues(
330 &error,
331 "[0]",
332 [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
333 );
334 assert_issues(
335 &error,
336 "[1]",
337 [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
338 );
339 }
340
341 #[test]
342 fn option_vec_validate_hooks_run_once_at_each_indexed_path() {
343 let value = Some(vec![HookProbe::default(), HookProbe::default()]);
344
345 let error = validate(&value).expect_err("probe validators should report issues");
346
347 let Some(probes) = value.as_ref() else {
348 panic!("validate should preserve the populated option");
349 };
350 for probe in probes {
351 assert_eq!(probe.auto_validate.get(), 1);
352 assert_eq!(probe.custom_validate.get(), 1);
353 }
354 assert!(error.issues().get("").is_none());
355 assert_issues(&error, "[0]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
356 assert_issues(&error, "[1]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
357 }
358
359 #[test]
360 fn box_transparency_keeps_one_forwarded_hook_call() {
361 let mut normalized = Box::new(HookProbe::default());
362 let _ = normalize(&mut normalized).expect_err("probe normalizers should report issues");
363 assert_eq!(normalized.auto_normalize, 1);
364 assert_eq!(normalized.custom_normalize, 1);
365
366 let validated = Box::new(HookProbe::default());
367 let _ = validate(&validated).expect_err("probe validators should report issues");
368 assert_eq!(validated.auto_validate.get(), 1);
369 assert_eq!(validated.custom_validate.get(), 1);
370 }
371}