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 {}