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