garde/
validate.rs

1//! ## Core validation traits and types
2
3use std::fmt::Debug;
4
5use crate::error::{Path, PathComponentKind};
6use crate::Report;
7
8/// The core trait of this crate.
9///
10/// Validation runs the fields through every validation rules,
11/// and aggregates any errors into a [`Report`].
12pub trait Validate {
13    /// A user-provided context.
14    ///
15    /// Custom validators receive a reference to this context.
16    type Context;
17
18    /// Validates `Self`, returning an `Err` with an aggregate of all errors if
19    /// the validation failed.
20    ///
21    /// This method should not be implemented manually. Implement [`Validate::validate_into`] instead,
22    /// because [`Validate::validate`] has a default implementation that calls [`Validate::validate_into`].
23    fn validate(&self) -> Result<(), Report>
24    where
25        Self::Context: Default,
26    {
27        let ctx = Self::Context::default();
28        self.validate_with(&ctx)
29    }
30
31    /// Validates `Self`, returning an `Err` with an aggregate of all errors if
32    /// the validation failed.
33    ///
34    /// This method should not be implemented manually. Implement [`Validate::validate_into`] instead,
35    /// because [`Validate::validate_with`] has a default implementation that calls [`Validate::validate_into`].
36    fn validate_with(&self, ctx: &Self::Context) -> Result<(), Report> {
37        let mut report = Report::new();
38        self.validate_into(ctx, &mut Path::empty, &mut report);
39        match report.is_empty() {
40            true => Ok(()),
41            false => Err(report),
42        }
43    }
44
45    /// Validates `Self`, aggregating all validation errors into `Report`.
46    fn validate_into(
47        &self,
48        ctx: &Self::Context,
49        parent: &mut dyn FnMut() -> Path,
50        report: &mut Report,
51    );
52}
53
54/// A struct which wraps a valid instance of some `T`.
55///
56/// The only way to create an instance of this struct is through the `validate`
57/// function on the [`Unvalidated`] type. This ensures that if you have a `Valid<T>`,
58/// it was definitely validated at some point. This is commonly referred to as the
59/// typestate pattern.
60#[derive(Debug, Clone, Copy)]
61pub struct Valid<T>(T);
62
63impl<T: Validate> Valid<T> {
64    /// Returns the inner value.
65    pub fn into_inner(self) -> T {
66        self.0
67    }
68}
69
70impl<T> std::ops::Deref for Valid<T> {
71    type Target = T;
72
73    fn deref(&self) -> &Self::Target {
74        &self.0
75    }
76}
77
78/// A struct which wraps a potentially invalid instance of some `T`.
79///
80/// Use the `validate` method to turn this type into a `Valid<T>`.
81#[derive(Clone, Copy, Default)]
82#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
83#[cfg_attr(feature = "serde", serde(transparent))]
84#[repr(transparent)]
85pub struct Unvalidated<T>(T);
86
87impl<T: Validate> Unvalidated<T> {
88    /// Creates an `Unvalidated<T>`
89    pub fn new(v: T) -> Self {
90        Self(v)
91    }
92
93    /// Validates `self`, transforming it into a `Valid<T>`.
94    /// This is the only way to create an instance of `Valid<T>`.
95    pub fn validate(self) -> Result<Valid<T>, Report>
96    where
97        <T as Validate>::Context: Default,
98    {
99        self.0.validate()?;
100        Ok(Valid(self.0))
101    }
102
103    /// Validates `self`, transforming it into a `Valid<T>`.
104    /// This is the only way to create an instance of `Valid<T>`.
105    pub fn validate_with(self, ctx: &<T as Validate>::Context) -> Result<Valid<T>, Report> {
106        self.0.validate_with(ctx)?;
107        Ok(Valid(self.0))
108    }
109}
110
111impl<T: Validate> From<T> for Unvalidated<T> {
112    fn from(value: T) -> Self {
113        Self(value)
114    }
115}
116
117impl<T: Debug> Debug for Unvalidated<T> {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        Debug::fmt(&self.0, f)
120    }
121}
122
123impl<T: ?Sized + Validate> Validate for &T {
124    type Context = T::Context;
125
126    fn validate_into(
127        &self,
128        ctx: &Self::Context,
129        parent: &mut dyn FnMut() -> Path,
130        report: &mut Report,
131    ) {
132        <T as Validate>::validate_into(self, ctx, parent, report)
133    }
134}
135
136impl<T: ?Sized + Validate> Validate for &mut T {
137    type Context = T::Context;
138
139    fn validate_into(
140        &self,
141        ctx: &Self::Context,
142        parent: &mut dyn FnMut() -> Path,
143        report: &mut Report,
144    ) {
145        <T as Validate>::validate_into(self, ctx, parent, report)
146    }
147}
148
149impl<T: Validate> Validate for std::boxed::Box<T> {
150    type Context = T::Context;
151
152    fn validate_into(
153        &self,
154        ctx: &Self::Context,
155        parent: &mut dyn FnMut() -> Path,
156        report: &mut Report,
157    ) {
158        <T as Validate>::validate_into(self, ctx, parent, report)
159    }
160}
161
162impl<T: Validate> Validate for std::rc::Rc<T> {
163    type Context = T::Context;
164
165    fn validate_into(
166        &self,
167        ctx: &Self::Context,
168        parent: &mut dyn FnMut() -> Path,
169        report: &mut Report,
170    ) {
171        <T as Validate>::validate_into(self, ctx, parent, report)
172    }
173}
174
175impl<T: Validate> Validate for std::sync::Arc<T> {
176    type Context = T::Context;
177
178    fn validate_into(
179        &self,
180        ctx: &Self::Context,
181        parent: &mut dyn FnMut() -> Path,
182        report: &mut Report,
183    ) {
184        <T as Validate>::validate_into(self, ctx, parent, report)
185    }
186}
187
188macro_rules! impl_validate_list {
189    (<$T:ident $(, $Other:ident)*> $Container:ty) => {
190        impl<$T, $($Other),*> Validate for $Container
191        where
192            $T: Validate
193        {
194            type Context = T::Context;
195
196            fn validate_into(&self, ctx: &Self::Context, mut parent: &mut dyn FnMut() -> Path, report: &mut Report) {
197                for (index, item) in self.iter().enumerate() {
198                    let mut path = $crate::util::nested_path!(parent, index);
199                    <T as Validate>::validate_into(item, ctx, &mut path, report);
200                }
201            }
202        }
203    };
204}
205
206impl_validate_list!(<T, S> std::collections::HashSet<T, S>);
207impl_validate_list!(<T> std::collections::BTreeSet<T>);
208impl_validate_list!(<T> std::collections::BinaryHeap<T>);
209impl_validate_list!(<T> std::collections::LinkedList<T>);
210impl_validate_list!(<T> std::collections::VecDeque<T>);
211impl_validate_list!(<T> std::vec::Vec<T>);
212impl_validate_list!(<T> [T]);
213
214impl<T: Validate, const N: usize> Validate for [T; N] {
215    type Context = T::Context;
216
217    fn validate_into(
218        &self,
219        ctx: &Self::Context,
220        mut parent: &mut dyn FnMut() -> Path,
221        report: &mut Report,
222    ) {
223        for (index, item) in self.iter().enumerate() {
224            let mut path = crate::util::nested_path!(parent, index);
225            <T as Validate>::validate_into(item, ctx, &mut path, report);
226        }
227    }
228}
229
230macro_rules! impl_validate_tuple {
231    ($A:ident, $($T:ident),*) => {
232        impl<$A, $($T),*> Validate for ($A, $($T,)*)
233        where
234            $A : Validate,
235            $($T : Validate<Context=$A::Context>,)*
236        {
237            type Context = $A::Context;
238
239            #[allow(non_snake_case)]
240            fn validate_into(&self, ctx: &Self::Context, mut parent: &mut dyn FnMut() -> Path, report: &mut Report) {
241                let ($A, $($T,)*) = self;
242                let mut index = 0usize;
243                let _index = index;
244                let mut path = $crate::util::nested_path!(parent, _index);
245                <$A as Validate>::validate_into($A, ctx, &mut path, report);
246                drop(path);
247                index += 1;
248                $({
249                    let _index = index;
250                    let mut path = $crate::util::nested_path!(parent, _index);
251                    <$T as Validate>::validate_into($T, ctx, &mut path, report);
252                    drop(path);
253                    index += 1;
254                })*
255                let _ = index;
256            }
257        }
258    }
259}
260
261impl_validate_tuple!(A,);
262impl_validate_tuple!(A, B);
263impl_validate_tuple!(A, B, C);
264impl_validate_tuple!(A, B, C, D);
265impl_validate_tuple!(A, B, C, D, E);
266impl_validate_tuple!(A, B, C, D, E, F);
267impl_validate_tuple!(A, B, C, D, E, F, G);
268impl_validate_tuple!(A, B, C, D, E, F, G, H);
269impl_validate_tuple!(A, B, C, D, E, F, G, H, I);
270impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J);
271impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J, K);
272impl_validate_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
273
274impl Validate for () {
275    type Context = ();
276
277    fn validate_into(&self, _: &Self::Context, _: &mut dyn FnMut() -> Path, _: &mut Report) {}
278}
279
280impl<K, V, S> Validate for std::collections::HashMap<K, V, S>
281where
282    K: Clone + PathComponentKind,
283    V: Validate,
284{
285    type Context = V::Context;
286
287    fn validate_into(
288        &self,
289        ctx: &Self::Context,
290        mut parent: &mut dyn FnMut() -> Path,
291        report: &mut Report,
292    ) {
293        for (key, value) in self.iter() {
294            let mut path = crate::util::nested_path!(parent, key);
295            <V as Validate>::validate_into(value, ctx, &mut path, report);
296        }
297    }
298}
299
300impl<K, V> Validate for std::collections::BTreeMap<K, V>
301where
302    K: Clone + PathComponentKind,
303    V: Validate,
304{
305    type Context = V::Context;
306
307    fn validate_into(
308        &self,
309        ctx: &Self::Context,
310        mut parent: &mut dyn FnMut() -> Path,
311        report: &mut Report,
312    ) {
313        for (key, value) in self.iter() {
314            let mut path = crate::util::nested_path!(parent, key);
315            <V as Validate>::validate_into(value, ctx, &mut path, report);
316        }
317    }
318}
319
320impl<T: Validate> Validate for Option<T> {
321    type Context = T::Context;
322
323    fn validate_into(
324        &self,
325        ctx: &Self::Context,
326        parent: &mut dyn FnMut() -> Path,
327        report: &mut Report,
328    ) {
329        if let Some(value) = self {
330            value.validate_into(ctx, parent, report)
331        }
332    }
333}
334
335impl<B: Validate> Validate for std::borrow::Cow<'_, B>
336where
337    B: ToOwned,
338{
339    type Context = B::Context;
340
341    fn validate_into(
342        &self,
343        ctx: &Self::Context,
344        parent: &mut dyn FnMut() -> Path,
345        report: &mut Report,
346    ) {
347        self.as_ref().validate_into(ctx, parent, report)
348    }
349}