1use super::semver_eval_ext;
2use semver::{Comparator, Op, Version, VersionReq};
3use std::fmt::{self, Display};
4
5pub trait VersionExt {
6 fn is_prerelease(&self) -> bool;
7
8 fn to_req(&self, op: Op) -> VersionReq;
9
10 fn to_exact_req(&self) -> VersionReq {
11 self.to_req(Op::Exact)
12 }
13
14 fn to_caret_req(&self) -> VersionReq {
15 self.to_req(Op::Caret)
16 }
17}
18
19impl VersionExt for Version {
20 fn is_prerelease(&self) -> bool {
21 !self.pre.is_empty()
22 }
23
24 fn to_req(&self, op: Op) -> VersionReq {
25 VersionReq {
26 comparators: vec![Comparator {
27 op,
28 major: self.major,
29 minor: Some(self.minor),
30 patch: Some(self.patch),
31 pre: self.pre.clone(),
32 }],
33 }
34 }
35}
36
37pub trait VersionReqExt {
38 fn matches_prerelease(&self, version: &Version) -> bool;
39}
40
41impl VersionReqExt for VersionReq {
42 fn matches_prerelease(&self, version: &Version) -> bool {
43 semver_eval_ext::matches_prerelease(self, version)
44 }
45}
46
47#[derive(Clone, Copy, Debug)]
49pub enum VersionReqMatchMode {
50 Default,
52 Prerelease,
54}
55
56#[derive(PartialEq, Eq, Hash, Clone, Debug)]
57pub enum OptVersionReq {
58 Any,
59 Req(VersionReq),
60 Locked(Version, VersionReq),
62 Precise(Version, VersionReq),
68}
69
70impl OptVersionReq {
71 pub fn exact(version: &Version) -> Self {
72 OptVersionReq::Req(version.to_exact_req())
73 }
74
75 pub fn lock_to_exact(version: &Version) -> Self {
79 OptVersionReq::Locked(version.clone(), version.to_exact_req())
80 }
81
82 pub fn is_exact(&self) -> bool {
83 match self {
84 OptVersionReq::Any => false,
85 OptVersionReq::Req(req) | OptVersionReq::Precise(_, req) => {
86 req.comparators.len() == 1 && {
87 let cmp = &req.comparators[0];
88 cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
89 }
90 }
91 OptVersionReq::Locked(..) => true,
92 }
93 }
94
95 pub fn lock_to(&mut self, version: &Version, mode: VersionReqMatchMode) {
96 assert!(
97 self.matches(version, mode),
98 "cannot lock {self} to {version}"
99 );
100 use OptVersionReq::*;
101 let version = version.clone();
102 *self = match self {
103 Any => Locked(version, VersionReq::STAR),
104 Req(req) | Locked(_, req) | Precise(_, req) => Locked(version, req.clone()),
105 };
106 }
107
108 pub fn precise_to(&mut self, version: &Version) {
112 use OptVersionReq::*;
113 let version = version.clone();
114 *self = match self {
115 Any => Precise(version, VersionReq::STAR),
116 Req(req) | Locked(_, req) | Precise(_, req) => Precise(version, req.clone()),
117 };
118 }
119
120 pub fn is_precise(&self) -> bool {
121 matches!(self, OptVersionReq::Precise(..))
122 }
123
124 pub fn precise_version(&self) -> Option<&Version> {
126 match self {
127 OptVersionReq::Precise(version, _) => Some(version),
128 _ => None,
129 }
130 }
131
132 pub fn is_locked(&self) -> bool {
133 matches!(self, OptVersionReq::Locked(..))
134 }
135
136 pub fn locked_version(&self) -> Option<&Version> {
138 match self {
139 OptVersionReq::Locked(version, _) => Some(version),
140 _ => None,
141 }
142 }
143
144 pub fn matches(&self, version: &Version, mode: VersionReqMatchMode) -> bool {
145 match self {
146 OptVersionReq::Any => true,
147 OptVersionReq::Req(req) => match mode {
148 VersionReqMatchMode::Default => req.matches(version),
149 VersionReqMatchMode::Prerelease => {
150 req.matches(version)
151 || (version.is_prerelease() && req.matches_prerelease(version))
152 }
153 },
154 OptVersionReq::Locked(v, _) => {
155 v == version
163 }
164 OptVersionReq::Precise(v, _) => {
165 v.major == version.major
176 && v.minor == version.minor
177 && v.patch == version.patch
178 && v.pre == version.pre
179 && (v.build == version.build || v.build.is_empty())
180 }
181 }
182 }
183}
184
185impl Display for OptVersionReq {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 match self {
188 OptVersionReq::Any => f.write_str("*"),
189 OptVersionReq::Req(req)
190 | OptVersionReq::Locked(_, req)
191 | OptVersionReq::Precise(_, req) => Display::fmt(req, f),
192 }
193 }
194}
195
196impl From<VersionReq> for OptVersionReq {
197 fn from(req: VersionReq) -> Self {
198 OptVersionReq::Req(req)
199 }
200}
201
202#[cfg(test)]
203mod matches_prerelease {
204 use semver::VersionReq;
205
206 use super::OptVersionReq;
207 use super::Version;
208 use super::VersionReqMatchMode;
209
210 #[test]
211 fn prerelease() {
212 let cases = [
232 ("1.2.3", "1.2.3-0", false),
234 ("1.2.3", "1.2.3-1", false),
235 ("1.2.3", "1.2.4-0", true),
236 (">=1.2.3", "1.2.3-0", false),
238 (">=1.2.3", "1.2.3-1", false),
239 (">=1.2.3", "1.2.4-0", true),
240 (">1.2.3", "1.2.3-0", false),
242 (">1.2.3", "1.2.3-1", false),
243 (">1.2.3", "1.2.4-0", true),
244 (">1.2.3, <1.2.4", "1.2.3-0", false),
246 (">1.2.3, <1.2.4", "1.2.3-1", false),
247 (">1.2.3, <1.2.4", "1.2.4-0", false), (">=1.2.3, <1.2.4", "1.2.3-0", false),
250 (">=1.2.3, <1.2.4", "1.2.3-1", false),
251 (">=1.2.3, <1.2.4", "1.2.4-0", false), (">1.2.3, <=1.2.4", "1.2.3-0", false),
254 (">1.2.3, <=1.2.4", "1.2.3-1", false),
255 (">1.2.3, <=1.2.4", "1.2.4-0", true),
256 (">=1.2.3-0, <1.2.3", "1.2.3-0", true), (">=1.2.3-0, <1.2.3", "1.2.3-1", true), (">=1.2.3-0, <1.2.3", "1.2.4-0", false),
260 ("1.2.3", "2.0.0-0", false), ("=1.2.3-0", "1.2.3", false),
263 ("=1.2.3-0", "1.2.3-0", true),
264 ("=1.2.3-0", "1.2.4", false),
265 (">=1.2.3-2, <1.2.3-4", "1.2.3-0", false),
266 (">=1.2.3-2, <1.2.3-4", "1.2.3-3", true),
267 (">=1.2.3-2, <1.2.3-4", "1.2.3-5", false), ];
269 for (req, ver, expected) in cases {
270 let version_req = OptVersionReq::Req(req.parse().unwrap());
271 let version = ver.parse().unwrap();
272 let matched = version_req.matches(&version, VersionReqMatchMode::Prerelease);
273 assert_eq!(expected, matched, "req: {req}; ver: {ver}");
274 }
275 }
276
277 #[test]
278 fn opt_version_req_matches_prerelease() {
279 let req_ver: VersionReq = "^1.2.3-rc.0".parse().unwrap();
280 let to_ver: Version = "1.2.3-rc.0".parse().unwrap();
281
282 let req = OptVersionReq::Req(req_ver.clone());
283 assert!(req.matches(&to_ver, VersionReqMatchMode::Prerelease));
284
285 let req = OptVersionReq::Locked(to_ver.clone(), req_ver.clone());
286 assert!(req.matches(&to_ver, VersionReqMatchMode::Prerelease));
287
288 let req = OptVersionReq::Precise(to_ver.clone(), req_ver.clone());
289 assert!(req.matches(&to_ver, VersionReqMatchMode::Prerelease));
290 }
291}