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