1use anyhow::{Context, Result, ensure};
3use std::error::Error;
4use std::fmt::Display;
5use std::ops::RangeInclusive;
6use std::str::FromStr;
7
8pub fn parse_range<T>(s: &str, limits: RangeInclusive<T>) -> Result<RangeInclusive<T>>
19where
20 T: FromStr + Copy + PartialOrd + Display,
21 <T as FromStr>::Err: Error + Sync + Send + 'static,
22{
23 let (start, end) = s.split_once("..").context(
24 "Range must be in the form [start]..[end] (where [start] and [end] can be empty)",
25 )?;
26 parse_range_parts(start, end, limits.clone(), *limits.start(), *limits.end())
27}
28
29pub fn parse_range_parts<T>(
41 start: &str,
42 end: &str,
43 limits: RangeInclusive<T>,
44 default_lower: T,
45 default_upper: T,
46) -> Result<RangeInclusive<T>>
47where
48 T: FromStr + Copy + PartialOrd + Display,
49 <T as FromStr>::Err: Error + Sync + Send + 'static,
50{
51 assert!(
52 limits.start() <= limits.end(),
53 "Start of limits must be before end"
54 );
55 assert!(
56 default_lower <= default_upper,
57 "default_lower must be less than default_upper"
58 );
59
60 let start = start.trim();
61 let end = end.trim();
62 ensure!(
63 !start.is_empty() || !end.is_empty(),
64 "Start and end of range cannot both be omitted"
65 );
66
67 let value1 = if start.is_empty() {
68 default_lower
69 } else {
70 start.parse()?
71 };
72 let value2 = if end.is_empty() {
73 default_upper
74 } else {
75 end.parse()?
76 };
77
78 ensure!(
79 value1 <= value2,
80 "Start value must be less than or equal to end value"
81 );
82 ensure!(
83 value1 >= *limits.start(),
84 "Start value must be >= {}",
85 limits.start()
86 );
87 ensure!(
88 value2 <= *limits.end(),
89 "End value must be <= {}",
90 limits.end()
91 );
92
93 Ok(value1..=value2)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use rstest::rstest;
100
101 #[rstest]
102 #[case("1..2", 1..=2)]
103 #[case("1..1", 1..=1)]
104 #[case("..2", 0..=2)]
105 #[case("1..", 1..=100)]
106 fn parse_range_ok(#[case] input: &str, #[case] expected: RangeInclusive<i32>) {
107 assert_eq!(parse_range(input, 0..=100).unwrap(), expected);
108 }
109
110 #[rstest]
111 #[case("..")] #[case("-1..10")] #[case("0..101")] #[case("2..1")] fn parse_range_error(#[case] input: &str) {
116 parse_range(input, 0..=100).unwrap_err();
117 }
118}