1use std::{collections::HashSet, hash::Hash, str::FromStr};
10
11use anyhow::{Error, Result};
12use num_traits::{Bounded, FromPrimitive, NumOps, ToPrimitive, Zero};
13use regex::Regex;
14
15use crate::utils::until_err;
16
17#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub enum ConstrainedValue<T>
20where
21 T: Constrainable,
22{
23 All,
25 Range(T, T),
27 Repetition {
33 start: T,
35 end: Option<T>,
37 rep: u8,
39 },
40 Specific(Vec<T>),
42}
43
44impl<T> ConstrainedValueMatcher<T> for ConstrainedValue<T>
45where
46 T: Constrainable + NumOps + Zero + Copy + FromPrimitive,
47{
48 fn matches(&self, value: T) -> bool {
49 match self {
50 Self::All => true,
51 Self::Range(first, second) => value >= *first && value <= *second,
52 Self::Repetition { start, end, rep } => {
53 if value < *start {
54 return false;
55 }
56 if let Some(end_value) = end
57 && value > *end_value
58 {
59 return false;
60 }
61 let diff = value - *start;
62 diff % T::from_u8(*rep).unwrap() == T::zero()
63 }
64 Self::Specific(values) => values.contains(&value),
65 }
66 }
67}
68
69pub trait Constrainable:
71 Bounded + Copy + Eq + FromStr + Hash + Ord + PartialEq + PartialOrd + ToPrimitive
72{
73}
74
75impl<T> Constrainable for T where
76 T: Bounded + Copy + Eq + FromStr + Hash + Ord + PartialEq + PartialOrd + ToPrimitive
77{
78}
79
80pub trait ConstrainedValueParser<'a, T>:
82 FromStr<Err = Error> + TryFrom<&'a str, Error = Error>
83where
84 T: Constrainable,
85{
86 fn invalid(s: &str) -> Error;
88
89 fn repetition_regex() -> Regex;
91
92 fn range_regex() -> Regex;
94
95 #[must_use]
97 fn allow_rand() -> bool {
98 false
99 }
100
101 fn all() -> Self;
103
104 fn rand() -> Self;
106
107 fn rep(start: T, end: Option<T>, rep: u8) -> Self;
109
110 fn range(first: T, second: T) -> Self;
112
113 fn specific(values: Vec<T>) -> Self;
115
116 fn parse(s: &str) -> Result<Self> {
121 if s.is_empty() {
122 Err(Self::invalid(s))
123 } else if s == "*" {
124 Ok(Self::all())
125 } else if s == "R" && Self::allow_rand() {
126 Ok(Self::rand())
127 } else if Self::repetition_regex().is_match(s) {
128 Self::parse_repetition(s)
129 } else if Self::range_regex().is_match(s) {
130 Self::parse_range(s)
131 } else {
132 Self::parse_specific(s)
133 }
134 }
135
136 fn parse_range(s: &str) -> Result<Self> {
141 if let Some(caps) = Self::range_regex().captures(s) {
142 let first = caps[1].parse::<T>().map_err(|_| Self::invalid(s))?;
143 let second = caps[2].parse::<T>().map_err(|_| Self::invalid(s))?;
144 if (first < T::min_value() || first > T::max_value())
145 || (second < T::min_value() || second > T::max_value())
146 || (first > second)
147 {
148 Err(Self::invalid(s))
149 } else {
150 Ok(Self::range(first, second))
151 }
152 } else {
153 Err(Self::invalid(s))
154 }
155 }
156
157 fn parse_repetition(s: &str) -> Result<Self> {
162 if let Some(caps) = Self::repetition_regex().captures(s) {
163 let start = caps[1].parse::<T>().map_err(|_| Self::invalid(s))?;
164 let end = if let Some(end_match) = caps.get(3) {
165 Some(
166 end_match
167 .as_str()
168 .parse::<T>()
169 .map_err(|_| Self::invalid(s))?,
170 )
171 } else {
172 None
173 };
174 let rep = caps[4].parse::<u8>().map_err(|_| Self::invalid(s))?;
175 if rep == 0 || start < T::min_value() || start > T::max_value() {
176 Err(Self::invalid(s))
177 } else if let Some(end_val) = end {
178 if end_val < start || end_val < T::min_value() || end_val > T::max_value() {
179 Err(Self::invalid(s))
180 } else {
181 Ok(Self::rep(start, Some(end_val), rep))
182 }
183 } else {
184 Ok(Self::rep(start, None, rep))
185 }
186 } else {
187 Err(Self::invalid(s))
188 }
189 }
190
191 fn parse_specific(s: &str) -> Result<Self> {
196 let mut err = Ok(());
197 let mut values: Vec<T> = s
198 .split(',')
199 .map(|part| part.parse::<T>().map_err(|_| Self::invalid(s)))
200 .scan(&mut err, until_err)
201 .collect::<HashSet<_>>()
202 .into_iter()
203 .collect();
204 err?;
205 values.sort_unstable();
206 Ok(Self::specific(values))
207 }
208}
209
210pub trait ConstrainedValueMatcher<T>
212where
213 T: Constrainable,
214{
215 fn matches(&self, value: T) -> bool;
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 type Test = ConstrainedValue<u8>;
224
225 impl TryFrom<&str> for Test {
226 type Error = Error;
227
228 #[cfg_attr(coverage_nightly, coverage(off))]
229 fn try_from(s: &str) -> Result<Self> {
230 Test::parse(s)
231 }
232 }
233
234 impl FromStr for Test {
235 type Err = Error;
236
237 #[cfg_attr(coverage_nightly, coverage(off))]
238 fn from_str(s: &str) -> Result<Self> {
239 Test::try_from(s)
240 }
241 }
242
243 #[cfg_attr(coverage_nightly, coverage(off))]
244 impl ConstrainedValueParser<'_, u8> for Test {
245 fn invalid(s: &str) -> Error {
246 Error::msg(format!("invalid constrained value: {s}"))
247 }
248
249 fn all() -> Self {
250 Test::All
251 }
252
253 fn rand() -> Self {
254 Test::All
255 }
256
257 fn repetition_regex() -> Regex {
258 Regex::new(r"^(\d{1,3})(-(\d{1,3}))?/(\d{1,3})$").unwrap()
259 }
260
261 fn range_regex() -> Regex {
262 Regex::new(r"^(\d{1,3})-(\d{1,3})$").unwrap()
263 }
264
265 fn rep(start: u8, end: Option<u8>, rep: u8) -> Self {
266 Test::Repetition { start, end, rep }
267 }
268
269 fn range(first: u8, second: u8) -> Self {
270 Test::Range(first, second)
271 }
272
273 fn specific(values: Vec<u8>) -> Self {
274 Test::Specific(values)
275 }
276 }
277
278 #[test]
279 fn parse_range_errors() {
280 assert!(Test::parse_range("").is_err());
281 }
282
283 #[test]
284 fn parse_repetition_errors() {
285 assert!(Test::parse_repetition("").is_err());
286 }
287}