use std::ops::RangeInclusive;
use ibc_relayer_types::core::ics04_channel::packet::Sequence;
use thiserror::Error;
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum Error {
#[error("Invalid sequence number: {0}")]
InvalidSequenceNumber(String),
#[error("Invalid range: {0}")]
InvalidRange(String),
}
pub fn parse_seq_ranges(s: &str) -> Result<Vec<RangeInclusive<Sequence>>, Error> {
s.split(',').map(parse_seq_range).collect()
}
pub fn parse_seq_range(s: &str) -> Result<RangeInclusive<Sequence>, Error> {
if s.contains("..") {
parse_range(s)
} else {
parse_single(s)
}
}
fn parse_int(s: &str) -> Result<Sequence, Error> {
s.parse::<Sequence>()
.map_err(|_| Error::InvalidSequenceNumber(s.to_string()))
}
fn parse_single(s: &str) -> Result<RangeInclusive<Sequence>, Error> {
parse_int(s).map(|num| num..=num)
}
fn parse_range(s: &str) -> Result<RangeInclusive<Sequence>, Error> {
match s.split_once("..") {
Some(("", "")) => Ok(Sequence::MIN..=Sequence::MAX),
Some(("", end)) => {
let end = parse_int(end)?;
Ok(Sequence::MIN..=end)
}
Some((start, "")) => {
let start = parse_int(start)?;
Ok(start..=Sequence::MAX)
}
Some((start, end)) => {
let start = parse_int(start)?;
let end = parse_int(end)?;
Ok(start..=end)
}
None => Err(Error::InvalidRange(s.to_string())),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(range: RangeInclusive<u64>) -> RangeInclusive<Sequence> {
Sequence::from(*range.start())..=Sequence::from(*range.end())
}
#[test]
fn parse_seq_ranges_works() {
let tests = [
("1", vec![r(1..=1)]),
("1,2", vec![r(1..=1), r(2..=2)]),
("1,2,3", vec![r(1..=1), r(2..=2), r(3..=3)]),
("1..3", vec![r(1..=3)]),
("..3", vec![r(u64::MIN..=3)]),
("3..", vec![r(3..=u64::MAX)]),
("..", vec![r(u64::MIN..=u64::MAX)]),
("1..3,4", vec![r(1..=3), r(4..=4)]),
("1,2..4", vec![r(1..=1), r(2..=4)]),
("1..3,4..6", vec![r(1..=3), r(4..=6)]),
(
"..3,4..,..",
vec![r(u64::MIN..=3), r(4..=u64::MAX), r(u64::MIN..=u64::MAX)],
),
(
"1..,..6,7..7",
vec![r(1..=u64::MAX), r(u64::MIN..=6), r(7..=7)],
),
];
for (input, expected) in tests {
let actual = parse_seq_ranges(input).unwrap();
assert_eq!(actual, expected);
}
let fails = ["1-1", "1.1", "-1", "1..2..3", "1..-2", "-1.22"];
for fail in fails {
assert!(parse_seq_ranges(fail).is_err());
}
}
}