nuget_client/model/
version_range.rs

1use std::str::FromStr;
2use nom::bytes::complete::tag;
3use nom::combinator::opt;
4use nom::{AsBytes, IResult};
5use nom::branch::alt;
6use nom::sequence::tuple;
7
8use crate::Error;
9
10#[derive(Clone, Eq, PartialEq)]
11pub enum RangeSpecifier {
12    Open,
13    Inclusive(String),
14    Exclusive(String),
15}
16
17#[derive(Clone, Eq, PartialEq)]
18pub struct VersionRange {
19    pub low: RangeSpecifier,
20    pub high: RangeSpecifier
21}
22
23fn version_ident(input: &[u8]) -> IResult<&[u8], &[u8]> {
24    nom::bytes::complete::take_while1(|c| (c as char).is_ascii_alphanumeric() || c == b'-' || c == b'.' || c == b'+')(input)
25}
26
27fn whitespace(input: &[u8]) -> IResult<&[u8], &[u8]> {
28    nom::bytes::complete::take_while(|c| (c as char).is_ascii_whitespace())(input)
29}
30
31fn full_specifier(input: &[u8]) -> IResult<&[u8], VersionRange> {
32    let (remain, (_, lo_tag, _, lo, _, _, _, hi, _, hi_tag, _)) = tuple((
33        whitespace,
34        alt((tag("["), tag("("))),
35        whitespace,
36        opt(version_ident),
37        whitespace,
38        tag(b","),
39        whitespace,
40        opt(version_ident),
41        whitespace,
42        alt((tag("]"), tag(")"))),
43        whitespace
44    ))(input.as_bytes())?;
45
46    let lo = match (lo_tag, lo) {
47        (_, None) => RangeSpecifier::Open,
48        (b"[", Some(v)) => RangeSpecifier::Inclusive(String::from_utf8(v.to_owned()).unwrap()),
49        (b"(", Some(v)) => RangeSpecifier::Exclusive(String::from_utf8(v.to_owned()).unwrap()),
50        _ => unreachable!()
51    };
52
53    let hi = match (hi_tag, hi) {
54        (_, None) => RangeSpecifier::Open,
55        (b"]", Some(v)) => RangeSpecifier::Inclusive(String::from_utf8(v.to_owned()).unwrap()),
56        (b")", Some(v)) => RangeSpecifier::Exclusive(String::from_utf8(v.to_owned()).unwrap()),
57        _ => unreachable!()
58    };
59
60    Ok((remain, VersionRange {
61        low: lo,
62        high: hi
63    }))
64}
65
66fn exact_match(input: &[u8]) -> IResult<&[u8], VersionRange> {
67    let (remain, (_, _, _, version, _, _, _)) = tuple((
68        whitespace,
69        tag(b"["),
70        whitespace,
71        version_ident,
72        whitespace,
73        tag(b"]"),
74        whitespace
75    ))(input)?;
76
77    Ok((remain, VersionRange {
78        low: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap()),
79        high: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap())
80    }))
81}
82
83fn min_version_bare(input: &[u8]) -> IResult<&[u8], VersionRange> {
84    let (remain, version) = version_ident(input)?;
85
86    Ok((remain, VersionRange {
87        low: RangeSpecifier::Inclusive(String::from_utf8(version.to_owned()).unwrap()),
88        high: RangeSpecifier::Open
89    }))
90}
91
92impl FromStr for VersionRange {
93    type Err = Error;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        let result = alt((full_specifier, exact_match, min_version_bare))(s.as_bytes());
97
98        match result {
99            Ok((b"", v)) => Ok(v),
100            _ => Err(Error::InvalidVersionRange(s.to_owned())),
101        }
102    }
103}
104
105impl std::fmt::Debug for VersionRange {
106    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
107        write!(f, "VersionRange({})", self)
108    }
109}
110
111impl std::fmt::Display for VersionRange {
112    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
113        match &self.low {
114            RangeSpecifier::Open => write!(f, "[")?,
115            RangeSpecifier::Inclusive(v) => write!(f, "[{} ", v)?,
116            RangeSpecifier::Exclusive(v) => write!(f, "({} ", v)?,
117        }
118
119        write!(f, ", ")?;
120
121        match &self.high {
122            RangeSpecifier::Open => write!(f, "]")?,
123            RangeSpecifier::Inclusive(v) => write!(f, "{}]", v)?,
124            RangeSpecifier::Exclusive(v) => write!(f, "{})", v)?,
125        }
126
127        Ok(())
128    }
129}
130
131#[cfg(test)]
132mod test {
133    use std::str::FromStr;
134
135    use crate::model::{RangeSpecifier, VersionRange};
136
137    #[test]
138    fn test_vecs() {
139        assert_eq!("1.0".parse::<VersionRange>().unwrap(), VersionRange {
140            low: RangeSpecifier::Inclusive("1.0".to_owned()),
141            high: RangeSpecifier::Open
142        });
143
144        assert_eq!("[1.0]".parse::<VersionRange>().unwrap(), VersionRange {
145            low: RangeSpecifier::Inclusive("1.0".to_owned()),
146            high: RangeSpecifier::Inclusive("1.0".to_owned())
147        });
148
149        assert_eq!("[1.0, )".parse::<VersionRange>().unwrap(), VersionRange {
150            low: RangeSpecifier::Inclusive("1.0".to_owned()),
151            high: RangeSpecifier::Open
152        });
153        assert_eq!("  [ 1.0  ,)  ".parse::<VersionRange>().unwrap(), VersionRange {
154            low: RangeSpecifier::Inclusive("1.0".to_owned()),
155            high: RangeSpecifier::Open
156        });
157        assert_eq!("(1.0, )".parse::<VersionRange>().unwrap(), VersionRange {
158            low: RangeSpecifier::Exclusive("1.0".to_owned()),
159            high: RangeSpecifier::Open
160        });
161        assert_eq!("(,1.0]".parse::<VersionRange>().unwrap(), VersionRange {
162            low: RangeSpecifier::Open,
163            high: RangeSpecifier::Inclusive("1.0".to_owned())
164        });
165        assert_eq!("(,1.0)".parse::<VersionRange>().unwrap(), VersionRange {
166            low: RangeSpecifier::Open,
167            high: RangeSpecifier::Exclusive("1.0".to_owned())
168        });
169        assert_eq!("[1.0,2.0]".parse::<VersionRange>().unwrap(), VersionRange {
170            low: RangeSpecifier::Inclusive("1.0".to_owned()),
171            high: RangeSpecifier::Inclusive("2.0".to_owned())
172        });
173        assert_eq!("(1.0,2.0)".parse::<VersionRange>().unwrap(), VersionRange {
174            low: RangeSpecifier::Exclusive("1.0".to_owned()),
175            high: RangeSpecifier::Exclusive("2.0".to_owned())
176        });
177        assert_eq!("[1.0,2.0)".parse::<VersionRange>().unwrap(), VersionRange {
178            low: RangeSpecifier::Inclusive("1.0".to_owned()),
179            high: RangeSpecifier::Exclusive("2.0".to_owned())
180        });
181        assert!(VersionRange::from_str("(1.0)").is_err());
182    }
183}