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