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
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use error_rail::validation::Validation;
98    ///
99    /// let v = Validation::<&str, ()>::invalid_many(["missing", "invalid"]);
100    /// assert!(v.is_invalid());
101    /// assert_eq!(v.into_errors().unwrap().len(), 2);
102    /// ```
103    #[inline]
104    pub fn invalid_many<I>(errors: I) -> Self
105    where
106        I: IntoIterator<Item = E>,
107    {
108        let mut acc = Accumulator::new();
109        acc.extend(errors);
110        Self::Invalid(acc)
111    }
112
113    /// Returns `true` if the validation contains a value.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use error_rail::validation::Validation;
119    ///
120    /// let v = Validation::<&str, i32>::valid(42);
121    /// assert!(v.is_valid());
122    /// ```
123    #[must_use]
124    #[inline]
125    pub fn is_valid(&self) -> bool {
126        matches!(self, Self::Valid(_))
127    }
128
129    /// Returns `true` if the validation contains errors.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use error_rail::validation::Validation;
135    ///
136    /// let v = Validation::<&str, i32>::invalid("error");
137    /// assert!(v.is_invalid());
138    /// ```
139    #[must_use]
140    #[inline]
141    pub fn is_invalid(&self) -> bool {
142        !self.is_valid()
143    }
144
145    /// Maps the valid value using the provided function.
146    ///
147    /// If the validation is invalid, the errors are preserved unchanged.
148    ///
149    /// # Arguments
150    ///
151    /// * `f` - A function that transforms the success value from type `A` to type `B`
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use error_rail::validation::Validation;
157    ///
158    /// let v = Validation::<&str, i32>::valid(21);
159    /// let doubled = v.map(|x| x * 2);
160    /// assert_eq!(doubled.into_value(), Some(42));
161    /// ```
162    #[inline]
163    pub fn map<B, F>(self, f: F) -> Validation<E, B>
164    where
165        F: FnOnce(A) -> B,
166    {
167        match self {
168            Self::Valid(value) => Validation::Valid(f(value)),
169            Self::Invalid(errors) => Validation::Invalid(errors),
170        }
171    }
172
173    /// Chains computations that may produce additional validation errors.
174    ///
175    /// Behaves like [`Result::and_then`], propagating invalid states while
176    /// invoking `f` only when the current validation is valid.
177    ///
178    /// # Arguments
179    ///
180    /// * `f` - Function producing the next validation step
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use error_rail::validation::Validation;
186    ///
187    /// fn parse_even(input: i32) -> Validation<&'static str, i32> {
188    ///     if input % 2 == 0 {
189    ///         Validation::valid(input)
190    ///     } else {
191    ///         Validation::invalid("not even")
192    ///     }
193    /// }
194    ///
195    /// let result = Validation::valid(4).and_then(parse_even);
196    /// assert_eq!(result.into_value(), Some(4));
197    ///
198    /// let invalid = Validation::valid(3).and_then(parse_even);
199    /// assert!(invalid.is_invalid());
200    /// ```
201    #[inline]
202    pub fn and_then<B, F>(self, f: F) -> Validation<E, B>
203    where
204        F: FnOnce(A) -> Validation<E, B>,
205    {
206        match self {
207            Self::Valid(value) => f(value),
208            Self::Invalid(errors) => Validation::Invalid(errors),
209        }
210    }
211
212    /// Calls `op` if the validation is invalid, otherwise returns the `Valid` value.
213    ///
214    /// This function can be used for control flow based on validation results.
215    ///
216    /// # Arguments
217    ///
218    /// * `op` - The function to call if the validation is invalid.
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use error_rail::validation::Validation;
224    ///
225    /// let v = Validation::<&str, i32>::invalid("error");
226    /// let res = v.or_else(|_errs| Validation::valid(42));
227    /// assert_eq!(res.into_value(), Some(42));
228    /// ```
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.into_inner()),
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    #[inline]
265    pub fn zip<B>(self, other: Validation<E, B>) -> Validation<E, (A, B)> {
266        match (self, other) {
267            (Validation::Valid(a), Validation::Valid(b)) => Validation::Valid((a, b)),
268            (Validation::Invalid(e), Validation::Valid(_)) => Validation::Invalid(e),
269            (Validation::Valid(_), Validation::Invalid(e)) => Validation::Invalid(e),
270            (Validation::Invalid(mut e1), Validation::Invalid(e2)) => {
271                e1.extend(e2.into_inner());
272                Validation::Invalid(e1)
273            },
274        }
275    }
276
277    /// Maps each error while preserving the success branch.
278    ///
279    /// Transforms all accumulated errors using the provided function,
280    /// leaving valid values unchanged.
281    ///
282    /// # Arguments
283    ///
284    /// * `f` - A function that transforms errors from type `E` to type `G`
285    ///
286    /// # Examples
287    ///
288    /// ```
289    /// use error_rail::validation::Validation;
290    ///
291    /// let v = Validation::<&str, i32>::invalid("error");
292    /// let mapped = v.map_err(|e| format!("Error: {}", e));
293    /// assert!(mapped.is_invalid());
294    /// ```
295    #[inline]
296    pub fn map_err<F, G>(self, f: F) -> Validation<G, A>
297    where
298        F: Fn(E) -> G,
299    {
300        match self {
301            Self::Valid(value) => Validation::Valid(value),
302            Self::Invalid(errors) => {
303                let mut acc = Accumulator::new();
304                acc.extend(errors.into_inner().into_iter().map(f));
305                Validation::Invalid(acc)
306            },
307        }
308    }
309
310    /// Converts into a `Result`, losing error accumulation if invalid.
311    ///
312    /// The success value becomes `Ok`, and all accumulated errors become `Err`.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use error_rail::validation::Validation;
318    ///
319    /// let v = Validation::<&str, i32>::valid(42);
320    /// assert_eq!(v.to_result(), Ok(42));
321    ///
322    /// let v = Validation::<&str, i32>::invalid("error");
323    /// assert!(v.to_result().is_err());
324    /// ```
325    #[inline]
326    pub fn to_result(self) -> Result<A, ErrorVec<E>> {
327        match self {
328            Self::Valid(value) => Ok(value),
329            Self::Invalid(errors) => Err(errors.into_inner()),
330        }
331    }
332
333    /// Wraps a normal `Result` into a `Validation`, turning the error side into a singleton vec.
334    ///
335    /// # Arguments
336    ///
337    /// * `result` - The result to convert
338    ///
339    /// # Examples
340    ///
341    /// ```
342    /// use error_rail::validation::Validation;
343    ///
344    /// let result: Result<i32, &str> = Ok(42);
345    /// let v = Validation::from_result(result);
346    /// assert!(v.is_valid());
347    /// ```
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.into_inner()),
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}