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 [`validator!`](crate::validator)
43/// macro, which turns a condition or a function into a validator type.
44/// Implementing the trait is needed for types with generics or lifetimes,
45/// which the macro does not support:
46///
47/// ```
48/// use deser_validate::{Validator, Violation};
49///
50/// pub struct Name<'a>(&'a str);
51///
52/// pub struct NonEmptyName;
53///
54/// impl<'a> Validator<Name<'a>> for NonEmptyName {
55///     fn validate(value: &Name<'a>) -> Result<(), Violation> {
56///         if value.0.is_empty() {
57///             return Err(Violation::new("not_empty", "must not be empty"));
58///         }
59///         Ok(())
60///     }
61/// }
62/// ```
63pub trait Validator<T: ?Sized> {
64    /// Validates the value.
65    fn validate(value: &T) -> Result<(), Violation>;
66}
67
68/// Accepts every value.
69impl<T: ?Sized> Validator<T> for () {
70    fn validate(_value: &T) -> Result<(), Violation> {
71        Ok(())
72    }
73}
74
75macro_rules! impl_tuple {
76    ($($name:ident),*) => {
77        /// Requires all validators, the first violation is reported.
78        impl<T: ?Sized, $($name: Validator<T>),*> Validator<T> for ($($name,)*) {
79            fn validate(value: &T) -> Result<(), Violation> {
80                $($name::validate(value)?;)*
81                Ok(())
82            }
83        }
84    };
85}
86
87impl_tuple!(A);
88impl_tuple!(A, B);
89impl_tuple!(A, B, C);
90impl_tuple!(A, B, C, D);
91impl_tuple!(A, B, C, D, E);
92impl_tuple!(A, B, C, D, E, F);
93
94/// A parameter of a [`Violation`].
95#[derive(Debug, Clone, PartialEq)]
96pub enum Param {
97    /// An integer.
98    Int(i128),
99    /// A string.
100    Str(Cow<'static, str>),
101}
102
103impl fmt::Display for Param {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Param::Int(value) => fmt::Display::fmt(value, f),
107            Param::Str(value) => fmt::Display::fmt(value, f),
108        }
109    }
110}
111
112impl From<i128> for Param {
113    fn from(value: i128) -> Param {
114        Param::Int(value)
115    }
116}
117
118impl From<usize> for Param {
119    fn from(value: usize) -> Param {
120        Param::Int(value as i128)
121    }
122}
123
124impl From<&'static str> for Param {
125    fn from(value: &'static str) -> Param {
126        Param::Str(Cow::Borrowed(value))
127    }
128}
129
130impl From<String> for Param {
131    fn from(value: String) -> Param {
132        Param::Str(Cow::Owned(value))
133    }
134}
135
136/// Why a value is invalid.
137///
138/// A violation has a code which identifies the rule that was violated (for
139/// instance `length`), a message for humans and parameters (for instance
140/// the minimum length).  Codes and parameters are meant for programs, for
141/// instance to translate the messages or to point at the value in a user
142/// interface.
143///
144/// When a value is invalid, the error has the violation attached (see
145/// [`Error::attachment`]):
146///
147/// ```
148/// use deser::Deserialize;
149/// use deser_validate::{Check, Email, Violation};
150///
151/// #[derive(Deserialize, Debug)]
152/// struct User {
153///     #[deser(as = Check<Email>)]
154///     email: String,
155/// }
156///
157/// let json = r#"{"email": "nope"}"#;
158/// let err = deser_json::from_str::<User>(json).unwrap_err();
159/// assert_eq!(
160///     err.to_string(),
161///     "Unexpected: invalid value: must be an email address \
162///      at line 1 column 11"
163/// );
164/// assert_eq!(err.attachment::<Violation>().unwrap().code(), "email");
165/// ```
166#[derive(Debug, Clone, PartialEq)]
167pub struct Violation {
168    code: Cow<'static, str>,
169    message: Cow<'static, str>,
170    params: Vec<(&'static str, Param)>,
171}
172
173impl Violation {
174    /// Creates a violation with a code and a message.
175    pub fn new<C, M>(code: C, message: M) -> Violation
176    where
177        C: Into<Cow<'static, str>>,
178        M: Into<Cow<'static, str>>,
179    {
180        Violation {
181            code: code.into(),
182            message: message.into(),
183            params: Vec::new(),
184        }
185    }
186
187    /// Adds a parameter.
188    pub fn with_param<P: Into<Param>>(mut self, name: &'static str, value: P) -> Violation {
189        self.params.push((name, value.into()));
190        self
191    }
192
193    /// Returns the code of the rule that was violated.
194    pub fn code(&self) -> &str {
195        &self.code
196    }
197
198    /// Returns the message.
199    pub fn message(&self) -> &str {
200        &self.message
201    }
202
203    /// Returns the parameters.
204    pub fn params(&self) -> &[(&'static str, Param)] {
205        &self.params
206    }
207
208    /// Returns the value of a parameter.
209    pub fn param(&self, name: &str) -> Option<&Param> {
210        self.params
211            .iter()
212            .find(|(key, _)| *key == name)
213            .map(|(_, value)| value)
214    }
215
216    /// Creates the error for the violation.
217    ///
218    /// The error has the violation attached, the message is the same as
219    /// the one of `#[deser(validate = ...)]`.
220    pub fn into_error(self) -> Error {
221        Error::new(
222            ErrorKind::Unexpected,
223            format!("invalid value: {}", self.message),
224        )
225        .with_attachment(self)
226    }
227}
228
229impl fmt::Display for Violation {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        f.write_str(&self.message)
232    }
233}
234
235impl ErrorAttachment for Violation {}