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 const 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        Self::Invalid(Accumulator::single(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 (must contain at least one error)
91    ///
92    /// # Panics
93    ///
94    /// Panics if the iterator is empty.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// use error_rail::validation::Validation;
100    ///
101    /// let v = Validation::<&str, ()>::invalid_many(["missing", "invalid"]);
102    /// assert!(v.is_invalid());
103    /// assert_eq!(v.into_errors().unwrap().len(), 2);
104    /// ```
105    #[inline]
106    pub fn invalid_many<I>(errors: I) -> Self
107    where
108        I: IntoIterator<Item = E>,
109    {
110        let acc = Accumulator::from_iter(errors);
111        assert!(!acc.is_empty(), "invalid_many requires at least one error");
112        Self::Invalid(acc)
113    }
114
115    /// Creates an invalid value from an iterator of errors, returning `None` if empty.
116    ///
117    /// # Arguments
118    ///
119    /// * `errors` - An iterator of errors to collect
120    ///
121    /// # Returns
122    ///
123    /// * `Some(Validation::Invalid(...))` if the iterator contains at least one error
124    /// * `None` if the iterator is empty
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use error_rail::validation::Validation;
130    ///
131    /// let v = Validation::<&str, ()>::try_invalid_many(["missing", "invalid"]);
132    /// assert!(v.is_some());
133    ///
134    /// let empty: Option<Validation<&str, ()>> = Validation::try_invalid_many(Vec::<&str>::new());
135    /// assert!(empty.is_none());
136    /// ```
137    #[inline]
138    pub fn try_invalid_many<I>(errors: I) -> Option<Self>
139    where
140        I: IntoIterator<Item = E>,
141    {
142        let acc = Accumulator::from_iter(errors);
143        if acc.is_empty() {
144            None
145        } else {
146            Some(Self::Invalid(acc))
147        }
148    }
149
150    /// Returns `true` if the validation contains a value.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use error_rail::validation::Validation;
156    ///
157    /// let v = Validation::<&str, i32>::valid(42);
158    /// assert!(v.is_valid());
159    /// ```
160    #[must_use]
161    #[inline]
162    pub const fn is_valid(&self) -> bool {
163        matches!(self, Self::Valid(_))
164    }
165
166    /// Returns `true` if the validation contains errors.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use error_rail::validation::Validation;
172    ///
173    /// let v = Validation::<&str, i32>::invalid("error");
174    /// assert!(v.is_invalid());
175    /// ```
176    #[must_use]
177    #[inline]
178    pub const fn is_invalid(&self) -> bool {
179        matches!(self, Self::Invalid(_))
180    }
181
182    /// Maps the valid value using the provided function.
183    ///
184    /// If the validation is invalid, the errors are preserved unchanged.
185    ///
186    /// # Arguments
187    ///
188    /// * `f` - A function that transforms the success value from type `A` to type `B`
189    ///
190    /// # Examples
191    ///
192    /// ```
193    /// use error_rail::validation::Validation;
194    ///
195    /// let v = Validation::<&str, i32>::valid(21);
196    /// let doubled = v.map(|x| x * 2);
197    /// assert_eq!(doubled.into_value(), Some(42));
198    /// ```
199    #[inline]
200    pub fn map<B, F>(self, f: F) -> Validation<E, B>
201    where
202        F: FnOnce(A) -> B,
203    {
204        match self {
205            Self::Valid(value) => Validation::Valid(f(value)),
206            Self::Invalid(errors) => Validation::Invalid(errors),
207        }
208    }
209
210    /// Chains computations that may produce additional validation errors.
211    ///
212    /// Behaves like [`Result::and_then`], propagating invalid states while
213    /// invoking `f` only when the current validation is valid.
214    ///
215    /// # Arguments
216    ///
217    /// * `f` - Function producing the next validation step
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use error_rail::validation::Validation;
223    ///
224    /// fn parse_even(input: i32) -> Validation<&'static str, i32> {
225    ///     if input % 2 == 0 {
226    ///         Validation::valid(input)
227    ///     } else {
228    ///         Validation::invalid("not even")
229    ///     }
230    /// }
231    ///
232    /// let result = Validation::valid(4).and_then(parse_even);
233    /// assert_eq!(result.into_value(), Some(4));
234    ///
235    /// let invalid = Validation::valid(3).and_then(parse_even);
236    /// assert!(invalid.is_invalid());
237    /// ```
238    #[inline]
239    pub fn and_then<B, F>(self, f: F) -> Validation<E, B>
240    where
241        F: FnOnce(A) -> Validation<E, B>,
242    {
243        match self {
244            Self::Valid(value) => f(value),
245            Self::Invalid(errors) => Validation::Invalid(errors),
246        }
247    }
248
249    /// Calls `op` if the validation is invalid, otherwise returns the `Valid` value.
250    ///
251    /// This function can be used for control flow based on validation results.
252    ///
253    /// # Arguments
254    ///
255    /// * `op` - The function to call if the validation is invalid.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use error_rail::validation::Validation;
261    ///
262    /// let v = Validation::<&str, i32>::invalid("error");
263    /// let res = v.or_else(|_errs| Validation::valid(42));
264    /// assert_eq!(res.into_value(), Some(42));
265    /// ```
266    #[inline]
267    pub fn or_else<F>(self, op: F) -> Self
268    where
269        F: FnOnce(ErrorVec<E>) -> Self,
270    {
271        match self {
272            Self::Valid(value) => Self::Valid(value),
273            Self::Invalid(errors) => op(errors.into_inner()),
274        }
275    }
276
277    /// Combines two validations into a tuple, accumulating all errors.
278    ///
279    /// If both validations are valid, returns a tuple of both values.
280    /// If either or both are invalid, accumulates all errors from both.
281    ///
282    /// # Error Order
283    ///
284    /// When both validations are invalid, errors from `self` appear first,
285    /// followed by errors from `other`.
286    ///
287    /// # Arguments
288    ///
289    /// * `other` - Another validation to combine with this one
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use error_rail::validation::Validation;
295    ///
296    /// let v1 = Validation::<&str, i32>::valid(42);
297    /// let v2 = Validation::<&str, i32>::valid(21);
298    /// let result = v1.zip(v2);
299    /// assert_eq!(result.into_value(), Some((42, 21)));
300    ///
301    /// let v3 = Validation::<&str, i32>::invalid("error1");
302    /// let v4 = Validation::<&str, i32>::invalid("error2");
303    /// let result = v3.zip(v4);
304    /// assert_eq!(result.into_errors().unwrap().len(), 2);
305    /// ```
306    #[inline]
307    pub fn zip<B>(self, other: Validation<E, B>) -> Validation<E, (A, B)> {
308        match (self, other) {
309            (Self::Valid(a), Validation::Valid(b)) => Validation::Valid((a, b)),
310            (Self::Invalid(e), Validation::Valid(_)) | (Self::Valid(_), Validation::Invalid(e)) => {
311                Validation::Invalid(e)
312            },
313            (Self::Invalid(mut e1), Validation::Invalid(e2)) => {
314                e1.merge(e2);
315                Validation::Invalid(e1)
316            },
317        }
318    }
319
320    /// Maps each error while preserving the success branch.
321    ///
322    /// Transforms all accumulated errors using the provided function,
323    /// leaving valid values unchanged.
324    ///
325    /// # Arguments
326    ///
327    /// * `f` - A function that transforms errors from type `E` to type `G`
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use error_rail::validation::Validation;
333    ///
334    /// let v = Validation::<&str, i32>::invalid("error");
335    /// let mapped = v.map_err(|e| format!("Error: {}", e));
336    /// assert!(mapped.is_invalid());
337    /// ```
338    #[inline]
339    pub fn map_err<F, G>(self, f: F) -> Validation<G, A>
340    where
341        F: FnMut(E) -> G,
342    {
343        match self {
344            Self::Valid(value) => Validation::Valid(value),
345            Self::Invalid(errors) => Validation::Invalid(errors.map(f)),
346        }
347    }
348
349    /// Converts into a `Result`, losing error accumulation if invalid.
350    ///
351    /// The success value becomes `Ok`, and all accumulated errors become `Err`.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use error_rail::validation::Validation;
357    ///
358    /// let v = Validation::<&str, i32>::valid(42);
359    /// assert_eq!(v.to_result(), Ok(42));
360    ///
361    /// let v = Validation::<&str, i32>::invalid("error");
362    /// assert!(v.to_result().is_err());
363    /// ```
364    #[inline]
365    pub fn to_result(self) -> Result<A, ErrorVec<E>> {
366        match self {
367            Self::Valid(value) => Ok(value),
368            Self::Invalid(errors) => Err(errors.into_inner()),
369        }
370    }
371
372    /// Wraps a normal `Result` into a `Validation`, turning the error side into a singleton vec.
373    ///
374    /// # Arguments
375    ///
376    /// * `result` - The result to convert
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use error_rail::validation::Validation;
382    ///
383    /// let result: Result<i32, &str> = Ok(42);
384    /// let v = Validation::from_result(result);
385    /// assert!(v.is_valid());
386    /// ```
387    #[inline]
388    pub fn from_result(result: Result<A, E>) -> Self {
389        match result {
390            Ok(value) => Self::Valid(value),
391            Err(error) => Self::invalid(error),
392        }
393    }
394
395    /// Extracts the error list, if any.
396    ///
397    /// Returns `Some(errors)` if invalid, `None` if valid.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// use error_rail::validation::Validation;
403    ///
404    /// let v = Validation::<&str, i32>::invalid("error");
405    /// assert_eq!(v.into_errors().unwrap().len(), 1);
406    /// ```
407    #[must_use]
408    #[inline]
409    pub fn into_errors(self) -> Option<ErrorVec<E>> {
410        match self {
411            Self::Valid(_) => None,
412            Self::Invalid(errors) => Some(errors.into_inner()),
413        }
414    }
415
416    /// Extracts the value, if valid.
417    ///
418    /// Returns `Some(value)` if valid, `None` if invalid.
419    ///
420    /// # Examples
421    ///
422    /// ```
423    /// use error_rail::validation::Validation;
424    ///
425    /// let v = Validation::<&str, i32>::valid(42);
426    /// assert_eq!(v.into_value(), Some(42));
427    /// ```
428    #[must_use]
429    #[inline]
430    pub fn into_value(self) -> Option<A> {
431        match self {
432            Self::Valid(value) => Some(value),
433            Self::Invalid(_) => None,
434        }
435    }
436}