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