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