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(PartialEq, Eq, Hash, Clone, Debug)]
48pub enum OptVersionReq {
49 Any,
50 Req(VersionReq),
51 Locked(Version, VersionReq),
53 Precise(Version, VersionReq),
59}
60
61impl OptVersionReq {
62 pub fn exact(version: &Version) -> Self {
63 OptVersionReq::Req(version.to_exact_req())
64 }
65
66 pub fn lock_to_exact(version: &Version) -> Self {
70 OptVersionReq::Locked(version.clone(), version.to_exact_req())
71 }
72
73 pub fn is_exact(&self) -> bool {
74 match self {
75 OptVersionReq::Any => false,
76 OptVersionReq::Req(req) | OptVersionReq::Precise(_, req) => {
77 req.comparators.len() == 1 && {
78 let cmp = &req.comparators[0];
79 cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
80 }
81 }
82 OptVersionReq::Locked(..) => true,
83 }
84 }
85
86 pub fn lock_to(&mut self, version: &Version) {
87 assert!(self.matches(version), "cannot lock {} to {}", self, version);
88 use OptVersionReq::*;
89 let version = version.clone();
90 *self = match self {
91 Any => Locked(version, VersionReq::STAR),
92 Req(req) | Locked(_, req) | Precise(_, req) => Locked(version, req.clone()),
93 };
94 }
95
96 pub fn precise_to(&mut self, version: &Version) {
100 use OptVersionReq::*;
101 let version = version.clone();
102 *self = match self {
103 Any => Precise(version, VersionReq::STAR),
104 Req(req) | Locked(_, req) | Precise(_, req) => Precise(version, req.clone()),
105 };
106 }
107
108 pub fn is_precise(&self) -> bool {
109 matches!(self, OptVersionReq::Precise(..))
110 }
111
112 pub fn precise_version(&self) -> Option<&Version> {
114 match self {
115 OptVersionReq::Precise(version, _) => Some(version),
116 _ => None,
117 }
118 }
119
120 pub fn is_locked(&self) -> bool {
121 matches!(self, OptVersionReq::Locked(..))
122 }
123
124 pub fn locked_version(&self) -> Option<&Version> {
126 match self {
127 OptVersionReq::Locked(version, _) => Some(version),
128 _ => None,
129 }
130 }
131
132 pub fn matches_prerelease(&self, version: &Version) -> bool {
135 if let OptVersionReq::Req(req) = self {
136 return req.matches_prerelease(version);
137 } else {
138 return self.matches(version);
139 }
140 }
141
142 pub fn matches(&self, version: &Version) -> bool {
143 match self {
144 OptVersionReq::Any => true,
145 OptVersionReq::Req(req) => req.matches(version),
146 OptVersionReq::Locked(v, _) => {
147 v == version
155 }
156 OptVersionReq::Precise(v, _) => {
157 v.major == version.major
168 && v.minor == version.minor
169 && v.patch == version.patch
170 && v.pre == version.pre
171 && (v.build == version.build || v.build.is_empty())
172 }
173 }
174 }
175}
176
177impl Display for OptVersionReq {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 match self {
180 OptVersionReq::Any => f.write_str("*"),
181 OptVersionReq::Req(req)
182 | OptVersionReq::Locked(_, req)
183 | OptVersionReq::Precise(_, req) => Display::fmt(req, f),
184 }
185 }
186}
187
188impl From<VersionReq> for OptVersionReq {
189 fn from(req: VersionReq) -> Self {
190 OptVersionReq::Req(req)
191 }
192}
193
194#[cfg(test)]
195mod matches_prerelease {
196 use semver::VersionReq;
197
198 use super::OptVersionReq;
199 use super::Version;
200
201 #[test]
202 fn prerelease() {
203 let cases = [
223 ("1.2.3", "1.2.3-0", false),
225 ("1.2.3", "1.2.3-1", false),
226 ("1.2.3", "1.2.4-0", true),
227 (">=1.2.3", "1.2.3-0", false),
229 (">=1.2.3", "1.2.3-1", false),
230 (">=1.2.3", "1.2.4-0", true),
231 (">1.2.3", "1.2.3-0", false),
233 (">1.2.3", "1.2.3-1", false),
234 (">1.2.3", "1.2.4-0", true),
235 (">1.2.3, <1.2.4", "1.2.3-0", false),
237 (">1.2.3, <1.2.4", "1.2.3-1", false),
238 (">1.2.3, <1.2.4", "1.2.4-0", false), (">=1.2.3, <1.2.4", "1.2.3-0", false),
241 (">=1.2.3, <1.2.4", "1.2.3-1", false),
242 (">=1.2.3, <1.2.4", "1.2.4-0", false), (">1.2.3, <=1.2.4", "1.2.3-0", false),
245 (">1.2.3, <=1.2.4", "1.2.3-1", false),
246 (">1.2.3, <=1.2.4", "1.2.4-0", true),
247 (">=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),
251 ("1.2.3", "2.0.0-0", false), ("=1.2.3-0", "1.2.3", false),
254 ("=1.2.3-0", "1.2.3-0", true),
255 ("=1.2.3-0", "1.2.4", false),
256 (">=1.2.3-2, <1.2.3-4", "1.2.3-0", false),
257 (">=1.2.3-2, <1.2.3-4", "1.2.3-3", true),
258 (">=1.2.3-2, <1.2.3-4", "1.2.3-5", false), ];
260 for (req, ver, expected) in cases {
261 let version_req = req.parse().unwrap();
262 let version = ver.parse().unwrap();
263 let matched = OptVersionReq::Req(version_req).matches_prerelease(&version);
264 assert_eq!(expected, matched, "req: {req}; ver: {ver}");
265 }
266 }
267
268 #[test]
269 fn opt_version_req_matches_prerelease() {
270 let req_ver: VersionReq = "^1.2.3-rc.0".parse().unwrap();
271 let to_ver: Version = "1.2.3-rc.0".parse().unwrap();
272
273 let req = OptVersionReq::Req(req_ver.clone());
274 assert!(req.matches_prerelease(&to_ver));
275
276 let req = OptVersionReq::Locked(to_ver.clone(), req_ver.clone());
277 assert!(req.matches_prerelease(&to_ver));
278
279 let req = OptVersionReq::Precise(to_ver.clone(), req_ver.clone());
280 assert!(req.matches_prerelease(&to_ver));
281 }
282}