1use crate::input::is_sorted_and_unique;
3use anyhow::{ensure, Context, Result};
4use itertools::Itertools;
5
6fn parse_and_validate_year(s: &str, valid_years: &[u32]) -> Option<u32> {
8 let year = s.trim().parse::<u32>().ok()?;
9 if valid_years.binary_search(&year).is_ok() {
10 Some(year)
11 } else {
12 None
13 }
14}
15
16pub fn parse_year_str(s: &str, valid_years: &[u32]) -> Result<Vec<u32>> {
34 assert!(
36 is_sorted_and_unique(valid_years),
37 "`valid_years` must be sorted and unique"
38 );
39
40 let s = s.trim();
41 ensure!(!s.is_empty(), "No years provided");
42
43 if s.eq_ignore_ascii_case("all") {
44 return Ok(Vec::from_iter(valid_years.iter().copied()));
45 }
46
47 let years: Vec<_> = s
48 .split(";")
49 .map(|y| {
50 parse_and_validate_year(y, valid_years).with_context(|| format!("Invalid year: {y}"))
51 })
52 .try_collect()?;
53
54 ensure!(
55 is_sorted_and_unique(&years),
56 "Years must be in order and unique"
57 );
58
59 Ok(years)
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::fixture::assert_error;
66 use rstest::rstest;
67
68 #[rstest]
69 #[case("2020", &[2020, 2021], &[2020])]
70 #[case("all", &[2020, 2021], &[2020,2021])]
71 #[case("ALL", &[2020, 2021], &[2020,2021])]
72 #[case(" ALL ", &[2020, 2021], &[2020,2021])]
73 #[case("2020;2021", &[2020, 2021], &[2020,2021])]
74 #[case(" 2020; 2021", &[2020, 2021], &[2020,2021])] fn test_parse_year_str_valid(
76 #[case] input: &str,
77 #[case] milestone_years: &[u32],
78 #[case] expected: &[u32],
79 ) {
80 assert_eq!(parse_year_str(input, milestone_years).unwrap(), expected);
81 }
82
83 #[rstest]
84 #[case("", &[2020], "No years provided")]
85 #[case("2021", &[2020], "Invalid year: 2021")]
86 #[case("a;2020", &[2020], "Invalid year: a")]
87 #[case("2021;2020", &[2020, 2021],"Years must be in order and unique")] #[case("2021;2020;2021", &[2020, 2021],"Years must be in order and unique")] fn test_parse_year_str_invalid(
90 #[case] input: &str,
91 #[case] milestone_years: &[u32],
92 #[case] error_msg: &str,
93 ) {
94 assert_error!(parse_year_str(input, milestone_years), error_msg);
95 }
96}