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}