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