error_rail/validation/
core.rs

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