Skip to main content

deser_validate/
validator.rs

1//! The validator trait and violations.
2use std::borrow::Cow;
3use std::fmt;
4
5use deser_core::{Error, ErrorAttachment, ErrorKind};
6
7/// Validates values of type `T`.
8///
9/// Validators are types (usually without fields) so that they can be named
10/// in the types of fields, for instance `Validated<String, Email>`.  They
11/// are parameterized with const generics (`Len<1, 64>`) and combined with
12/// tuples: `(NonEmpty, MaxLen<64>)` requires all of them.  Validators that
13/// need data that cannot be a const generic (like a pattern) are types of
14/// their own.  Implementing them for all types that are strings covers
15/// `String`, `&str`, `Cow<str>` and `Box<str>`:
16///
17/// ```
18/// use deser_validate::{Validator, Violation};
19///
20/// /// A lowercase identifier like `my-service`.
21/// pub struct Slug;
22///
23/// impl<T: AsRef<str> + ?Sized> Validator<T> for Slug {
24///     fn validate(value: &T) -> Result<(), Violation> {
25///         let value = value.as_ref();
26///         if !value.is_empty()
27///             && value.bytes().all(|b| {
28///                 b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-'
29///             })
30///         {
31///             Ok(())
32///         } else {
33///             Err(Violation::new("slug", "must be a lowercase identifier"))
34///         }
35///     }
36/// }
37///
38/// assert!(Slug::validate("my-service").is_ok());
39/// assert!(Slug::validate(&"My Service".to_string()).is_err());
40/// ```
41///
42/// Most validators are easier to write with the
43/// [`validator!`](macro@crate::validator) macro, which turns a condition or
44/// a function into a validator type.
45/// Implementing the trait is needed for types with generics or lifetimes,
46/// which the macro does not support:
47///
48/// ```
49/// use deser_validate::{Validator, Violation};
50///
51/// pub struct Name<'a>(&'a str);
52///
53/// pub struct NonEmptyName;
54///
55/// impl<'a> Validator<Name<'a>> for NonEmptyName {
56///     fn validate(value: &Name<'a>) -> Result<(), Violation> {
57///         if value.0.is_empty() {
58///             return Err(Violation::new("not_empty", "must not be empty"));
59///         }
60///         Ok(())
61///     }
62/// }
63/// ```
64pub trait Validator<T: ?Sized> {
65    /// Validates the value.
66    fn validate(value: &T) -> Result<(), Violation>;
67}
68
69/// Accepts every value.
70impl<T: ?Sized> Validator<T> for () {
71    fn validate(_value: &T) -> Result<(), Violation> {
72        Ok(())
73    }
74}
75
76macro_rules! impl_tuple {
77    ($($name:ident),*) => {
78        /// Requires all validators, the first violation is reported.
79        impl<T: ?Sized, $($name: Validator<T>),*> Validator<T> for ($($name,)*) {
80            fn validate(value: &T) -> Result<(), Violation> {
81                $($name::validate(value)?;)*
82                Ok(())
83            }
84        }
85    };
86}
87
88impl_tuple!(A);
89impl_tuple!(A, B);
90impl_tuple!(A, B, C);
91impl_tuple!(A, B, C, D);
92impl_tuple!(A, B, C, D, E);
93impl_tuple!(A, B, C, D, E, F);
94
95/// A parameter of a [`Violation`].
96#[derive(Debug, Clone, PartialEq)]
97pub enum Param {
98    /// An integer.
99    Int(i128),
100    /// A string.
101    Str(Cow<'static, str>),
102}
103
104impl fmt::Display for Param {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Param::Int(value) => fmt::Display::fmt(value, f),
108            Param::Str(value) => fmt::Display::fmt(value, f),
109        }
110    }
111}
112
113impl From<i128> for Param {
114    fn from(value: i128) -> Param {
115        Param::Int(value)
116    }
117}
118
119impl From<usize> for Param {
120    fn from(value: usize) -> Param {
121        Param::Int(value as i128)
122    }
123}
124
125impl From<&'static str> for Param {
126    fn from(value: &'static str) -> Param {
127        Param::Str(Cow::Borrowed(value))
128    }
129}
130
131impl From<String> for Param {
132    fn from(value: String) -> Param {
133        Param::Str(Cow::Owned(value))
134    }
135}
136
137/// Why a value is invalid.
138///
139/// A violation has a code which identifies the rule that was violated (for
140/// instance `length`), a message for humans and parameters (for instance
141/// the minimum length).  Codes and parameters are meant for programs, for
142/// instance to translate the messages or to point at the value in a user
143/// interface.
144///
145/// When a value is invalid, the error has the violation attached (see
146/// [`Error::attachment`]):
147///
148/// ```
149/// use deser::Deserialize;
150/// use deser_validate::{Check, Email, Violation};
151///
152/// #[derive(Deserialize, Debug)]
153/// struct User {
154///     #[deser(as = Check<Email>)]
155///     email: String,
156/// }
157///
158/// let json = r#"{"email": "nope"}"#;
159/// let err = deser_json::from_str::<User>(json).unwrap_err();
160/// assert_eq!(
161///     err.to_string(),
162///     "InvalidValue: invalid value: must be an email address \
163///      at line 1 column 11"
164/// );
165/// assert_eq!(err.attachment::<Violation>().unwrap().code(), "email");
166/// ```
167#[derive(Debug, Clone, PartialEq)]
168pub struct Violation {
169    code: Cow<'static, str>,
170    message: Cow<'static, str>,
171    params: Vec<(&'static str, Param)>,
172}
173
174impl Violation {
175    /// Creates a violation with a code and a message.
176    pub fn new<C, M>(code: C, message: M) -> Violation
177    where
178        C: Into<Cow<'static, str>>,
179        M: Into<Cow<'static, str>>,
180    {
181        Violation {
182            code: code.into(),
183            message: message.into(),
184            params: Vec::new(),
185        }
186    }
187
188    /// Adds a parameter.
189    pub fn with_param<P: Into<Param>>(mut self, name: &'static str, value: P) -> Violation {
190        self.params.push((name, value.into()));
191        self
192    }
193
194    /// Returns the code of the rule that was violated.
195    pub fn code(&self) -> &str {
196        &self.code
197    }
198
199    /// Returns the message.
200    pub fn message(&self) -> &str {
201        &self.message
202    }
203
204    /// Returns the parameters.
205    pub fn params(&self) -> &[(&'static str, Param)] {
206        &self.params
207    }
208
209    /// Returns the value of a parameter.
210    pub fn param(&self, name: &str) -> Option<&Param> {
211        self.params
212            .iter()
213            .find(|(key, _)| *key == name)
214            .map(|(_, value)| value)
215    }
216
217    /// Creates the error for the violation.
218    ///
219    /// The error has the violation attached, the message is the same as
220    /// the one of `#[deser(validate = ...)]`.
221    pub fn into_error(self) -> Error {
222        let mut err = Error::new(
223            ErrorKind::InvalidValue,
224            format!("invalid value: {}", self.message),
225        );
226        err.set_attachment(self);
227        err
228    }
229}
230
231impl fmt::Display for Violation {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        f.write_str(&self.message)
234    }
235}
236
237impl ErrorAttachment for Violation {}