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