error_rail/validation/
core.rs

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