├── .gitignore ├── Cargo.toml ├── README.md ├── .github └── workflows │ └── matrix.yml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "validation" 3 | version = "0.1.0" 4 | authors = ["Michael Snoyman "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # validation 2 | 3 | ![Continuous integration](https://github.com/snoyberg/validation-rs/workflows/Continuous%20integration/badge.svg) 4 | 5 | Provides a `Validation` enum. `Validation` provides a `FromIterator` implementation which is just like `Result`'s, except it will collect multiple error values instead of just the first. 6 | 7 | Performance can probably be improved, and documentation is basically nonexistent. If there's interest in making this production grade, let me know in the issue tracker! -------------------------------------------------------------------------------- /.github/workflows/matrix.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | rust: 11 | - stable 12 | - beta 13 | - nightly 14 | # - 1.31.0 # MSRV 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: ${{ matrix.rust }} 23 | override: true 24 | components: rustfmt, clippy 25 | 26 | - uses: actions-rs/cargo@v1 27 | with: 28 | command: build 29 | 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: fmt 37 | args: --all -- --check 38 | 39 | - uses: actions-rs/cargo@v1 40 | with: 41 | command: clippy 42 | args: -- -D warnings -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | #[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy, Hash)] 4 | pub enum Validation { 5 | Ok(T), 6 | Err(E), 7 | } 8 | 9 | impl Validation { 10 | pub fn into_result(self) -> Result { 11 | match self { 12 | Validation::Ok(t) => Ok(t), 13 | Validation::Err(e) => Err(e), 14 | } 15 | } 16 | } 17 | 18 | struct Phase1 { 19 | iter: I, 20 | err: Option, 21 | } 22 | 23 | impl>> Iterator for &mut Phase1 { 24 | type Item = T; 25 | 26 | fn next(&mut self) -> Option { 27 | assert!(self.err.is_none()); 28 | match self.iter.next()? { 29 | Ok(t) => Some(t), 30 | Err(e) => { 31 | self.err = Some(e); 32 | None 33 | } 34 | } 35 | } 36 | } 37 | 38 | struct Phase2 { 39 | first: Option, 40 | iter: I, 41 | } 42 | 43 | impl>> Iterator for Phase2 { 44 | type Item = E; 45 | 46 | fn next(&mut self) -> Option { 47 | match self.first.take() { 48 | Some(e) => Some(e), 49 | None => loop { 50 | match self.iter.next()? { 51 | Ok(_) => (), 52 | Err(e) => break Some(e), 53 | } 54 | }, 55 | } 56 | } 57 | } 58 | 59 | impl, E, VE: FromIterator> FromIterator> 60 | for Validation 61 | { 62 | fn from_iter>>(iter: I) -> Self { 63 | let mut phase1 = Phase1 { 64 | err: None, 65 | iter: iter.into_iter(), 66 | }; 67 | let vt = (&mut phase1).collect(); 68 | match phase1.err { 69 | None => Validation::Ok(vt), 70 | Some(e) => { 71 | let phase2 = Phase2 { 72 | first: Some(e), 73 | iter: phase1.iter, 74 | }; 75 | Validation::Err(phase2.collect()) 76 | } 77 | } 78 | } 79 | } 80 | 81 | impl, E, VE: FromIterator> FromIterator> 82 | for Validation 83 | { 84 | fn from_iter>>(iter: I) -> Self { 85 | iter.into_iter().map(|v| v.into_result()).collect() 86 | } 87 | } 88 | 89 | // FIXME impl Try for Validation 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | #[derive(Eq, PartialEq, Debug)] 94 | struct Good(i32); 95 | #[derive(Eq, PartialEq, Debug)] 96 | struct Bad(i32); 97 | use super::Validation; 98 | 99 | #[test] 100 | fn no_results_is_ok() { 101 | let input: Vec> = vec![]; 102 | let res: Validation, Vec> = input.into_iter().collect(); 103 | assert_eq!(res, Validation::Ok(vec![])); 104 | assert_eq!(res.into_result(), Ok(vec![])); 105 | } 106 | 107 | #[test] 108 | fn one_good() { 109 | let input: Vec> = vec![Ok(Good(1))]; 110 | let res: Validation, Vec> = input.into_iter().collect(); 111 | assert_eq!(res, Validation::Ok(vec![Good(1)])); 112 | } 113 | 114 | #[test] 115 | fn one_bad() { 116 | let input: Vec> = vec![Err(Bad(1))]; 117 | let res: Validation, Vec> = input.into_iter().collect(); 118 | assert_eq!(res, Validation::Err(vec![Bad(1)])); 119 | } 120 | 121 | #[test] 122 | fn one_good_one_bad() { 123 | let input: Vec> = vec![Err(Bad(1)), Ok(Good(2))]; 124 | let res: Validation, Vec> = input.into_iter().collect(); 125 | assert_eq!(res, Validation::Err(vec![Bad(1)])); 126 | } 127 | 128 | #[test] 129 | fn all_good() { 130 | let input: Vec> = (0..10).map(|x| Ok(Good(x))).collect(); 131 | let res: Validation, Vec> = input.into_iter().collect(); 132 | let expected = (0..10).map(|x| Validation::Ok(Good(x))).collect(); 133 | assert_eq!(res, expected); 134 | } 135 | 136 | #[test] 137 | fn all_bad() { 138 | let input: Vec> = (0..10).map(|x| Err(Bad(x))).collect(); 139 | let res: Validation, Vec> = input.into_iter().collect(); 140 | let expected = (0..10).map(|x| Validation::Err(Bad(x))).collect(); 141 | assert_eq!(res, expected); 142 | } 143 | } 144 | --------------------------------------------------------------------------------