1use std::{cmp, fmt};
39
40pub use error::Error;
41use error::Result;
42
43mod error;
44
45#[derive(Clone, PartialEq, Eq, Debug)]
46pub struct Version {
47 pub numeric_identifiers: NumericIdentifiers,
49 pub suffix: Option<String>,
51}
52
53impl Version {
54 pub fn parse(text: &str) -> Result<Self> {
57 let vec: Vec<&str> = text.splitn(2, '-').collect();
58
59 if vec == [""] {
60 return Err(Error::NoVersion);
61 }
62
63 let numeric_identifiers = NumericIdentifiers::parse(vec.first().ok_or(Error::NoVersion)?)?;
64
65 let suffix = vec.get(1).map(|pr| pr.to_string());
66
67 Ok(Self {
68 numeric_identifiers,
69 suffix,
70 })
71 }
72
73 pub fn matches(&self, vr: &VersionRequirement) -> bool {
75 vr.comparators.iter().all(|c| self.matches_comparators(c))
76 }
77
78 fn matches_comparators(&self, cmp: &Comparator) -> bool {
79 let useful_len = cmp.version.numeric_identifiers.0.len();
80 let useful_lhs = &self.numeric_identifiers.0[..useful_len];
81 let useful_rhs = cmp.version.numeric_identifiers.0.as_slice();
82
83 match cmp.operator.unwrap_or(Operator::Exact) {
84 Operator::Exact => self == &cmp.version,
85 Operator::Different => self != &cmp.version,
86 Operator::Greater => useful_lhs > useful_rhs,
87 Operator::GreaterEq => useful_lhs >= useful_rhs,
88 Operator::Less => useful_lhs < useful_rhs,
89 Operator::LessEq => useful_lhs <= useful_rhs,
90 Operator::RightMost => {
91 let prefix_len = useful_rhs.len() - 1;
92 let prefix_lhs = &useful_lhs[..prefix_len];
93 let prefix_rhs = &useful_rhs[..prefix_len];
94
95 (useful_lhs >= useful_rhs) && (prefix_lhs == prefix_rhs)
96 }
97 }
98 }
99}
100
101impl cmp::PartialOrd for Version {
102 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
103 self.numeric_identifiers
104 .partial_cmp(&other.numeric_identifiers)
105 }
106}
107
108impl cmp::Ord for Version {
109 fn cmp(&self, other: &Self) -> cmp::Ordering {
110 self.numeric_identifiers.cmp(&other.numeric_identifiers)
111 }
112}
113
114impl fmt::Display for Version {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 let pr = match &self.suffix {
117 None => "".to_string(),
118 Some(suffix) => format!("-{}", suffix),
119 };
120 write!(f, "{}{pr}", self.numeric_identifiers)
121 }
122}
123
124#[derive(Clone, PartialEq, Eq, Debug)]
126pub struct VersionRequirement {
127 pub comparators: Vec<Comparator>,
128}
129
130impl VersionRequirement {
131 pub fn parse(text: &str) -> Result<Self> {
134 let vec: Vec<&str> = text.split(',').map(|s| s.trim()).collect();
135 if vec == [""] {
136 return Err(Error::NoVersionRequirement);
137 }
138
139 let comparators: Vec<Comparator> = vec
140 .iter()
141 .map(|comp| Comparator::parse(comp))
142 .collect::<Result<Vec<Comparator>>>()?;
143
144 if comparators.len() > 1
145 && comparators
146 .iter()
147 .any(|c| c.operator == Some(Operator::Exact) || c.operator.is_none())
148 {
149 return Err(Error::NotAllowedOperatorWithMultipleComparators(
150 text.to_string(),
151 ));
152 }
153
154 Ok(Self { comparators })
155 }
156
157 pub fn is_without_operator(&self) -> bool {
159 match &self.comparators[..] {
160 [item] => item.operator.is_none(),
161 _ => false,
162 }
163 }
164}
165
166#[derive(Clone, PartialEq, Eq, Debug)]
167pub struct Comparator {
168 pub operator: Option<Operator>,
170 pub version: Version,
171}
172
173impl Comparator {
174 fn parse(text: &str) -> Result<Self> {
175 let Some((operator, version)) = Comparator::split_and_parse_operator(text) else {
176 return Err(Error::InvalidOperator(text.to_string()));
177 };
178 let version = Version::parse(version)?;
179
180 match operator {
181 Some(op)
182 if version.suffix.is_some()
183 && op != Operator::Exact
184 && op != Operator::Different =>
185 {
186 return Err(Error::NotAllowedOperatorWithSuffix(op));
187 }
188 _ => {}
189 }
190
191 Ok(Self { operator, version })
192 }
193
194 #[allow(clippy::manual_map)]
195 fn split_and_parse_operator(text: &str) -> Option<(Option<Operator>, &str)> {
196 if let Some(rest) = text.strip_prefix("<=") {
197 Some((Some(Operator::LessEq), rest.trim_start()))
198 } else if let Some(rest) = text.strip_prefix(">=") {
199 Some((Some(Operator::GreaterEq), rest.trim_start()))
200 } else if let Some(rest) = text.strip_prefix("!=") {
201 Some((Some(Operator::Different), rest.trim_start()))
202 } else if let Some(rest) = text.strip_prefix("~>") {
203 Some((Some(Operator::RightMost), rest.trim_start()))
204 } else if let Some(rest) = text.strip_prefix('<') {
205 Some((Some(Operator::Less), rest.trim_start()))
206 } else if let Some(rest) = text.strip_prefix('>') {
207 Some((Some(Operator::Greater), rest.trim_start()))
208 } else if let Some(rest) = text.strip_prefix('=') {
209 Some((Some(Operator::Exact), rest.trim_start()))
210 } else if let Some(first) = text.trim_start().chars().next() {
211 if first.is_ascii_digit() {
212 Some((None, text.trim_start()))
213 } else {
214 None
215 }
216 } else {
217 None
218 }
219 }
220}
221
222#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
223pub struct NumericIdentifiers(Vec<u32>);
224
225impl NumericIdentifiers {
226 pub fn new(vec: Vec<u32>) -> NumericIdentifiers {
227 NumericIdentifiers(vec)
228 }
229
230 pub fn parse(text: &str) -> Result<Self> {
231 let nums = text
232 .split('.')
233 .map(|ni| {
234 ni.parse::<u32>()
235 .map_err(|err| Error::ImpossibleNumericIdentifierParsing {
236 err,
237 text: text.into(),
238 ni: ni.into(),
239 })
240 })
241 .collect::<Result<Vec<_>>>()?;
242
243 Ok(Self(nums))
244 }
245}
246
247impl fmt::Display for NumericIdentifiers {
248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249 self.0
250 .iter()
251 .map(|integer| integer.to_string())
252 .collect::<Vec<String>>()
253 .join(".")
254 .fmt(f)
255 }
256}
257
258#[derive(Copy, Clone, PartialEq, Eq, Debug)]
259pub enum Operator {
260 Exact,
262 Different,
264 Greater,
266 GreaterEq,
268 Less,
270 LessEq,
272 RightMost,
274}
275
276impl fmt::Display for Operator {
277 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
278 fmt::Debug::fmt(self, f)
279 }
280}