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