1use std::str::FromStr;
2
3use super::errors::{CargoResult, invalid_release_level, unsupported_version_req};
4
5pub trait VersionExt {
7 fn increment_major(&mut self);
9 fn increment_minor(&mut self);
11 fn increment_patch(&mut self);
13 fn increment_alpha(&mut self) -> CargoResult<()>;
19 fn increment_beta(&mut self) -> CargoResult<()>;
25 fn increment_rc(&mut self) -> CargoResult<()>;
31 fn metadata(&mut self, metadata: &str) -> CargoResult<()>;
33 fn is_prerelease(&self) -> bool;
35}
36
37impl VersionExt for semver::Version {
38 fn increment_major(&mut self) {
39 self.major += 1;
40 self.minor = 0;
41 self.patch = 0;
42 self.pre = semver::Prerelease::EMPTY;
43 self.build = semver::BuildMetadata::EMPTY;
44 }
45
46 fn increment_minor(&mut self) {
47 self.minor += 1;
48 self.patch = 0;
49 self.pre = semver::Prerelease::EMPTY;
50 self.build = semver::BuildMetadata::EMPTY;
51 }
52
53 fn increment_patch(&mut self) {
54 self.patch += 1;
55 self.pre = semver::Prerelease::EMPTY;
56 self.build = semver::BuildMetadata::EMPTY;
57 }
58
59 fn increment_alpha(&mut self) -> CargoResult<()> {
60 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
61 if pre_ext == VERSION_BETA || pre_ext == VERSION_RC {
62 Err(invalid_release_level(VERSION_ALPHA, self.clone()))
63 } else {
64 let new_ext_ver = if pre_ext == VERSION_ALPHA {
65 pre_ext_ver.unwrap_or(0) + 1
66 } else {
67 1
68 };
69 self.pre = semver::Prerelease::new(&format!("{VERSION_ALPHA}.{new_ext_ver}"))?;
70 Ok(())
71 }
72 } else {
73 self.increment_patch();
74 self.pre = semver::Prerelease::new(&format!("{VERSION_ALPHA}.1",))?;
75 Ok(())
76 }
77 }
78
79 fn increment_beta(&mut self) -> CargoResult<()> {
80 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
81 if pre_ext == VERSION_RC {
82 Err(invalid_release_level(VERSION_BETA, self.clone()))
83 } else {
84 let new_ext_ver = if pre_ext == VERSION_BETA {
85 pre_ext_ver.unwrap_or(0) + 1
86 } else {
87 1
88 };
89 self.pre = semver::Prerelease::new(&format!("{VERSION_BETA}.{new_ext_ver}"))?;
90 Ok(())
91 }
92 } else {
93 self.increment_patch();
94 self.pre = semver::Prerelease::new(&format!("{VERSION_BETA}.1"))?;
95 Ok(())
96 }
97 }
98
99 fn increment_rc(&mut self) -> CargoResult<()> {
100 if let Some((pre_ext, pre_ext_ver)) = prerelease_id_version(self)? {
101 let new_ext_ver = if pre_ext == VERSION_RC {
102 pre_ext_ver.unwrap_or(0) + 1
103 } else {
104 1
105 };
106 self.pre = semver::Prerelease::new(&format!("{VERSION_RC}.{new_ext_ver}"))?;
107 Ok(())
108 } else {
109 self.increment_patch();
110 self.pre = semver::Prerelease::new(&format!("{VERSION_RC}.1"))?;
111 Ok(())
112 }
113 }
114
115 fn metadata(&mut self, build: &str) -> CargoResult<()> {
116 self.build = semver::BuildMetadata::new(build)?;
117 Ok(())
118 }
119
120 fn is_prerelease(&self) -> bool {
121 !self.pre.is_empty()
122 }
123}
124
125static VERSION_ALPHA: &str = "alpha";
126static VERSION_BETA: &str = "beta";
127static VERSION_RC: &str = "rc";
128
129fn prerelease_id_version(version: &semver::Version) -> CargoResult<Option<(String, Option<u64>)>> {
130 if !version.pre.is_empty() {
131 if let Some((alpha, numeric)) = version.pre.as_str().split_once('.') {
132 let alpha = alpha.to_owned();
133 let numeric = u64::from_str(numeric)
134 .map_err(|_| anyhow::format_err!("This version scheme is not supported. Use format like `pre`, `dev` or `alpha.1` for prerelease symbol"))?;
135 Ok(Some((alpha, Some(numeric))))
136 } else {
137 Ok(Some((version.pre.as_str().to_owned(), None)))
138 }
139 } else {
140 Ok(None)
141 }
142}
143
144pub fn upgrade_requirement(req: &str, version: &semver::Version) -> CargoResult<Option<String>> {
146 let req_text = req.to_owned();
147 let raw_req = semver::VersionReq::parse(&req_text)
148 .expect("semver to generate valid version requirements");
149 if raw_req.comparators.is_empty() {
150 Ok(None)
152 } else {
153 let comparators: CargoResult<Vec<_>> = raw_req
154 .comparators
155 .into_iter()
156 .map(|p| set_comparator(p, version))
157 .collect();
158 let comparators = comparators?;
159 let new_req = semver::VersionReq { comparators };
160 let mut new_req_text = new_req.to_string();
161 if new_req_text.starts_with('^') && !req.starts_with('^') {
162 new_req_text.remove(0);
163 }
164 #[cfg(debug_assertions)]
166 {
167 assert!(
168 new_req.matches(version),
169 "Invalid req created: {new_req_text}"
170 );
171 }
172 if new_req_text == req_text {
173 Ok(None)
174 } else {
175 Ok(Some(new_req_text))
176 }
177 }
178}
179
180fn set_comparator(
181 mut pred: semver::Comparator,
182 version: &semver::Version,
183) -> CargoResult<semver::Comparator> {
184 match pred.op {
185 semver::Op::Wildcard => {
186 pred.major = version.major;
187 if pred.minor.is_some() {
188 pred.minor = Some(version.minor);
189 }
190 if pred.patch.is_some() {
191 pred.patch = Some(version.patch);
192 }
193 Ok(pred)
194 }
195 semver::Op::Exact => Ok(assign_partial_req(version, pred)),
196 semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
197 let user_pred = pred.to_string();
198 Err(unsupported_version_req(user_pred))
199 }
200 semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
201 semver::Op::Caret => Ok(assign_partial_req(version, pred)),
202 _ => {
203 let user_pred = pred.to_string();
204 Err(unsupported_version_req(user_pred))
205 }
206 }
207}
208
209fn assign_partial_req(
210 version: &semver::Version,
211 mut pred: semver::Comparator,
212) -> semver::Comparator {
213 pred.major = version.major;
214 if pred.minor.is_some() {
215 pred.minor = Some(version.minor);
216 }
217 if pred.patch.is_some() {
218 pred.patch = Some(version.patch);
219 }
220 pred.pre = version.pre.clone();
221 pred
222}
223
224#[cfg(test)]
225mod test {
226 use super::*;
227
228 mod increment {
229 use super::*;
230
231 #[test]
232 fn alpha() {
233 let mut v = semver::Version::parse("1.0.0").unwrap();
234 v.increment_alpha().unwrap();
235 assert_eq!(v, semver::Version::parse("1.0.1-alpha.1").unwrap());
236
237 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
238 v2.increment_alpha().unwrap();
239 assert_eq!(v2, semver::Version::parse("1.0.1-alpha.1").unwrap());
240
241 let mut v3 = semver::Version::parse("1.0.1-alpha.1").unwrap();
242 v3.increment_alpha().unwrap();
243 assert_eq!(v3, semver::Version::parse("1.0.1-alpha.2").unwrap());
244
245 let mut v4 = semver::Version::parse("1.0.1-beta.1").unwrap();
246 assert!(v4.increment_alpha().is_err());
247 }
248
249 #[test]
250 fn beta() {
251 let mut v = semver::Version::parse("1.0.0").unwrap();
252 v.increment_beta().unwrap();
253 assert_eq!(v, semver::Version::parse("1.0.1-beta.1").unwrap());
254
255 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
256 v2.increment_beta().unwrap();
257 assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
258
259 let mut v2 = semver::Version::parse("1.0.1-alpha.1").unwrap();
260 v2.increment_beta().unwrap();
261 assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
262
263 let mut v3 = semver::Version::parse("1.0.1-beta.1").unwrap();
264 v3.increment_beta().unwrap();
265 assert_eq!(v3, semver::Version::parse("1.0.1-beta.2").unwrap());
266
267 let mut v4 = semver::Version::parse("1.0.1-rc.1").unwrap();
268 assert!(v4.increment_beta().is_err());
269 }
270
271 #[test]
272 fn rc() {
273 let mut v = semver::Version::parse("1.0.0").unwrap();
274 v.increment_rc().unwrap();
275 assert_eq!(v, semver::Version::parse("1.0.1-rc.1").unwrap());
276
277 let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
278 v2.increment_rc().unwrap();
279 assert_eq!(v2, semver::Version::parse("1.0.1-rc.1").unwrap());
280
281 let mut v3 = semver::Version::parse("1.0.1-rc.1").unwrap();
282 v3.increment_rc().unwrap();
283 assert_eq!(v3, semver::Version::parse("1.0.1-rc.2").unwrap());
284 }
285
286 #[test]
287 fn metadata() {
288 let mut v = semver::Version::parse("1.0.0").unwrap();
289 v.metadata("git.123456").unwrap();
290 assert_eq!(v, semver::Version::parse("1.0.0+git.123456").unwrap());
291 }
292 }
293
294 mod upgrade_requirement {
295 use super::*;
296
297 #[track_caller]
298 fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
299 let version = semver::Version::parse(version).unwrap();
300 let actual = upgrade_requirement(req, &version).unwrap();
301 let expected = expected.into();
302 assert_eq!(actual.as_deref(), expected);
303 }
304
305 #[test]
306 fn wildcard_major() {
307 assert_req_bump("1.0.0", "*", None);
308 }
309
310 #[test]
311 fn wildcard_minor() {
312 assert_req_bump("1.0.0", "1.*", None);
313 assert_req_bump("1.1.0", "1.*", None);
314 assert_req_bump("2.0.0", "1.*", "2.*");
315 }
316
317 #[test]
318 fn wildcard_patch() {
319 assert_req_bump("1.0.0", "1.0.*", None);
320 assert_req_bump("1.1.0", "1.0.*", "1.1.*");
321 assert_req_bump("1.1.1", "1.0.*", "1.1.*");
322 assert_req_bump("2.0.0", "1.0.*", "2.0.*");
323 }
324
325 #[test]
326 fn caret_major() {
327 assert_req_bump("1.0.0", "1", None);
328 assert_req_bump("1.0.0", "^1", None);
329
330 assert_req_bump("1.1.0", "1", None);
331 assert_req_bump("1.1.0", "^1", None);
332
333 assert_req_bump("2.0.0", "1", "2");
334 assert_req_bump("2.0.0", "^1", "^2");
335 }
336
337 #[test]
338 fn caret_minor() {
339 assert_req_bump("1.0.0", "1.0", None);
340 assert_req_bump("1.0.0", "^1.0", None);
341
342 assert_req_bump("1.1.0", "1.0", "1.1");
343 assert_req_bump("1.1.0", "^1.0", "^1.1");
344
345 assert_req_bump("1.1.1", "1.0", "1.1");
346 assert_req_bump("1.1.1", "^1.0", "^1.1");
347
348 assert_req_bump("2.0.0", "1.0", "2.0");
349 assert_req_bump("2.0.0", "^1.0", "^2.0");
350 }
351
352 #[test]
353 fn caret_patch() {
354 assert_req_bump("1.0.0", "1.0.0", None);
355 assert_req_bump("1.0.0", "^1.0.0", None);
356
357 assert_req_bump("1.1.0", "1.0.0", "1.1.0");
358 assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
359
360 assert_req_bump("1.1.1", "1.0.0", "1.1.1");
361 assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
362
363 assert_req_bump("2.0.0", "1.0.0", "2.0.0");
364 assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
365 }
366
367 #[test]
368 fn tilde_major() {
369 assert_req_bump("1.0.0", "~1", None);
370 assert_req_bump("1.1.0", "~1", None);
371 assert_req_bump("2.0.0", "~1", "~2");
372 }
373
374 #[test]
375 fn tilde_minor() {
376 assert_req_bump("1.0.0", "~1.0", None);
377 assert_req_bump("1.1.0", "~1.0", "~1.1");
378 assert_req_bump("1.1.1", "~1.0", "~1.1");
379 assert_req_bump("2.0.0", "~1.0", "~2.0");
380 }
381
382 #[test]
383 fn tilde_patch() {
384 assert_req_bump("1.0.0", "~1.0.0", None);
385 assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
386 assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
387 assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
388 }
389
390 #[test]
391 fn equal_major() {
392 assert_req_bump("1.0.0", "=1", None);
393 assert_req_bump("1.1.0", "=1", None);
394 assert_req_bump("2.0.0", "=1", "=2");
395 }
396
397 #[test]
398 fn equal_minor() {
399 assert_req_bump("1.0.0", "=1.0", None);
400 assert_req_bump("1.1.0", "=1.0", "=1.1");
401 assert_req_bump("1.1.1", "=1.0", "=1.1");
402 assert_req_bump("2.0.0", "=1.0", "=2.0");
403 }
404
405 #[test]
406 fn equal_patch() {
407 assert_req_bump("1.0.0", "=1.0.0", None);
408 assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
409 assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
410 assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
411 }
412 }
413}