use std::{fmt, str::FromStr};
use regex::Regex;
#[derive(Debug)]
pub struct NumericRangeError;
impl fmt::Display for NumericRangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NumericRangeError")
}
}
impl std::error::Error for NumericRangeError {}
#[derive(Debug, Clone, PartialEq)]
pub enum NumericRange {
None,
Index(u32),
Range(u32, u32),
MultipleRanges(Vec<NumericRange>),
}
impl NumericRange {
pub fn has_range(&self) -> bool {
*self != NumericRange::None
}
}
#[test]
fn valid_numeric_ranges() {
let valid_ranges = vec![
("", NumericRange::None, ""),
("0", NumericRange::Index(0), "0"),
("0000", NumericRange::Index(0), "0"),
("1", NumericRange::Index(1), "1"),
("0123456789", NumericRange::Index(123456789), "123456789"),
("4294967295", NumericRange::Index(4294967295), "4294967295"),
("1:2", NumericRange::Range(1, 2), "1:2"),
("2:3", NumericRange::Range(2, 3), "2:3"),
(
"0:1,0:2,0:3,0:4,0:5",
NumericRange::MultipleRanges(vec![
NumericRange::Range(0, 1),
NumericRange::Range(0, 2),
NumericRange::Range(0, 3),
NumericRange::Range(0, 4),
NumericRange::Range(0, 5),
]),
"0:1,0:2,0:3,0:4,0:5",
),
(
"0:1,2,3,0:4,5,6,7,8,0:9",
NumericRange::MultipleRanges(vec![
NumericRange::Range(0, 1),
NumericRange::Index(2),
NumericRange::Index(3),
NumericRange::Range(0, 4),
NumericRange::Index(5),
NumericRange::Index(6),
NumericRange::Index(7),
NumericRange::Index(8),
NumericRange::Range(0, 9),
]),
"0:1,2,3,0:4,5,6,7,8,0:9",
),
];
for vr in valid_ranges {
let range = vr.0.parse::<NumericRange>();
if range.is_err() {
println!("Range {} is in error when it should be ok", vr.0);
}
assert!(range.is_ok());
assert_eq!(range.unwrap(), vr.1);
assert_eq!(vr.2, &vr.1.as_string());
}
}
#[test]
fn invalid_numeric_ranges() {
let invalid_ranges = vec![
" ",
" 1",
"1 ",
":",
":1",
"1:1",
"2:1",
"0:1,2,3,4:4",
"1:",
"1:1:2",
",",
":,",
",:",
",1",
"1,",
"1,2,",
"1,,2",
"01234567890",
"0,1,2,3,4,5,6,7,8,9,10",
"4294967296",
"0:4294967296",
"4294967296:0",
];
for vr in invalid_ranges {
println!("vr = {}", vr);
let range = vr.parse::<NumericRange>();
if range.is_ok() {
println!("Range {} is ok when it should be in error", vr);
}
assert!(range.is_err());
}
}
const MAX_INDICES: usize = 10;
impl FromStr for NumericRange {
type Err = NumericRangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Ok(NumericRange::None)
} else {
let parts: Vec<_> = s.split(',').collect();
match parts.len() {
1 => Self::parse_range(parts[0]),
2..=MAX_INDICES => {
let mut ranges = Vec::with_capacity(parts.len());
for p in &parts {
if let Ok(range) = Self::parse_range(p) {
ranges.push(range);
} else {
return Err(NumericRangeError);
}
}
Ok(NumericRange::MultipleRanges(ranges))
}
_ => Err(NumericRangeError),
}
}
}
}
impl NumericRange {
pub fn new<T>(s: T) -> Result<Self, NumericRangeError>
where
T: Into<String>,
{
Self::from_str(s.into().as_ref())
}
pub fn as_string(&self) -> String {
match self {
NumericRange::None => String::new(),
NumericRange::Index(idx) => {
format!("{}", idx)
}
NumericRange::Range(min, max) => {
format!("{}:{}", min, max)
}
NumericRange::MultipleRanges(ref ranges) => {
let ranges: Vec<String> = ranges.iter().map(|r| r.as_string()).collect();
ranges.join(",")
}
}
}
fn parse_range(s: &str) -> Result<NumericRange, NumericRangeError> {
if s.is_empty() {
Err(NumericRangeError)
} else {
lazy_static! {
static ref RE: Regex =
Regex::new("^(?P<min>[0-9]{1,10})(:(?P<max>[0-9]{1,10}))?$").unwrap();
}
if let Some(captures) = RE.captures(s) {
let min = captures.name("min");
let max = captures.name("max");
match (min, max) {
(None, None) | (None, Some(_)) => Err(NumericRangeError),
(Some(min), None) => min
.as_str()
.parse::<u32>()
.map(NumericRange::Index)
.map_err(|_| NumericRangeError),
(Some(min), Some(max)) => {
if let Ok(min) = min.as_str().parse::<u64>() {
if let Ok(max) = max.as_str().parse::<u64>() {
if min >= max || max > u32::MAX as u64 {
Err(NumericRangeError)
} else {
Ok(NumericRange::Range(min as u32, max as u32))
}
} else {
Err(NumericRangeError)
}
} else {
Err(NumericRangeError)
}
}
}
} else {
Err(NumericRangeError)
}
}
}
pub fn is_valid(&self) -> bool {
match self {
NumericRange::None => true,
NumericRange::Index(_) => true,
NumericRange::Range(min, max) => min < max,
NumericRange::MultipleRanges(ref ranges) => {
let found_invalid = ranges.iter().any(|r| {
match r {
NumericRange::MultipleRanges(_) => true,
r => !r.is_valid(),
}
});
!found_invalid
}
}
}
}