1use std::{error, fmt, str::FromStr};
2
3use globset::{Error as GlobError, Glob, GlobBuilder, GlobMatcher};
4
5use crate::version::{self, PartialVersion, VersionError};
6
7use super::Runtime;
8
9#[derive(Debug)]
10pub enum ConstraintError {
11 GlobError(GlobError),
12 VersionError(version::VersionError),
13}
14
15impl fmt::Display for ConstraintError {
16 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
17 use ConstraintError::*;
18 match self {
19 GlobError(ref error) => write!(fmt, "could not parse constraint: {error}",),
20 VersionError(ref error) => write!(
21 fmt,
22 "could not parse version constraint {text:?}: {error}",
23 text = error.text().unwrap_or("<unknown>")
24 ),
25 }
26 }
27}
28
29impl error::Error for ConstraintError {
30 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
31 match *self {
32 ConstraintError::GlobError(ref error) => Some(error),
33 ConstraintError::VersionError(ref error) => Some(error),
34 }
35 }
36}
37
38impl From<GlobError> for ConstraintError {
39 fn from(error: GlobError) -> ConstraintError {
40 ConstraintError::GlobError(error)
41 }
42}
43
44impl From<version::VersionError> for ConstraintError {
45 fn from(error: version::VersionError) -> ConstraintError {
46 ConstraintError::VersionError(error)
47 }
48}
49
50#[derive(Clone, Debug)]
52pub enum Constraint {
53 BinDir(GlobMatcher),
55 Version(PartialVersion),
57 Either(Box<Constraint>, Box<Constraint>),
59 Both(Box<Constraint>, Box<Constraint>),
61 Not(Box<Constraint>),
63 Anything,
65 Nothing,
67}
68
69impl Constraint {
70 pub fn path(pattern: &str) -> Result<Self, GlobError> {
81 Ok(Self::BinDir(
82 GlobBuilder::new(pattern)
83 .literal_separator(true)
84 .empty_alternates(true)
85 .build()?
86 .compile_matcher(),
87 ))
88 }
89
90 pub fn glob(glob: &Glob) -> Self {
92 Self::BinDir(glob.compile_matcher())
93 }
94
95 pub fn version(version: &str) -> Result<Self, VersionError> {
97 Ok(Self::Version(version.parse()?))
98 }
99
100 pub fn any<C: IntoIterator<Item = Constraint>>(constraints: C) -> Self {
104 constraints
105 .into_iter()
106 .reduce(|a, b| a | b)
107 .unwrap_or(Self::Nothing)
108 }
109
110 pub fn all<C: IntoIterator<Item = Constraint>>(constraints: C) -> Self {
114 constraints
115 .into_iter()
116 .reduce(|a, b| a & b)
117 .unwrap_or(Self::Anything)
118 }
119
120 pub fn matches(&self, runtime: &Runtime) -> bool {
122 match self {
123 Self::BinDir(matcher) => matcher.is_match(&runtime.bindir),
124 Self::Version(version) => version.compatible(runtime.version),
125 Self::Either(ca, cb) => ca.matches(runtime) || cb.matches(runtime),
126 Self::Both(ca, cb) => ca.matches(runtime) && cb.matches(runtime),
127 Self::Not(constraint) => !constraint.matches(runtime),
128 Self::Anything => true,
129 Self::Nothing => false,
130 }
131 }
132}
133
134impl std::ops::Not for Constraint {
135 type Output = Self;
136
137 fn not(self) -> Self::Output {
139 match self {
140 Self::Anything => Self::Nothing,
141 Self::Nothing => Self::Anything,
142 Self::Not(constraint) => *constraint,
143 _ => Self::Not(Box::new(self)),
144 }
145 }
146}
147
148impl std::ops::BitOr for Constraint {
149 type Output = Self;
150
151 fn bitor(self, rhs: Self) -> Self::Output {
153 match (self, rhs) {
154 (Self::Anything, _) | (_, Self::Anything) => Self::Anything,
155 (Self::Nothing, c) | (c, Self::Nothing) => c,
156 (ca, cb) => Self::Either(Box::new(ca), Box::new(cb)),
157 }
158 }
159}
160
161impl std::ops::BitAnd for Constraint {
162 type Output = Self;
163
164 fn bitand(self, rhs: Self) -> Self::Output {
166 match (self, rhs) {
167 (Self::Anything, c) | (c, Self::Anything) => c,
168 (Self::Nothing, _) | (_, Self::Nothing) => Self::Nothing,
169 (ca, cb) => Self::Both(Box::new(ca), Box::new(cb)),
170 }
171 }
172}
173
174impl From<PartialVersion> for Constraint {
175 fn from(version: PartialVersion) -> Self {
177 Self::Version(version)
178 }
179}
180
181impl FromStr for Constraint {
182 type Err = ConstraintError;
183
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
189 if s.contains(std::path::MAIN_SEPARATOR) {
190 Ok(Self::path(s)?)
191 } else {
192 Ok(Self::version(s)?)
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::Constraint;
200 use super::PartialVersion;
201
202 const CONSTRAINT: Constraint = Constraint::Version(PartialVersion::Post10m(13));
204
205 #[test]
206 fn test_not() {
207 let c1 = Constraint::Version(PartialVersion::Post10m(13));
208 assert!(matches!(c1, Constraint::Version(_)));
209 let c2 = !c1;
210 assert!(matches!(c2, Constraint::Not(_)));
211 let c3 = !c2;
212 assert!(matches!(c3, Constraint::Version(_)));
213 }
214
215 #[test]
216 fn test_not_anything_and_nothing() {
217 let c1 = Constraint::Anything;
218 let c2 = !c1;
219 assert!(matches!(c2, Constraint::Nothing));
220 let c3 = !c2;
221 assert!(matches!(c3, Constraint::Anything));
222 }
223
224 #[test]
225 fn test_or() {
226 assert!(matches!(
227 Constraint::Anything | CONSTRAINT.clone(),
228 Constraint::Anything
229 ));
230 assert!(matches!(
231 CONSTRAINT.clone() | Constraint::Anything,
232 Constraint::Anything
233 ));
234 assert!(matches!(
235 Constraint::Nothing | CONSTRAINT.clone(),
236 Constraint::Version(_)
237 ));
238 assert!(matches!(
239 CONSTRAINT.clone() | Constraint::Nothing,
240 Constraint::Version(_)
241 ));
242 }
243
244 #[test]
245 fn test_or_anything_and_nothing() {
246 assert!(matches!(
247 Constraint::Anything | Constraint::Anything,
248 Constraint::Anything
249 ));
250 assert!(matches!(
251 Constraint::Nothing | Constraint::Anything,
252 Constraint::Anything
253 ));
254 assert!(matches!(
255 Constraint::Anything | Constraint::Nothing,
256 Constraint::Anything
257 ));
258 }
259
260 #[test]
261 fn test_and() {
262 assert!(matches!(
263 Constraint::Anything & CONSTRAINT.clone(),
264 Constraint::Version(_)
265 ));
266 assert!(matches!(
267 CONSTRAINT.clone() & Constraint::Anything,
268 Constraint::Version(_)
269 ));
270 assert!(matches!(
271 Constraint::Nothing & CONSTRAINT.clone(),
272 Constraint::Nothing
273 ));
274 assert!(matches!(
275 CONSTRAINT.clone() & Constraint::Nothing,
276 Constraint::Nothing
277 ));
278 }
279
280 #[test]
281 fn test_and_anything_and_nothing() {
282 assert!(matches!(
283 Constraint::Anything & Constraint::Anything,
284 Constraint::Anything
285 ));
286 assert!(matches!(
287 Constraint::Nothing & Constraint::Anything,
288 Constraint::Nothing
289 ));
290 assert!(matches!(
291 Constraint::Anything & Constraint::Nothing,
292 Constraint::Nothing
293 ));
294 }
295
296 #[test]
297 fn test_any() {
298 assert!(matches!(Constraint::any([]), Constraint::Nothing));
299 assert!(matches!(
300 Constraint::any([
301 Constraint::Anything,
302 Constraint::Nothing,
303 Constraint::Nothing
304 ]),
305 Constraint::Anything
306 ));
307 assert!(matches!(
308 Constraint::any([Constraint::Nothing, CONSTRAINT.clone(), Constraint::Nothing]),
309 Constraint::Version(_)
310 ));
311 assert!(matches!(
312 Constraint::any([
313 Constraint::Anything,
314 CONSTRAINT.clone(),
315 Constraint::Nothing
316 ]),
317 Constraint::Anything
318 ));
319 assert!(matches!(
320 Constraint::any([CONSTRAINT.clone(), CONSTRAINT.clone()]),
321 Constraint::Either(ca, cb)
322 if matches!(*ca, Constraint::Version(_))
323 && matches!(*cb, Constraint::Version(_))
324 ));
325 }
326
327 #[test]
328 fn test_all() {
329 assert!(matches!(Constraint::all([]), Constraint::Anything));
330 assert!(matches!(
331 Constraint::all([
332 Constraint::Anything,
333 Constraint::Anything,
334 Constraint::Anything
335 ]),
336 Constraint::Anything
337 ));
338 assert!(matches!(
339 Constraint::all([
340 Constraint::Anything,
341 CONSTRAINT.clone(),
342 Constraint::Anything,
343 ]),
344 Constraint::Version(_),
345 ));
346 assert!(matches!(
347 Constraint::all([
348 Constraint::Anything,
349 CONSTRAINT.clone(),
350 Constraint::Nothing,
351 ]),
352 Constraint::Nothing,
353 ));
354 assert!(matches!(
355 Constraint::all([CONSTRAINT.clone(), CONSTRAINT.clone()]),
356 Constraint::Both(ca, cb)
357 if matches!(*ca, Constraint::Version(_))
358 && matches!(*cb, Constraint::Version(_))
359 ));
360 }
361}