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/// # Serde Support
13///
14/// `Validation` implements `Serialize` and `Deserialize` when `E` and `A` do.
15/// This makes it easy to use in API responses or configuration files.
16///
17/// # Type Parameters
18///
19/// * `E` - The error type
20/// * `A` - The success value type
21///
22/// # Variants
23///
24/// * `Valid(A)` - Contains a successful value
25/// * `Invalid(ErrorVec<E>)` - Contains one or more errors
26///
27/// # Examples
28///
29/// ```
30/// use error_rail::validation::Validation;
31///
32/// let valid = Validation::<&str, i32>::valid(42);
33/// assert!(valid.is_valid());
34///
35/// let invalid = Validation::<&str, i32>::invalid("error");
36/// assert!(invalid.is_invalid());
37/// ```
38#[must_use]
39#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Serialize, Deserialize)]
40pub enum Validation<E, A> {
41    Valid(A),
42    Invalid(ErrorVec<E>),
43}
44
45impl<E, A> Validation<E, A> {
46    /// Creates a valid value.
47    ///
48    /// # Arguments
49    ///
50    /// * `value` - The success value to wrap
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use error_rail::validation::Validation;
56    ///
57    /// let v = Validation::<&str, i32>::valid(42);
58    /// assert_eq!(v.into_value(), Some(42));
59    /// ```
60    #[must_use]
61    #[inline]
62    pub fn valid(value: A) -> Self {
63        Self::Valid(value)
64    }
65
66    /// Creates an invalid value from a single error.
67    ///
68    /// # Arguments
69    ///
70    /// * `error` - The error to wrap
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use error_rail::validation::Validation;
76    ///
77    /// let v = Validation::<&str, ()>::invalid("missing field");
78    /// assert!(v.is_invalid());
79    /// ```
80    #[must_use]
81    #[inline]
82    pub fn invalid(error: E) -> Self {
83        Self::Invalid(smallvec![error])
84    }
85
86    /// Creates an invalid value from an iterator of errors.
87    ///
88    /// # Arguments
89    ///
90    /// * `errors` - An iterator of errors to collect
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use error_rail::validation::Validation;
96    ///
97    /// let v = Validation::<&str, ()>::invalid_many(["missing", "invalid"]);
98    /// assert!(v.is_invalid());
99    /// assert_eq!(v.into_errors().unwrap().len(), 2);
100    /// ```
101    #[must_use]
102    #[inline]
103    pub fn invalid_many<I>(errors: I) -> Self
104    where
105        I: IntoIterator<Item = E>,
106    {
107        Self::Invalid(errors.into_iter().collect())
108    }
109
110    /// Returns `true` if the validation contains a value.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use error_rail::validation::Validation;
116    ///
117    /// let v = Validation::<&str, i32>::valid(42);
118    /// assert!(v.is_valid());
119    /// ```
120    #[must_use]
121    #[inline]
122    pub fn is_valid(&self) -> bool {
123        matches!(self, Self::Valid(_))
124    }
125
126    /// Returns `true` if the validation contains errors.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use error_rail::validation::Validation;
132    ///
133    /// let v = Validation::<&str, i32>::invalid("error");
134    /// assert!(v.is_invalid());
135    /// ```
136    #[must_use]
137    #[inline]
138    pub fn is_invalid(&self) -> bool {
139        !self.is_valid()
140    }
141
142    /// Maps the valid value using the provided function.
143    ///
144    /// If the validation is invalid, the errors are preserved unchanged.
145    ///
146    /// # Arguments
147    ///
148    /// * `f` - A function that transforms the success value from type `A` to type `B`
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use error_rail::validation::Validation;
154    ///
155    /// let v = Validation::<&str, i32>::valid(21);
156    /// let doubled = v.map(|x| x * 2);
157    /// assert_eq!(doubled.into_value(), Some(42));
158    /// ```
159    #[must_use]
160    #[inline]
161    pub fn map<B, F>(self, f: F) -> Validation<E, B>
162    where
163        F: FnOnce(A) -> B,
164    {
165        match self {
166            Self::Valid(value) => Validation::Valid(f(value)),
167            Self::Invalid(errors) => Validation::Invalid(errors),
168        }
169    }
170
171    /// Chains computations that may produce additional validation errors.
172    ///
173    /// Behaves like [`Result::and_then`], propagating invalid states while
174    /// invoking `f` only when the current validation is valid.
175    ///
176    /// # Arguments
177    ///
178    /// * `f` - Function producing the next validation step
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use error_rail::validation::Validation;
184    ///
185    /// fn parse_even(input: i32) -> Validation<&'static str, i32> {
186    ///     if input % 2 == 0 {
187    ///         Validation::valid(input)
188    ///     } else {
189    ///         Validation::invalid("not even")
190    ///     }
191    /// }
192    ///
193    /// let result = Validation::valid(4).and_then(parse_even);
194    /// assert_eq!(result.into_value(), Some(4));
195    ///
196    /// let invalid = Validation::valid(3).and_then(parse_even);
197    /// assert!(invalid.is_invalid());
198    /// ```
199    #[must_use]
200    #[inline]
201    pub fn and_then<B, F>(self, f: F) -> Validation<E, B>
202    where
203        F: FnOnce(A) -> Validation<E, B>,
204    {
205        match self {
206            Self::Valid(value) => f(value),
207            Self::Invalid(errors) => Validation::Invalid(errors),
208        }
209    }
210
211    /// Calls `op` if the validation is invalid, otherwise returns the `Valid` value.
212    ///
213    /// This function can be used for control flow based on validation results.
214    ///
215    /// # Arguments
216    ///
217    /// * `op` - The function to call if the validation is invalid.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use error_rail::validation::Validation;
223    ///
224    /// let v = Validation::<&str, i32>::invalid("error");
225    /// let res = v.or_else(|_errs| Validation::valid(42));
226    /// assert_eq!(res.into_value(), Some(42));
227    /// ```
228    #[must_use]
229    #[inline]
230    pub fn or_else<F>(self, op: F) -> Validation<E, A>
231    where
232        F: FnOnce(ErrorVec<E>) -> Validation<E, A>,
233    {
234        match self {
235            Self::Valid(value) => Validation::Valid(value),
236            Self::Invalid(errors) => op(errors),
237        }
238    }
239
240    /// Combines two validations into a tuple, accumulating all errors.
241    ///
242    /// If both validations are valid, returns a tuple of both values.
243    /// If either or both are invalid, accumulates all errors from both.
244    ///
245    /// # Arguments
246    ///
247    /// * `other` - Another validation to combine with this one
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use error_rail::validation::Validation;
253    ///
254    /// let v1 = Validation::<&str, i32>::valid(42);
255    /// let v2 = Validation::<&str, i32>::valid(21);
256    /// let result = v1.zip(v2);
257    /// assert_eq!(result.into_value(), Some((42, 21)));
258    ///
259    /// let v3 = Validation::<&str, i32>::invalid("error1");
260    /// let v4 = Validation::<&str, i32>::invalid("error2");
261    /// let result = v3.zip(v4);
262    /// assert_eq!(result.into_errors().unwrap().len(), 2);
263    /// ```
264    #[must_use]
265    #[inline]
266    pub fn zip<B>(self, other: Validation<E, B>) -> Validation<E, (A, B)> {
267        match (self, other) {
268            (Validation::Valid(a), Validation::Valid(b)) => Validation::Valid((a, b)),
269            (Validation::Invalid(e), Validation::Valid(_)) => Validation::Invalid(e),
270            (Validation::Valid(_), Validation::Invalid(e)) => Validation::Invalid(e),
271            (Validation::Invalid(mut e1), Validation::Invalid(e2)) => {
272                e1.extend(e2);
273                Validation::Invalid(e1)
274            }
275        }
276    }
277
278    /// Maps each error while preserving the success branch.
279    ///
280    /// Transforms all accumulated errors using the provided function,
281    /// leaving valid values unchanged.
282    ///
283    /// # Arguments
284    ///
285    /// * `f` - A function that transforms errors from type `E` to type `G`
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// use error_rail::validation::Validation;
291    ///
292    /// let v = Validation::<&str, i32>::invalid("error");
293    /// let mapped = v.map_err(|e| format!("Error: {}", e));
294    /// assert!(mapped.is_invalid());
295    /// ```
296    #[must_use]
297    #[inline]
298    pub fn map_err<F, G>(self, f: F) -> Validation<G, A>
299    where
300        F: Fn(E) -> G,
301    {
302        match self {
303            Self::Valid(value) => Validation::Valid(value),
304            Self::Invalid(errors) => Validation::Invalid(errors.into_iter().map(f).collect()),
305        }
306    }
307
308    /// Converts into a `Result`, losing error accumulation if invalid.
309    ///
310    /// The success value becomes `Ok`, and all accumulated errors become `Err`.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use error_rail::validation::Validation;
316    ///
317    /// let v = Validation::<&str, i32>::valid(42);
318    /// assert_eq!(v.to_result(), Ok(42));
319    ///
320    /// let v = Validation::<&str, i32>::invalid("error");
321    /// assert!(v.to_result().is_err());
322    /// ```
323    #[must_use]
324    #[inline]
325    pub fn to_result(self) -> Result<A, ErrorVec<E>> {
326        match self {
327            Self::Valid(value) => Ok(value),
328            Self::Invalid(errors) => Err(errors),
329        }
330    }
331
332    /// Wraps a normal `Result` into a `Validation`, turning the error side into a singleton vec.
333    ///
334    /// # Arguments
335    ///
336    /// * `result` - The result to convert
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use error_rail::validation::Validation;
342    ///
343    /// let result: Result<i32, &str> = Ok(42);
344    /// let v = Validation::from_result(result);
345    /// assert!(v.is_valid());
346    /// ```
347    #[must_use]
348    #[inline]
349    pub fn from_result(result: Result<A, E>) -> Self {
350        match result {
351            Ok(value) => Self::Valid(value),
352            Err(error) => Self::invalid(error),
353        }
354    }
355
356    /// Extracts the error list, if any.
357    ///
358    /// Returns `Some(errors)` if invalid, `None` if valid.
359    ///
360    /// # Examples
361    ///
362    /// ```
363    /// use error_rail::validation::Validation;
364    ///
365    /// let v = Validation::<&str, i32>::invalid("error");
366    /// assert_eq!(v.into_errors().unwrap().len(), 1);
367    /// ```
368    #[must_use]
369    #[inline]
370    pub fn into_errors(self) -> Option<ErrorVec<E>> {
371        match self {
372            Self::Valid(_) => None,
373            Self::Invalid(errors) => Some(errors),
374        }
375    }
376
377    /// Extracts the value, if valid.
378    ///
379    /// Returns `Some(value)` if valid, `None` if invalid.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use error_rail::validation::Validation;
385    ///
386    /// let v = Validation::<&str, i32>::valid(42);
387    /// assert_eq!(v.into_value(), Some(42));
388    /// ```
389    #[must_use]
390    #[inline]
391    pub fn into_value(self) -> Option<A> {
392        match self {
393            Self::Valid(value) => Some(value),
394            Self::Invalid(_) => None,
395        }
396    }
397}