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