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 /// Maps each error while preserving the success branch.
199 ///
200 /// Transforms all accumulated errors using the provided function,
201 /// leaving valid values unchanged.
202 ///
203 /// # Arguments
204 ///
205 /// * `f` - A function that transforms errors from type `E` to type `G`
206 ///
207 /// # Examples
208 ///
209 /// ```
210 /// use error_rail::validation::Validation;
211 ///
212 /// let v = Validation::<&str, i32>::invalid("error");
213 /// let mapped = v.map_err(|e| format!("Error: {}", e));
214 /// assert!(mapped.is_invalid());
215 /// ```
216 #[inline]
217 pub fn map_err<F, G>(self, f: F) -> Validation<G, A>
218 where
219 F: Fn(E) -> G,
220 {
221 match self {
222 Self::Valid(value) => Validation::Valid(value),
223 Self::Invalid(errors) => Validation::Invalid(errors.into_iter().map(f).collect()),
224 }
225 }
226
227 /// Converts into a `Result`, losing error accumulation if invalid.
228 ///
229 /// The success value becomes `Ok`, and all accumulated errors become `Err`.
230 ///
231 /// # Examples
232 ///
233 /// ```
234 /// use error_rail::validation::Validation;
235 ///
236 /// let v = Validation::<&str, i32>::valid(42);
237 /// assert_eq!(v.to_result(), Ok(42));
238 ///
239 /// let v = Validation::<&str, i32>::invalid("error");
240 /// assert!(v.to_result().is_err());
241 /// ```
242 #[inline]
243 pub fn to_result(self) -> Result<A, ErrorVec<E>> {
244 match self {
245 Self::Valid(value) => Ok(value),
246 Self::Invalid(errors) => Err(errors),
247 }
248 }
249
250 /// Wraps a normal `Result` into a `Validation`, turning the error side into a singleton vec.
251 ///
252 /// # Arguments
253 ///
254 /// * `result` - The result to convert
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use error_rail::validation::Validation;
260 ///
261 /// let result: Result<i32, &str> = Ok(42);
262 /// let v = Validation::from_result(result);
263 /// assert!(v.is_valid());
264 /// ```
265 #[inline]
266 pub fn from_result(result: Result<A, E>) -> Self
267 where
268 E: Clone,
269 {
270 match result {
271 Ok(value) => Self::Valid(value),
272 Err(error) => Self::invalid(error),
273 }
274 }
275
276 /// Extracts the error list, if any.
277 ///
278 /// Returns `Some(errors)` if invalid, `None` if valid.
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// use error_rail::validation::Validation;
284 ///
285 /// let v = Validation::<&str, i32>::invalid("error");
286 /// assert_eq!(v.into_errors().unwrap().len(), 1);
287 /// ```
288 #[inline]
289 pub fn into_errors(self) -> Option<ErrorVec<E>> {
290 match self {
291 Self::Valid(_) => None,
292 Self::Invalid(errors) => Some(errors),
293 }
294 }
295
296 /// Extracts the value, if valid.
297 ///
298 /// Returns `Some(value)` if valid, `None` if invalid.
299 ///
300 /// # Examples
301 ///
302 /// ```
303 /// use error_rail::validation::Validation;
304 ///
305 /// let v = Validation::<&str, i32>::valid(42);
306 /// assert_eq!(v.into_value(), Some(42));
307 /// ```
308 #[inline]
309 pub fn into_value(self) -> Option<A> {
310 match self {
311 Self::Valid(value) => Some(value),
312 Self::Invalid(_) => None,
313 }
314 }
315}