Skip to main content

peer_pressure/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![no_std]
3#![warn(missing_docs)]
4#![doc = include_str!("../README.md")] // the doc is in another castle
5
6extern crate self as peer_pressure;
7
8mod ops;
9mod tuple;
10
11#[cfg(feature = "async")]
12mod resolve;
13
14#[cfg(test)]
15mod tests;
16
17use core::convert::Infallible;
18
19pub use ops::*;
20#[cfg(feature = "derive")]
21pub use peer_pressure_derive as derive;
22#[cfg(feature = "async")]
23#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
24pub use resolve::{Resolve, ResolveFrom};
25
26/// Marker trait for types that are valid by construction.
27///
28/// Every [Valid] type trivially validates as itself via [Validate] and [ValidateFrom].
29///
30/// # Examples
31///
32/// ```
33/// use peer_pressure::{Valid, Validate};
34///
35/// #[derive(Clone, Debug, PartialEq)]
36/// pub struct NonEmptyVec<T>(Vec<T>);
37///
38/// impl<T> NonEmptyVec<T> {
39///     pub fn new(inner: Vec<T>) -> Option<Self> {
40///         (inner.len() > 0).then_some(Self(inner))
41///     }
42/// }
43///
44/// // `NonEmptyVec` can only be constructed via `NonEmptyVec::new` which ensures the non-emptiness
45/// // invariant holds for all `NonEmptyVec` values.
46/// impl<T> Valid for NonEmptyVec<T> {}
47///
48/// let p = NonEmptyVec::new(vec![1,2,3]).unwrap();
49/// assert_eq!(p.clone().validate(), Ok(p));
50/// ```
51pub trait Valid {}
52
53/// Deterministic validation.
54///
55/// Types implementing [Validate] commit to a single, canonical notion of validity, encoded
56/// by the `Output` type. The implementation of [Validate] must ensure that all invariants
57/// assumed of `Output` hold, and `Output` itself should not be constructible in a way that would
58/// violate those invariants.
59///
60/// In other words, `Output` should correctly implement [Valid].
61///
62/// # Examples
63///
64/// ```rust
65/// use peer_pressure::Validate;
66///
67/// struct RawAge(i32);
68///
69/// struct Age(u32);
70///
71/// impl Validate for RawAge {
72///     // Age being non-negative is a context-free property
73///     type Context = ();
74///     type Output = Age;
75///     type Error = &'static str;
76///
77///     fn validate_in_context(self, _: &()) -> Result<Age, &'static str> {
78///         if self.0 >= 0 {
79///             Ok(Age(self.0 as u32))
80///         } else {
81///             Err("negative age")
82///         }
83///     }
84/// }
85///
86/// let age = RawAge(30).validate().unwrap();
87/// assert_eq!(age.0, 30);
88/// assert!(RawAge(-1).validate().is_err());
89/// ```
90///
91/// ```rust
92/// use peer_pressure::Validate;
93///
94/// type ScrabbleHand = Vec<char>;
95/// pub struct Word(String);
96///
97/// pub struct LegalWord(String);
98///
99/// impl Validate for Word {
100///     // A scrabble word is valid for a given set of letters on hand
101///     type Context = ScrabbleHand;
102///     type Output = LegalWord;
103///     type Error = &'static str;
104///
105///     fn validate_in_context(self, hand: &ScrabbleHand) -> Result<LegalWord, Self::Error> {
106///         todo!("cmon, you can implement that yourself")
107///     }
108/// }
109/// ```
110pub trait Validate: Sized {
111    /// Immutable context of the validation.
112    ///
113    /// Use `()` for context-free checks.
114    type Context: ?Sized;
115    /// Validated form of `Self`.
116    type Output;
117    /// Validation error
118    type Error;
119
120    /// Validates `self` in given context, returning either a valid `Output` or `Error`.
121    fn validate_in_context(self, ctx: &Self::Context) -> Result<Self::Output, Self::Error>;
122
123    /// Validates `self`, returning either a valid `Output` or `Error`.
124    ///
125    /// Available only for types with context-free validation (ie. for which `Context` is equivalent to `()`).
126    fn validate(self) -> Result<Self::Output, Self::Error>
127    where
128        Self::Context: From<()>,
129    {
130        self.validate_in_context(&().into())
131    }
132}
133
134impl<T: Valid> Validate for T {
135    type Context = ();
136    type Output = T;
137    type Error = Infallible;
138
139    fn validate_in_context(self, _: &()) -> Result<T, Infallible> {
140        Ok(self)
141    }
142}
143
144/// Target-side dual of [Validate].
145///
146/// Unlike [Validate], which forces a single validated output type, [ValidateFrom] allows
147/// the same type to be validated from multiple source types, since many domain types may
148/// arrive in various formats. This should not be abused to make a single raw value validate
149/// into multiple types, but I can't force you.
150///
151/// # Examples
152///
153/// ```
154/// use peer_pressure::ValidateFrom;
155///
156/// struct Email(String);
157///
158/// impl ValidateFrom<&str> for Email {
159///     type Context = ();
160///     type Error = &'static str;
161///
162///     fn validate_from_in_context(raw: &str, _: &()) -> Result<Self, Self::Error> {
163///         if raw.contains('@') { Ok(Email(raw.to_string())) } else { Err("missing @") }
164///     }
165/// }
166///
167/// assert!(Email::validate_from("alice@example.com").is_ok());
168/// assert!(Email::validate_from("not-an-email").is_err());
169/// ```
170pub trait ValidateFrom<Raw>: Sized {
171    /// Immutable context of the validation.
172    ///
173    /// Use `()` for context-free checks.
174    type Context: ?Sized;
175    /// Validation error
176    type Error;
177
178    /// Validates `raw` in given context, returning either a valid `Self` or `Error`.
179    fn validate_from_in_context(raw: Raw, ctx: &Self::Context) -> Result<Self, Self::Error>;
180
181    /// Validates `raw`, returning either a valid `Self` or `Error`.
182    ///
183    /// Available only for types with context-free validation (ie. for which `Context = ()`).
184    fn validate_from(raw: Raw) -> Result<Self, Self::Error>
185    where
186        Self::Context: From<()>,
187    {
188        Self::validate_from_in_context(raw, &().into())
189    }
190}
191
192impl<T: Valid, R: Into<T>> ValidateFrom<R> for T {
193    type Context = ();
194    type Error = Infallible;
195
196    fn validate_from_in_context(raw: R, _: &()) -> Result<T, Infallible> {
197        Ok(raw.into())
198    }
199}