cargo_edit/
version.rs

1use std::str::FromStr;
2
3use super::errors::{CargoResult, invalid_release_level, unsupported_version_req};
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!("{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
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_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        // 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_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}