error_rail/validation/
core.rs

1use crate::types::ErrorVec;
2use serde::{Deserialize, Serialize};
3use smallvec::smallvec;
4
5/// Applicative-style validation that accumulates many errors instead of failing fast.
6///
7/// `Validation<E, A>` represents a computation that either succeeds with a value of type `A`
8/// or fails with one or more errors of type `E`. Unlike `Result`, which fails fast on the first
9/// error, `Validation` accumulates all errors, making it ideal for form validation and other
10/// scenarios where you want to collect all problems at once.
11///
12/// # Type Parameters
13///
14/// * `E` - The error type
15/// * `A` - The success value type
16///
17/// # Variants
18///
19/// * `Valid(A)` - Contains a successful value
20/// * `Invalid(ErrorVec<E>)` - Contains one or more errors
21///
22/// # Examples
23///
24/// ```
25/// use error_rail::validation::Validation;
26///
27/// let valid = Validation::<&str, i32>::valid(42);
28/// assert!(valid.is_valid());
29///
30/// let invalid = Validation::<&str, i32>::invalid("error");
31/// assert!(invalid.is_invalid());
32/// ```
33#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Serialize, Deserialize)]
34pub enum Validation<E, A> {
35    Valid(A),
36    Invalid(ErrorVec<E>),
37}
38
39impl<E, A> Validation<E, A> {
40    /// Creates a valid value.
41    ///
42    /// # Arguments
43    ///
44    /// * `value` - The success value to wrap
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use error_rail::validation::Validation;
50    ///
51    /// let v = Validation::<&str, i32>::valid(42);
52    /// assert_eq!(v.into_value(), Some(42));
53    /// ```
54    #[inline]
55    pub fn valid(value: A) -> Self {
56        Self::Valid(value)
57    }
58
59    /// Creates an invalid value from a single error.
60    ///
61    /// # Arguments
62    ///
63    /// * `error` - The error to wrap
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use error_rail::validation::Validation;
69    ///
70    /// let v = Validation::<&str, ()>::invalid("missing field");
71    /// assert!(v.is_invalid());
72    /// ```
73    #[inline]
74    pub fn invalid(error: E) -> Self {
75        Self::Invalid(smallvec![error])
76    }
77
78    /// Creates an invalid value from an iterator of errors.
79    ///
80    /// # Arguments
81    ///
82    /// * `errors` - An iterator of errors to collect
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use error_rail::validation::Validation;
88    ///
89    /// let v = Validation::<&str, ()>::invalid_many(["missing", "invalid"]);
90    /// assert!(v.is_invalid());
91    /// assert_eq!(v.into_errors().unwrap().len(), 2);
92    /// ```
93    #[inline]
94    pub fn invalid_many<I>(errors: I) -> Self
95    where
96        I: IntoIterator<Item = E>,
97    {
98        Self::Invalid(errors.into_iter().collect())
99    }
100
101    /// Returns `true` if the validation contains a value.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use error_rail::validation::Validation;
107    ///
108    /// let v = Validation::<&str, i32>::valid(42);
109    /// assert!(v.is_valid());
110    /// ```
111    #[inline]
112    pub fn is_valid(&self) -> bool {
113        matches!(self, Self::Valid(_))
114    }
115
116    /// Returns `true` if the validation contains errors.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use error_rail::validation::Validation;
122    ///
123    /// let v = Validation::<&str, i32>::invalid("error");
124    /// assert!(v.is_invalid());
125    /// ```
126    #[inline]
127    pub fn is_invalid(&self) -> bool {
128        !self.is_valid()
129    }
130
131    /// Maps the valid value using the provided function.
132    ///
133    /// If the validation is invalid, the errors are preserved unchanged.
134    ///
135    /// # Arguments
136    ///
137    /// * `f` - A function that transforms the success value from type `A` to type `B`
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use error_rail::validation::Validation;
143    ///
144    /// let v = Validation::<&str, i32>::valid(21);
145    /// let doubled = v.map(|x| x * 2);
146    /// assert_eq!(doubled.into_value(), Some(42));
147    /// ```
148    #[inline]
149    pub fn map<B, F>(self, f: F) -> Validation<E, B>
150    where
151        F: FnOnce(A) -> B,
152    {
153        match self {
154            Self::Valid(value) => Validation::Valid(f(value)),
155            Self::Invalid(errors) => Validation::Invalid(errors),
156        }
157    }
158
159    /// Chains computations that may produce additional validation errors.
160    ///
161    /// Behaves like [`Result::and_then`], propagating invalid states while
162    /// invoking `f` only when the current validation is valid.
163    ///
164    /// # Arguments
165    ///
166    /// * `f` - Function producing the next validation step
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use error_rail::validation::Validation;
172    ///
173    /// fn parse_even(input: i32) -> Validation<&'static str, i32> {
174    ///     if input % 2 == 0 {
175    ///         Validation::valid(input)
176    ///     } else {
177    ///         Validation::invalid("not even")
178    ///     }
179    /// }
180    ///
181    /// let result = Validation::valid(4).and_then(parse_even);
182    /// assert_eq!(result.into_value(), Some(4));
183    ///
184    /// let invalid = Validation::valid(3).and_then(parse_even);
185    /// assert!(invalid.is_invalid());
186    /// ```
187    #[inline]
188    pub fn and_then<B, F>(self, f: F) -> Validation<E, B>
189    where
190        F: FnOnce(A) -> Validation<E, B>,
191    {
192        match self {
193            Self::Valid(value) => f(value),
194            Self::Invalid(errors) => Validation::Invalid(errors),
195        }
196    }
197
198    /// Maps each error while preserving the success branch.
199    ///
200    /// Transforms all accumulated errors using the provided function,
201    /// leaving valid values unchanged.
202    ///
203    /// # Arguments
204    ///
205    /// * `f` - A function that transforms errors from type `E` to type `G`
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use error_rail::validation::Validation;
211    ///
212    /// let v = Validation::<&str, i32>::invalid("error");
213    /// let mapped = v.map_err(|e| format!("Error: {}", e));
214    /// assert!(mapped.is_invalid());
215    /// ```
216    #[inline]
217    pub fn map_err<F, G>(self, f: F) -> Validation<G, A>
218    where
219        F: Fn(E) -> G,
220    {
221        match self {
222            Self::Valid(value) => Validation::Valid(value),
223            Self::Invalid(errors) => Validation::Invalid(errors.into_iter().map(f).collect()),
224        }
225    }
226
227    /// Converts into a `Result`, losing error accumulation if invalid.
228    ///
229    /// The success value becomes `Ok`, and all accumulated errors become `Err`.
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// use error_rail::validation::Validation;
235    ///
236    /// let v = Validation::<&str, i32>::valid(42);
237    /// assert_eq!(v.to_result(), Ok(42));
238    ///
239    /// let v = Validation::<&str, i32>::invalid("error");
240    /// assert!(v.to_result().is_err());
241    /// ```
242    #[inline]
243    pub fn to_result(self) -> Result<A, ErrorVec<E>> {
244        match self {
245            Self::Valid(value) => Ok(value),
246            Self::Invalid(errors) => Err(errors),
247        }
248    }
249
250    /// Wraps a normal `Result` into a `Validation`, turning the error side into a singleton vec.
251    ///
252    /// # Arguments
253    ///
254    /// * `result` - The result to convert
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// use error_rail::validation::Validation;
260    ///
261    /// let result: Result<i32, &str> = Ok(42);
262    /// let v = Validation::from_result(result);
263    /// assert!(v.is_valid());
264    /// ```
265    #[inline]
266    pub fn from_result(result: Result<A, E>) -> Self
267    where
268        E: Clone,
269    {
270        match result {
271            Ok(value) => Self::Valid(value),
272            Err(error) => Self::invalid(error),
273        }
274    }
275
276    /// Extracts the error list, if any.
277    ///
278    /// Returns `Some(errors)` if invalid, `None` if valid.
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use error_rail::validation::Validation;
284    ///
285    /// let v = Validation::<&str, i32>::invalid("error");
286    /// assert_eq!(v.into_errors().unwrap().len(), 1);
287    /// ```
288    #[inline]
289    pub fn into_errors(self) -> Option<ErrorVec<E>> {
290        match self {
291            Self::Valid(_) => None,
292            Self::Invalid(errors) => Some(errors),
293        }
294    }
295
296    /// Extracts the value, if valid.
297    ///
298    /// Returns `Some(value)` if valid, `None` if invalid.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use error_rail::validation::Validation;
304    ///
305    /// let v = Validation::<&str, i32>::valid(42);
306    /// assert_eq!(v.into_value(), Some(42));
307    /// ```
308    #[inline]
309    pub fn into_value(self) -> Option<A> {
310        match self {
311            Self::Valid(value) => Some(value),
312            Self::Invalid(_) => None,
313        }
314    }
315}