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}