validity/lib.rs
1//! Type safe validation of arbitrary data
2//!
3//! Provides the `Valid<T>` struct which wraps some data, after verifiying that it meets some
4//! criteria:
5//! ```
6//! # use validity::*;
7//! #[derive(Debug)]
8//! struct PhoneNumber(String);
9//!
10//! enum InvalidPhoneNumber {
11//! NonDigit,
12//! WrongLength,
13//! }
14//!
15//! impl Validate for PhoneNumber {
16//! type Context<'a> = ();
17//! type Error = InvalidPhoneNumber;
18//!
19//! fn is_valid(&self, _ctx: Self::Context<'_>) -> Result<(), Self::Error> {
20//! if self.0.len() == 11 {
21//! return Err(InvalidPhoneNumber::WrongLength);
22//! }
23//!
24//! if self.0.chars().any(|c| !c.is_digit(10)) {
25//! return Err(InvalidPhoneNumber::NonDigit);
26//! }
27//!
28//! Ok(())
29//! }
30//! }
31//!
32//! fn main() {
33//! let number = PhoneNumber("01234567890".to_string());
34//! if let Ok(number) = number.validate() {
35//! handle_phone_number(number);
36//! } else {
37//! println!("error!");
38//! }
39//! }
40//!
41//! fn handle_phone_number(number: Valid<PhoneNumber>) {
42//! println!("This is a definitely valid phone number: {:?}", number.into_inner());
43//! }
44//! ```
45//!
46//! Some validation requires access to some context. For example, you may want to validate that an
47//! email address exists in your database. For that, you can pass this context via the `Context`
48//! associated type.
49//!
50//! When validating, you can call `foo.validate_with(context)`:
51//! ```
52//! # use validity::*;
53//! # struct Database {}
54//! # impl Database {
55//! # fn check_phone_exists(&self, _: &PhoneNumber) -> bool { true }
56//! # fn new() -> Self { Self {} }
57//! # }
58//! #[derive(Debug)]
59//! struct PhoneNumber(String);
60//!
61//! enum InvalidPhoneNumber {
62//! NonDigit,
63//! WrongLength,
64//! NotInDatabase,
65//! }
66//!
67//! impl Validate for PhoneNumber {
68//! type Context<'a> = Database;
69//! type Error = InvalidPhoneNumber;
70//!
71//! fn is_valid(&self, db: Self::Context<'_>) -> Result<(), Self::Error> {
72//! if self.0.len() == 11 {
73//! return Err(InvalidPhoneNumber::WrongLength);
74//! }
75//!
76//! if self.0.chars().any(|c| !c.is_digit(10)) {
77//! return Err(InvalidPhoneNumber::NonDigit);
78//! }
79//!
80//! if !db.check_phone_exists(self) {
81//! return Err(InvalidPhoneNumber::NotInDatabase);
82//! }
83//!
84//! Ok(())
85//! }
86//! }
87//! ```
88//! You can then call this with:
89//! ```rust
90//! # use validity::*;
91//! # struct Database {}
92//! # impl Database {
93//! # fn check_phone_exists(&self, _: &PhoneNumber) -> bool { true }
94//! # fn new() -> Self { Self {} }
95//! # }
96//! # #[derive(Debug)]
97//! # struct PhoneNumber(String);
98//! # enum InvalidPhoneNumber {
99//! # NonDigit,
100//! # WrongLength,
101//! # NotInDatabase,
102//! # }
103//! # impl Validate for PhoneNumber {
104//! # type Context<'a> = Database;
105//! # type Error = InvalidPhoneNumber;
106//! # fn is_valid(&self, db: Self::Context<'_>) -> Result<(), Self::Error> {
107//! # if self.0.len() == 11 {
108//! # return Err(InvalidPhoneNumber::WrongLength);
109//! # }
110//! # if self.0.chars().any(|c| !c.is_digit(10)) {
111//! # return Err(InvalidPhoneNumber::NonDigit);
112//! # }
113//! # if !db.check_phone_exists(self) {
114//! # return Err(InvalidPhoneNumber::NotInDatabase);
115//! # }
116//! # Ok(())
117//! # }
118//! # }
119//! let db = Database::new();
120//! let phone = PhoneNumber("01234567890".to_string());
121//! phone.validate_with(db);
122//! ```
123
124#![no_std]
125
126use core::{
127 fmt::{Debug, Formatter},
128 ops::Deref,
129};
130
131/// A thin wrapper around a value that guarantees that it is "valid"
132///
133/// A `Valid<T>` can only be constructed by calling [`Validate::validate`] and then handling the
134/// possible error
135///
136/// Note, `Valid<T>` is not `repr(transparent)`, so using `transmute` to forcibly convert is
137/// undefined behaviour.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
139pub struct Valid<T>(T);
140
141impl<T> Valid<T> {
142 /// Consume self and return the inner value
143 pub fn into_inner(self) -> T {
144 self.0
145 }
146
147 /// Create a `Valid<T>` without validating
148 ///
149 /// This is only available with the `test-mock` feature enabled.
150 ///
151 /// It goes without saying that this function invalidates all compile-time guarantees. It's
152 /// provided as an "escape hatch", intended for testing. While
153 #[cfg(feature = "test-mock")]
154 pub fn danger_new_unvalidated(t: T) -> Self {
155 Self(t)
156 }
157}
158
159impl<T> Deref for Valid<T> {
160 type Target = T;
161 fn deref(&self) -> &Self::Target {
162 &self.0
163 }
164}
165
166/// A trait which defines what it means for a type to be "valid"
167///
168/// Because validity is defined by a trait, each type has a single definition of "valid", so
169/// newtype wrappers are recommended to give additional meaning to each type.
170///
171/// For example:
172/// ```
173/// # use validity::*;
174/// #[derive(Debug)]
175/// struct PhoneNumber(String);
176///
177/// enum InvalidPhoneNumber {
178/// NonDigit,
179/// WrongLength,
180/// }
181///
182/// impl Validate for PhoneNumber {
183/// type Context<'a> = ();
184/// type Error = InvalidPhoneNumber;
185///
186/// fn is_valid(&self, _ctx: Self::Context<'_>) -> Result<(), Self::Error> {
187/// if self.0.len() == 11 {
188/// return Err(InvalidPhoneNumber::WrongLength);
189/// }
190///
191/// if self.0.chars().any(|c| !c.is_digit(10)) {
192/// return Err(InvalidPhoneNumber::NonDigit);
193/// }
194///
195/// Ok(())
196/// }
197/// }
198///
199/// fn main() {
200/// let number = PhoneNumber("01234567890".to_string());
201/// if let Ok(number) = number.validate() {
202/// handle_phone_number(number);
203/// } else {
204/// println!("error!");
205/// }
206/// }
207///
208/// fn handle_phone_number(number: Valid<PhoneNumber>) {
209/// println!("This is a definitely valid phone number: {:?}", number.into_inner());
210/// }
211/// ```
212pub trait Validate {
213 /// Context required for validation
214 type Context<'a>;
215
216 /// The error returned by validation operations
217 type Error;
218
219 /// Perform the validation on this object
220 ///
221 /// Valid data should return `Ok(())`, and invalid data should return `Err(Self::Error)` which
222 /// indicates the reason why validation failed
223 fn is_valid(&self, ctx: Self::Context<'_>) -> Result<(), Self::Error>;
224
225 /// Validate with the given context
226 fn validate_with(self, ctx: Self::Context<'_>) -> Result<Valid<Self>, Failure<Self>>
227 where
228 Self: Sized,
229 {
230 match self.is_valid(ctx) {
231 Ok(()) => Ok(Valid(self)),
232 Err(error) => Err(Failure { value: self, error }),
233 }
234 }
235
236 /// Validate this object, and if successful return a `Valid<Self>` which acts as a "proof of
237 /// validity"
238 ///
239 /// If validation fails,
240 fn validate(self) -> Result<Valid<Self>, Failure<Self>>
241 where
242 Self: for<'a> Validate<Context<'a> = ()>,
243 Self: Sized,
244 {
245 self.validate_with(())
246 }
247}
248
249/// A struct representing a failure to validate a value
250///
251/// It contains both the value that failed validation, as well as the error that caused that error
252pub struct Failure<T: Validate> {
253 /// The value that caused validation to fail
254 pub value: T,
255 /// The error that was generated
256 pub error: <T as Validate>::Error,
257}
258
259impl<T> Debug for Failure<T>
260where
261 T: Validate + Debug,
262 T::Error: Debug,
263{
264 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
265 f.debug_struct("Failure")
266 .field("value", &self.value)
267 .field("error", &self.error)
268 .finish()
269 }
270}