frunk/validated.rs
1//! Module for holding Validated logic
2//!
3//! `Validated` is a way of running a bunch of operations that can go wrong (for example,
4//! functions returning `Result<T, E>`) and, in the case of one or more things going wrong,
5//! having all the errors returned to you all at once. In the case that everything went well, you get
6//! an `HList` of all your results.
7//!
8//! # Examples
9//!
10//! ```
11//! # fn main() {
12//! use frunk::Validated;
13//! use frunk::prelude::*;
14//! use frunk_core::{HList, hlist_pat};
15//!
16//! #[derive(PartialEq, Eq, Debug)]
17//! struct Person {
18//! age: i32,
19//! name: String,
20//! }
21//!
22//! fn get_name() -> Result<String, String> {
23//! Ok("James".to_owned())
24//! }
25//!
26//! fn get_age() -> Result<i32, String> {
27//! Ok(32)
28//! }
29//!
30//! let v: Validated<HList!(String, i32), String> = get_name().into_validated() + get_age();
31//! let person = v.into_result()
32//! .map(|hlist_pat!(name, age)| {
33//! Person {
34//! name,
35//! age,
36//! }
37//! });
38//!
39//! assert_eq!(person.unwrap(),
40//! Person {
41//! name: "James".to_owned(),
42//! age: 32,
43//! });
44//! # }
45//! ```
46
47use super::hlist::*;
48
49use alloc::{vec, vec::Vec};
50use core::ops::Add;
51
52/// A Validated is either an Ok holding an HList or an Err, holding a vector
53/// of collected errors.
54#[derive(PartialEq, Debug, Eq, Clone, PartialOrd, Ord, Hash)]
55pub enum Validated<T, E>
56where
57 T: HList,
58{
59 Ok(T),
60 Err(Vec<E>),
61}
62
63impl<T, E> Validated<T, E>
64where
65 T: HList,
66{
67 /// Returns true if this validation is Ok, false otherwise
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// use frunk::Validated;
73 /// use frunk::prelude::*;
74 ///
75 /// let r1: Result<String, String> = Ok(String::from("hello"));
76 /// let v = r1.into_validated();
77 /// assert!(v.is_ok());
78 /// ```
79 pub fn is_ok(&self) -> bool {
80 matches!(*self, Validated::Ok(_))
81 }
82
83 /// Returns true if this validation is Err, false otherwise
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use frunk::prelude::*;
89 ///
90 /// let r1: Result<String, i32> = Err(32);
91 /// let v = r1.into_validated();
92 /// assert!(v.is_err());
93 /// ```
94 pub fn is_err(&self) -> bool {
95 !self.is_ok()
96 }
97
98 /// Turns this Validated into a Result.
99 ///
100 /// If this Validated is Ok, it will become a Ok, holding an HList of all the accumulated
101 /// results. Otherwise, it will become a Err with a list of all accumulated errors.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// # fn main() {
107 /// use frunk_core::hlist_pat;
108 /// use frunk::Validated;
109 /// use frunk::prelude::*;
110 ///
111 /// #[derive(PartialEq, Eq, Debug)]
112 /// struct Person {
113 /// age: i32,
114 /// name: String,
115 /// }
116 ///
117 /// fn get_name() -> Result<String, String> {
118 /// Ok("James".to_owned())
119 /// }
120 ///
121 /// fn get_age() -> Result<i32, String> {
122 /// Ok(32)
123 /// }
124 ///
125 /// let v = get_name().into_validated() + get_age();
126 /// let person = v.into_result()
127 /// .map(|hlist_pat!(name, age)| {
128 /// Person {
129 /// name,
130 /// age,
131 /// }
132 /// });
133 ///
134 /// assert_eq!(person.unwrap(),
135 /// Person {
136 /// name: "James".to_owned(),
137 /// age: 32,
138 /// });
139 /// # }
140 pub fn into_result(self) -> Result<T, Vec<E>> {
141 match self {
142 Validated::Ok(h) => Ok(h),
143 Validated::Err(errors) => Err(errors),
144 }
145 }
146}
147
148/// Trait for "lifting" a given type into a Validated
149pub trait IntoValidated<T, E> {
150 /// Consumes the current Result into a Validated so that we can begin chaining
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use frunk::prelude::*; // IntoValidated is in the prelude
156 ///
157 /// let r1: Result<String, i32> = Err(32);
158 /// let v = r1.into_validated();
159 /// assert!(v.is_err());
160 /// ```
161 fn into_validated(self) -> Validated<HCons<T, HNil>, E>;
162}
163
164impl<T, E> IntoValidated<T, E> for Result<T, E> {
165 fn into_validated(self) -> Validated<HCons<T, HNil>, E> {
166 match self {
167 Err(e) => Validated::Err(vec![e]),
168 Ok(v) => Validated::Ok(HCons {
169 head: v,
170 tail: HNil,
171 }),
172 }
173 }
174}
175
176/// Implements Add for the current Validated with a Result, returning a new Validated.
177///
178/// # Examples
179///
180/// ```
181/// # fn main() {
182/// use frunk::Validated;
183/// use frunk::prelude::*;
184/// use frunk_core::hlist;
185///
186/// let r1: Result<String, String> = Ok(String::from("hello"));
187/// let r2: Result<i32, String> = Ok(1);
188/// let v = r1.into_validated() + r2;
189/// assert_eq!(v, Validated::Ok(hlist!(String::from("hello"), 1)))
190/// # }
191/// ```
192///
193impl<T, E, T2> Add<Result<T2, E>> for Validated<T, E>
194where
195 T: HList + Add<HCons<T2, HNil>>,
196 <T as Add<HCons<T2, HNil>>>::Output: HList,
197{
198 type Output = Validated<<T as Add<HCons<T2, HNil>>>::Output, E>;
199
200 fn add(self, other: Result<T2, E>) -> Self::Output {
201 let other_as_validated = other.into_validated();
202 self + other_as_validated
203 }
204}
205
206/// Implements Add for the current Validated with another Validated, returning a new Validated.
207///
208/// # Examples
209///
210/// ```
211/// # fn main() {
212/// use frunk::Validated;
213/// use frunk::prelude::*;
214/// use frunk_core::hlist;
215///
216/// let r1: Result<String, String> = Ok(String::from("hello"));
217/// let r2: Result<i32, String> = Ok(1);
218/// let v1 = r1.into_validated();
219/// let v2 = r2.into_validated();
220/// let v3 = v1 + v2;
221/// assert_eq!(v3, Validated::Ok(hlist!(String::from("hello"), 1)))
222/// # }
223/// ```
224impl<T, E, T2> Add<Validated<T2, E>> for Validated<T, E>
225where
226 T: HList + Add<T2>,
227 T2: HList,
228 <T as Add<T2>>::Output: HList,
229{
230 type Output = Validated<<T as Add<T2>>::Output, E>;
231
232 fn add(self, other: Validated<T2, E>) -> Self::Output {
233 match (self, other) {
234 (Validated::Err(mut errs), Validated::Err(errs2)) => {
235 errs.extend(errs2);
236 Validated::Err(errs)
237 }
238 (Validated::Err(errs), _) => Validated::Err(errs),
239 (_, Validated::Err(errs)) => Validated::Err(errs),
240 (Validated::Ok(h1), Validated::Ok(h2)) => Validated::Ok(h1 + h2),
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use alloc::{borrow::ToOwned, string::String};
249 use frunk_core::{hlist, hlist_pat};
250
251 #[test]
252 fn test_adding_ok_results() {
253 let r1: Result<String, String> = Ok(String::from("hello"));
254 let r2: Result<i32, String> = Ok(1);
255 let v = r1.into_validated() + r2;
256 assert_eq!(v, Validated::Ok(hlist!(String::from("hello"), 1)))
257 }
258
259 #[test]
260 fn test_adding_validated_oks() {
261 let r1: Result<String, String> = Ok(String::from("hello"));
262 let r2: Result<i32, String> = Ok(1);
263 let r3: Result<i32, String> = Ok(3);
264 let v1 = r1.into_validated();
265 let v2 = r2.into_validated();
266 let v3 = r3.into_validated();
267 let comb = v1 + v2 + v3;
268 assert_eq!(comb, Validated::Ok(hlist!(String::from("hello"), 1, 3)))
269 }
270
271 #[test]
272 fn test_adding_err_results() {
273 let r1: Result<i16, String> = Ok(1);
274 let r2: Result<i16, String> = Err(String::from("NO!"));
275 let v1 = r1.into_validated() + r2;
276 assert!(v1.is_err());
277 assert_eq!(v1, Validated::Err(vec!["NO!".to_owned()]))
278 }
279
280 #[derive(PartialEq, Eq, Debug)]
281 struct Person {
282 age: i32,
283 name: String,
284 email: String,
285 }
286
287 #[derive(PartialEq, Eq, Debug)]
288 pub enum YahNah {
289 Yah,
290 Nah,
291 }
292
293 /// Our Errors
294 #[derive(PartialEq, Eq, Debug)]
295 pub enum Nope {
296 Name,
297 Age,
298 Email,
299 }
300
301 fn get_name(yah_nah: YahNah) -> Result<String, Nope> {
302 match yah_nah {
303 YahNah::Yah => Ok("James".to_owned()),
304 _ => Err(Nope::Name),
305 }
306 }
307
308 fn get_age(yah_nah: YahNah) -> Result<i32, Nope> {
309 match yah_nah {
310 YahNah::Yah => Ok(32),
311 _ => Err(Nope::Age),
312 }
313 }
314
315 fn get_email(yah_nah: YahNah) -> Result<String, Nope> {
316 match yah_nah {
317 YahNah::Yah => Ok("[email protected]".to_owned()),
318 _ => Err(Nope::Email),
319 }
320 }
321
322 #[test]
323 fn test_to_result_ok() {
324 let v =
325 get_name(YahNah::Yah).into_validated() + get_age(YahNah::Yah) + get_email(YahNah::Yah);
326 let person =
327 v.into_result()
328 .map(|hlist_pat!(name, age, email)| Person { name, age, email });
329
330 assert_eq!(
331 person.unwrap(),
332 Person {
333 name: "James".to_owned(),
334 age: 32,
335 email: "[email protected]".to_owned(),
336 }
337 );
338 }
339
340 #[test]
341 fn test_to_result_all_faulty() {
342 let v =
343 get_name(YahNah::Nah).into_validated() + get_age(YahNah::Nah) + get_email(YahNah::Nah);
344 let person = v.into_result().map(|_| unimplemented!());
345
346 assert_eq!(
347 person.unwrap_err(),
348 vec![Nope::Name, Nope::Age, Nope::Email]
349 );
350 }
351
352 #[test]
353 fn test_to_result_one_faulty() {
354 let v =
355 get_name(YahNah::Nah).into_validated() + get_age(YahNah::Yah) + get_email(YahNah::Nah);
356 let person = v.into_result().map(|_| unimplemented!());
357
358 assert_eq!(person.unwrap_err(), vec![Nope::Name, Nope::Email]);
359 }
360}