1use std::fmt::Display;
2
3use crate::CargoResult;
4
5pub(crate) fn upgrade_requirement(
8 req: &str,
9 version: &semver::Version,
10) -> CargoResult<Option<(String, semver::VersionReq)>> {
11 let req_text = req.to_string();
12 let raw_req = semver::VersionReq::parse(&req_text)
13 .expect("semver to generate valid version requirements");
14 if raw_req.comparators.is_empty() {
15 Ok(None)
17 } else {
18 let comparators: Vec<_> = raw_req
19 .comparators
20 .into_iter()
21 .filter(|p| p.pre.is_empty() || matches_greater(p, version))
23 .map(|p| set_comparator(p, version))
24 .collect::<CargoResult<_>>()?;
25 if comparators.is_empty() {
26 return Ok(None);
27 }
28 let new_req = semver::VersionReq { comparators };
29 let mut new_req_text = new_req.to_string();
30 if new_req_text.starts_with('^') && !req.starts_with('^') {
31 new_req_text.remove(0);
32 }
33 #[cfg(debug_assertions)]
35 {
36 assert!(
37 new_req.matches(version),
38 "New req {} is invalid, because {} does not match {}",
39 new_req_text,
40 new_req,
41 version
42 )
43 }
44 if new_req_text == req_text {
45 Ok(None)
46 } else {
47 Ok(Some((new_req_text, new_req)))
48 }
49 }
50}
51
52fn set_comparator(
53 mut pred: semver::Comparator,
54 version: &semver::Version,
55) -> CargoResult<semver::Comparator> {
56 match pred.op {
57 semver::Op::Wildcard => {
58 pred.major = version.major;
59 if pred.minor.is_some() {
60 pred.minor = Some(version.minor);
61 }
62 if pred.patch.is_some() {
63 pred.patch = Some(version.patch);
64 }
65 Ok(pred)
66 }
67 semver::Op::Exact => Ok(assign_partial_req(version, pred)),
68 semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
69 let user_pred = pred.to_string();
70 Err(unsupported_version_req(user_pred))
71 }
72 semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
73 semver::Op::Caret => Ok(assign_partial_req(version, pred)),
74 _ => {
75 let user_pred = pred.to_string();
76 Err(unsupported_version_req(user_pred))
77 }
78 }
79}
80
81fn matches_greater(cmp: &semver::Comparator, ver: &semver::Version) -> bool {
83 if ver.major != cmp.major {
84 return ver.major > cmp.major;
85 }
86
87 match cmp.minor {
88 None => return false,
89 Some(minor) => {
90 if ver.minor != minor {
91 return ver.minor > minor;
92 }
93 }
94 }
95
96 match cmp.patch {
97 None => return false,
98 Some(patch) => {
99 if ver.patch != patch {
100 return ver.patch > patch;
101 }
102 }
103 }
104
105 ver.pre > cmp.pre
106}
107
108fn assign_partial_req(
109 version: &semver::Version,
110 mut pred: semver::Comparator,
111) -> semver::Comparator {
112 pred.major = version.major;
113 if pred.minor.is_some() {
114 pred.minor = Some(version.minor);
115 }
116 if pred.patch.is_some() {
117 pred.patch = Some(version.patch);
118 }
119 pred.pre = version.pre.clone();
120 pred
121}
122
123fn unsupported_version_req(req: impl Display) -> anyhow::Error {
124 anyhow::format_err!("Support for modifying {} is currently unsupported", req)
125}
126
127#[cfg(test)]
128mod test {
129 use super::*;
130
131 mod upgrade_requirement {
132 use super::*;
133
134 #[track_caller]
135 fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
136 let version = semver::Version::parse(version).unwrap();
137 let actual = upgrade_requirement(req, &version)
138 .unwrap()
139 .map(|(actual, _req)| actual);
140 let expected = expected.into();
141 assert_eq!(actual.as_deref(), expected);
142 }
143
144 #[test]
145 fn wildcard_major() {
146 assert_req_bump("1.0.0", "*", None);
147 }
148
149 #[test]
150 fn wildcard_minor() {
151 assert_req_bump("1.0.0", "1.*", None);
152 assert_req_bump("1.1.0", "1.*", None);
153 assert_req_bump("2.0.0", "1.*", "2.*");
154 }
155
156 #[test]
157 fn wildcard_patch() {
158 assert_req_bump("1.0.0", "1.0.*", None);
159 assert_req_bump("1.1.0", "1.0.*", "1.1.*");
160 assert_req_bump("1.1.1", "1.0.*", "1.1.*");
161 assert_req_bump("2.0.0", "1.0.*", "2.0.*");
162 }
163
164 #[test]
165 fn caret_major() {
166 assert_req_bump("1.0.0", "1", None);
167 assert_req_bump("1.0.0", "^1", None);
168
169 assert_req_bump("1.1.0", "1", None);
170 assert_req_bump("1.1.0", "^1", None);
171
172 assert_req_bump("2.0.0", "1", "2");
173 assert_req_bump("2.0.0", "^1", "^2");
174 }
175
176 #[test]
177 fn caret_minor() {
178 assert_req_bump("1.0.0", "1.0", None);
179 assert_req_bump("1.0.0", "^1.0", None);
180
181 assert_req_bump("1.1.0", "1.0", "1.1");
182 assert_req_bump("1.1.0", "^1.0", "^1.1");
183
184 assert_req_bump("1.1.1", "1.0", "1.1");
185 assert_req_bump("1.1.1", "^1.0", "^1.1");
186
187 assert_req_bump("2.0.0", "1.0", "2.0");
188 assert_req_bump("2.0.0", "^1.0", "^2.0");
189 }
190
191 #[test]
192 fn caret_patch() {
193 assert_req_bump("1.0.0", "1.0.0", None);
194 assert_req_bump("1.0.0", "^1.0.0", None);
195
196 assert_req_bump("1.1.0", "1.0.0", "1.1.0");
197 assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
198
199 assert_req_bump("1.1.1", "1.0.0", "1.1.1");
200 assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
201
202 assert_req_bump("2.0.0", "1.0.0", "2.0.0");
203 assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
204 }
205
206 #[test]
207 fn tilde_major() {
208 assert_req_bump("1.0.0", "~1", None);
209 assert_req_bump("1.1.0", "~1", None);
210 assert_req_bump("2.0.0", "~1", "~2");
211 }
212
213 #[test]
214 fn tilde_minor() {
215 assert_req_bump("1.0.0", "~1.0", None);
216 assert_req_bump("1.1.0", "~1.0", "~1.1");
217 assert_req_bump("1.1.1", "~1.0", "~1.1");
218 assert_req_bump("2.0.0", "~1.0", "~2.0");
219 }
220
221 #[test]
222 fn tilde_patch() {
223 assert_req_bump("1.0.0", "~1.0.0", None);
224 assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
225 assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
226 assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
227 }
228
229 #[test]
230 fn equal_major() {
231 assert_req_bump("1.0.0", "=1", None);
232 assert_req_bump("1.1.0", "=1", None);
233 assert_req_bump("2.0.0", "=1", "=2");
234 }
235
236 #[test]
237 fn equal_minor() {
238 assert_req_bump("1.0.0", "=1.0", None);
239 assert_req_bump("1.1.0", "=1.0", "=1.1");
240 assert_req_bump("1.1.1", "=1.0", "=1.1");
241 assert_req_bump("2.0.0", "=1.0", "=2.0");
242 }
243
244 #[test]
245 fn equal_patch() {
246 assert_req_bump("1.0.0", "=1.0.0", None);
247 assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
248 assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
249 assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
250 }
251
252 #[test]
253 fn greater_prerelease() {
254 assert_req_bump("1.7.0", "2.0.0-beta.21", None);
255 assert_req_bump("1.7.0", "=2.0.0-beta.21", None);
256 assert_req_bump("1.7.0", "~2.0.0-beta.21", None);
257 assert_req_bump("2.0.0-beta.20", "2.0.0-beta.21", None);
258 assert_req_bump("2.0.0-beta.21", "2.0.0-beta.21", None);
259 assert_req_bump("2.0.0-beta.22", "2.0.0-beta.21", "2.0.0-beta.22");
260 assert_req_bump("2.0.0", "2.0.0-beta.21", "2.0.0");
261 assert_req_bump("3.0.0", "2.0.0-beta.21", "3.0.0");
262 }
263 }
264}