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}