cargo_edit_9/
version.rs

1use std::str::FromStr;
2
3use super::errors::*;
4
5/// Additional version functionality
6pub trait VersionExt {
7    /// Increments the major version number for this Version.
8    fn increment_major(&mut self);
9    /// Increments the minor version number for this Version.
10    fn increment_minor(&mut self);
11    /// Increments the patch version number for this Version.
12    fn increment_patch(&mut self);
13    /// Increment the alpha pre-release number for this Version.
14    ///
15    /// If this isn't alpha, switch to it.
16    ///
17    /// Errors if this would decrement the pre-release phase.
18    fn increment_alpha(&mut self) -> CargoResult<()>;
19    /// Increment the beta pre-release number for this Version.
20    ///
21    /// If this isn't beta, switch to it.
22    ///
23    /// Errors if this would decrement the pre-release phase.
24    fn increment_beta(&mut self) -> CargoResult<()>;
25    /// Increment the rc pre-release number for this Version.
26    ///
27    /// If this isn't rc, switch to it.
28    ///
29    /// Errors if this would decrement the pre-release phase.
30    fn increment_rc(&mut self) -> CargoResult<()>;
31    /// Append informational-only metadata.
32    fn metadata(&mut self, metadata: &str) -> CargoResult<()>;
33    /// Checks to see if the current Version is in pre-release status
34    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!("{}.1", VERSION_ALPHA))?;
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!("{}.1", VERSION_BETA))?;
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!("{}.1", VERSION_RC))?;
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
144/// Upgrade an existing requirement to a new version
145pub fn upgrade_requirement(req: &str, version: &semver::Version) -> CargoResult<Option<String>> {
146    let req_text = req.to_string();
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        // Empty matches everything, no-change.
151        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        // Validate contract
165        #[cfg(debug_assert)]
166        {
167            assert!(
168                new_req.matches(version),
169                "Invalid req created: {}",
170                new_req_text
171            )
172        }
173        if new_req_text == req_text {
174            Ok(None)
175        } else {
176            Ok(Some(new_req_text))
177        }
178    }
179}
180
181fn set_comparator(
182    mut pred: semver::Comparator,
183    version: &semver::Version,
184) -> CargoResult<semver::Comparator> {
185    match pred.op {
186        semver::Op::Wildcard => {
187            pred.major = version.major;
188            if pred.minor.is_some() {
189                pred.minor = Some(version.minor);
190            }
191            if pred.patch.is_some() {
192                pred.patch = Some(version.patch);
193            }
194            Ok(pred)
195        }
196        semver::Op::Exact => Ok(assign_partial_req(version, pred)),
197        semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
198            let user_pred = pred.to_string();
199            Err(unsupported_version_req(user_pred))
200        }
201        semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
202        semver::Op::Caret => Ok(assign_partial_req(version, pred)),
203        _ => {
204            let user_pred = pred.to_string();
205            Err(unsupported_version_req(user_pred))
206        }
207    }
208}
209
210fn assign_partial_req(
211    version: &semver::Version,
212    mut pred: semver::Comparator,
213) -> semver::Comparator {
214    pred.major = version.major;
215    if pred.minor.is_some() {
216        pred.minor = Some(version.minor);
217    }
218    if pred.patch.is_some() {
219        pred.patch = Some(version.patch);
220    }
221    pred.pre = version.pre.clone();
222    pred
223}
224
225#[cfg(test)]
226mod test {
227    use super::*;
228
229    mod increment {
230        use super::*;
231
232        #[test]
233        fn alpha() {
234            let mut v = semver::Version::parse("1.0.0").unwrap();
235            v.increment_alpha().unwrap();
236            assert_eq!(v, semver::Version::parse("1.0.1-alpha.1").unwrap());
237
238            let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
239            v2.increment_alpha().unwrap();
240            assert_eq!(v2, semver::Version::parse("1.0.1-alpha.1").unwrap());
241
242            let mut v3 = semver::Version::parse("1.0.1-alpha.1").unwrap();
243            v3.increment_alpha().unwrap();
244            assert_eq!(v3, semver::Version::parse("1.0.1-alpha.2").unwrap());
245
246            let mut v4 = semver::Version::parse("1.0.1-beta.1").unwrap();
247            assert!(v4.increment_alpha().is_err());
248        }
249
250        #[test]
251        fn beta() {
252            let mut v = semver::Version::parse("1.0.0").unwrap();
253            v.increment_beta().unwrap();
254            assert_eq!(v, semver::Version::parse("1.0.1-beta.1").unwrap());
255
256            let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
257            v2.increment_beta().unwrap();
258            assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
259
260            let mut v2 = semver::Version::parse("1.0.1-alpha.1").unwrap();
261            v2.increment_beta().unwrap();
262            assert_eq!(v2, semver::Version::parse("1.0.1-beta.1").unwrap());
263
264            let mut v3 = semver::Version::parse("1.0.1-beta.1").unwrap();
265            v3.increment_beta().unwrap();
266            assert_eq!(v3, semver::Version::parse("1.0.1-beta.2").unwrap());
267
268            let mut v4 = semver::Version::parse("1.0.1-rc.1").unwrap();
269            assert!(v4.increment_beta().is_err());
270        }
271
272        #[test]
273        fn rc() {
274            let mut v = semver::Version::parse("1.0.0").unwrap();
275            v.increment_rc().unwrap();
276            assert_eq!(v, semver::Version::parse("1.0.1-rc.1").unwrap());
277
278            let mut v2 = semver::Version::parse("1.0.1-dev").unwrap();
279            v2.increment_rc().unwrap();
280            assert_eq!(v2, semver::Version::parse("1.0.1-rc.1").unwrap());
281
282            let mut v3 = semver::Version::parse("1.0.1-rc.1").unwrap();
283            v3.increment_rc().unwrap();
284            assert_eq!(v3, semver::Version::parse("1.0.1-rc.2").unwrap());
285        }
286
287        #[test]
288        fn metadata() {
289            let mut v = semver::Version::parse("1.0.0").unwrap();
290            v.metadata("git.123456").unwrap();
291            assert_eq!(v, semver::Version::parse("1.0.0+git.123456").unwrap());
292        }
293    }
294
295    mod upgrade_requirement {
296        use super::*;
297
298        #[track_caller]
299        fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
300            let version = semver::Version::parse(version).unwrap();
301            let actual = upgrade_requirement(req, &version).unwrap();
302            let expected = expected.into();
303            assert_eq!(actual.as_deref(), expected);
304        }
305
306        #[test]
307        fn wildcard_major() {
308            assert_req_bump("1.0.0", "*", None);
309        }
310
311        #[test]
312        fn wildcard_minor() {
313            assert_req_bump("1.0.0", "1.*", None);
314            assert_req_bump("1.1.0", "1.*", None);
315            assert_req_bump("2.0.0", "1.*", "2.*");
316        }
317
318        #[test]
319        fn wildcard_patch() {
320            assert_req_bump("1.0.0", "1.0.*", None);
321            assert_req_bump("1.1.0", "1.0.*", "1.1.*");
322            assert_req_bump("1.1.1", "1.0.*", "1.1.*");
323            assert_req_bump("2.0.0", "1.0.*", "2.0.*");
324        }
325
326        #[test]
327        fn caret_major() {
328            assert_req_bump("1.0.0", "1", None);
329            assert_req_bump("1.0.0", "^1", None);
330
331            assert_req_bump("1.1.0", "1", None);
332            assert_req_bump("1.1.0", "^1", None);
333
334            assert_req_bump("2.0.0", "1", "2");
335            assert_req_bump("2.0.0", "^1", "^2");
336        }
337
338        #[test]
339        fn caret_minor() {
340            assert_req_bump("1.0.0", "1.0", None);
341            assert_req_bump("1.0.0", "^1.0", None);
342
343            assert_req_bump("1.1.0", "1.0", "1.1");
344            assert_req_bump("1.1.0", "^1.0", "^1.1");
345
346            assert_req_bump("1.1.1", "1.0", "1.1");
347            assert_req_bump("1.1.1", "^1.0", "^1.1");
348
349            assert_req_bump("2.0.0", "1.0", "2.0");
350            assert_req_bump("2.0.0", "^1.0", "^2.0");
351        }
352
353        #[test]
354        fn caret_patch() {
355            assert_req_bump("1.0.0", "1.0.0", None);
356            assert_req_bump("1.0.0", "^1.0.0", None);
357
358            assert_req_bump("1.1.0", "1.0.0", "1.1.0");
359            assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
360
361            assert_req_bump("1.1.1", "1.0.0", "1.1.1");
362            assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
363
364            assert_req_bump("2.0.0", "1.0.0", "2.0.0");
365            assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
366        }
367
368        #[test]
369        fn tilde_major() {
370            assert_req_bump("1.0.0", "~1", None);
371            assert_req_bump("1.1.0", "~1", None);
372            assert_req_bump("2.0.0", "~1", "~2");
373        }
374
375        #[test]
376        fn tilde_minor() {
377            assert_req_bump("1.0.0", "~1.0", None);
378            assert_req_bump("1.1.0", "~1.0", "~1.1");
379            assert_req_bump("1.1.1", "~1.0", "~1.1");
380            assert_req_bump("2.0.0", "~1.0", "~2.0");
381        }
382
383        #[test]
384        fn tilde_patch() {
385            assert_req_bump("1.0.0", "~1.0.0", None);
386            assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
387            assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
388            assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
389        }
390
391        #[test]
392        fn equal_major() {
393            assert_req_bump("1.0.0", "=1", None);
394            assert_req_bump("1.1.0", "=1", None);
395            assert_req_bump("2.0.0", "=1", "=2");
396        }
397
398        #[test]
399        fn equal_minor() {
400            assert_req_bump("1.0.0", "=1.0", None);
401            assert_req_bump("1.1.0", "=1.0", "=1.1");
402            assert_req_bump("1.1.1", "=1.0", "=1.1");
403            assert_req_bump("2.0.0", "=1.0", "=2.0");
404        }
405
406        #[test]
407        fn equal_patch() {
408            assert_req_bump("1.0.0", "=1.0.0", None);
409            assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
410            assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
411            assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
412        }
413    }
414}