1use crate::backport::*;
2use crate::error::{ErrorKind, Position};
3use crate::identifier::Identifier;
4use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
5use core::str::FromStr;
6
7/// Error parsing a SemVer version or version requirement.
8///
9/// # Example
10///
11/// ```
12/// use semver::Version;
13///
14/// fn main() {
15/// let err = Version::parse("1.q.r").unwrap_err();
16///
17/// // "unexpected character 'q' while parsing minor version number"
18/// eprintln!("{}", err);
19/// }
20/// ```
21pub struct Error {
22 pub(crate) kind: ErrorKind,
23}
24
25impl FromStr for Version {
26 type Err = Error;
27
28 fn from_str(text: &str) -> Result<Self, Self::Err> {
29 if text.is_empty() {
30 return Err(Error::new(ErrorKind::Empty));
31 }
32
33 let mut pos = Position::Major;
34 let (major, text) = numeric_identifier(text, pos)?;
35 let text = dot(text, pos)?;
36
37 pos = Position::Minor;
38 let (minor, text) = numeric_identifier(text, pos)?;
39 let text = dot(text, pos)?;
40
41 pos = Position::Patch;
42 let (patch, text) = numeric_identifier(text, pos)?;
43
44 if text.is_empty() {
45 return Ok(Version::new(major, minor, patch));
46 }
47
48 let (pre, text) = if let Some(text) = text.strip_prefix('-') {
49 pos = Position::Pre;
50 let (pre, text) = prerelease_identifier(text)?;
51 if pre.is_empty() {
52 return Err(Error::new(ErrorKind::EmptySegment(pos)));
53 }
54 (pre, text)
55 } else {
56 (Prerelease::EMPTY, text)
57 };
58
59 let (build, text) = if let Some(text) = text.strip_prefix('+') {
60 pos = Position::Build;
61 let (build, text) = build_identifier(text)?;
62 if build.is_empty() {
63 return Err(Error::new(ErrorKind::EmptySegment(pos)));
64 }
65 (build, text)
66 } else {
67 (BuildMetadata::EMPTY, text)
68 };
69
70 if let Some(unexpected) = text.chars().next() {
71 return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
72 }
73
74 Ok(Version {
75 major,
76 minor,
77 patch,
78 pre,
79 build,
80 })
81 }
82}
83
84impl FromStr for VersionReq {
85 type Err = Error;
86
87 fn from_str(text: &str) -> Result<Self, Self::Err> {
88 let text = text.trim_start_matches(' ');
89 if let Some((ch, text)) = wildcard(text) {
90 let rest = text.trim_start_matches(' ');
91 if rest.is_empty() {
92 #[cfg(not(no_const_vec_new))]
93 return Ok(VersionReq::STAR);
94 #[cfg(no_const_vec_new)] // rustc <1.39
95 return Ok(VersionReq {
96 comparators: Vec::new(),
97 });
98 } else if rest.starts_with(',') {
99 return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
100 } else {
101 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
102 }
103 }
104
105 let depth = 0;
106 let mut comparators = Vec::new();
107 let len = version_req(text, &mut comparators, depth)?;
108 unsafe { comparators.set_len(len) }
109 Ok(VersionReq { comparators })
110 }
111}
112
113impl FromStr for Comparator {
114 type Err = Error;
115
116 fn from_str(text: &str) -> Result<Self, Self::Err> {
117 let text: &str = text.trim_start_matches(' ');
118 let (comparator: Comparator, pos: Position, rest: &str) = comparator(input:text)?;
119 if !rest.is_empty() {
120 let unexpected: char = rest.chars().next().unwrap();
121 return Err(Error::new(kind:ErrorKind::UnexpectedCharAfter(pos, unexpected)));
122 }
123 Ok(comparator)
124 }
125}
126
127impl FromStr for Prerelease {
128 type Err = Error;
129
130 fn from_str(text: &str) -> Result<Self, Self::Err> {
131 let (pre: Prerelease, rest: &str) = prerelease_identifier(input:text)?;
132 if !rest.is_empty() {
133 return Err(Error::new(kind:ErrorKind::IllegalCharacter(Position::Pre)));
134 }
135 Ok(pre)
136 }
137}
138
139impl FromStr for BuildMetadata {
140 type Err = Error;
141
142 fn from_str(text: &str) -> Result<Self, Self::Err> {
143 let (build: BuildMetadata, rest: &str) = build_identifier(input:text)?;
144 if !rest.is_empty() {
145 return Err(Error::new(kind:ErrorKind::IllegalCharacter(Position::Build)));
146 }
147 Ok(build)
148 }
149}
150
151impl Error {
152 fn new(kind: ErrorKind) -> Self {
153 Error { kind }
154 }
155}
156
157impl Op {
158 const DEFAULT: Self = Op::Caret;
159}
160
161fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
162 let mut len = 0;
163 let mut value = 0u64;
164
165 while let Some(&digit) = input.as_bytes().get(len) {
166 if digit < b'0' || digit > b'9' {
167 break;
168 }
169 if value == 0 && len > 0 {
170 return Err(Error::new(ErrorKind::LeadingZero(pos)));
171 }
172 match value
173 .checked_mul(10)
174 .and_then(|value| value.checked_add((digit - b'0') as u64))
175 {
176 Some(sum) => value = sum,
177 None => return Err(Error::new(ErrorKind::Overflow(pos))),
178 }
179 len += 1;
180 }
181
182 if len > 0 {
183 Ok((value, &input[len..]))
184 } else if let Some(unexpected) = input[len..].chars().next() {
185 Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
186 } else {
187 Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
188 }
189}
190
191fn wildcard(input: &str) -> Option<(char, &str)> {
192 if let Some(rest: &str) = input.strip_prefix('*') {
193 Some(('*', rest))
194 } else if let Some(rest: &str) = input.strip_prefix('x') {
195 Some(('x', rest))
196 } else if let Some(rest: &str) = input.strip_prefix('X') {
197 Some(('X', rest))
198 } else {
199 None
200 }
201}
202
203fn dot(input: &str, pos: Position) -> Result<&str, Error> {
204 if let Some(rest: &str) = input.strip_prefix('.') {
205 Ok(rest)
206 } else if let Some(unexpected: char) = input.chars().next() {
207 Err(Error::new(kind:ErrorKind::UnexpectedCharAfter(pos, unexpected)))
208 } else {
209 Err(Error::new(kind:ErrorKind::UnexpectedEnd(pos)))
210 }
211}
212
213fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
214 let (string: &str, rest: &str) = identifier(input, pos:Position::Pre)?;
215 let identifier: Identifier = unsafe { Identifier::new_unchecked(string) };
216 Ok((Prerelease { identifier }, rest))
217}
218
219fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
220 let (string: &str, rest: &str) = identifier(input, pos:Position::Build)?;
221 let identifier: Identifier = unsafe { Identifier::new_unchecked(string) };
222 Ok((BuildMetadata { identifier }, rest))
223}
224
225fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
226 let mut accumulated_len = 0;
227 let mut segment_len = 0;
228 let mut segment_has_nondigit = false;
229
230 loop {
231 match input.as_bytes().get(accumulated_len + segment_len) {
232 Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
233 segment_len += 1;
234 segment_has_nondigit = true;
235 }
236 Some(b'0'..=b'9') => {
237 segment_len += 1;
238 }
239 boundary => {
240 if segment_len == 0 {
241 if accumulated_len == 0 && boundary != Some(&b'.') {
242 return Ok(("", input));
243 } else {
244 return Err(Error::new(ErrorKind::EmptySegment(pos)));
245 }
246 }
247 if pos == Position::Pre
248 && segment_len > 1
249 && !segment_has_nondigit
250 && input[accumulated_len..].starts_with('0')
251 {
252 return Err(Error::new(ErrorKind::LeadingZero(pos)));
253 }
254 accumulated_len += segment_len;
255 if boundary == Some(&b'.') {
256 accumulated_len += 1;
257 segment_len = 0;
258 segment_has_nondigit = false;
259 } else {
260 return Ok(input.split_at(accumulated_len));
261 }
262 }
263 }
264 }
265}
266
267fn op(input: &str) -> (Op, &str) {
268 let bytes: &[u8] = input.as_bytes();
269 if bytes.first() == Some(&b'=') {
270 (Op::Exact, &input[1..])
271 } else if bytes.first() == Some(&b'>') {
272 if bytes.get(index:1) == Some(&b'=') {
273 (Op::GreaterEq, &input[2..])
274 } else {
275 (Op::Greater, &input[1..])
276 }
277 } else if bytes.first() == Some(&b'<') {
278 if bytes.get(index:1) == Some(&b'=') {
279 (Op::LessEq, &input[2..])
280 } else {
281 (Op::Less, &input[1..])
282 }
283 } else if bytes.first() == Some(&b'~') {
284 (Op::Tilde, &input[1..])
285 } else if bytes.first() == Some(&b'^') {
286 (Op::Caret, &input[1..])
287 } else {
288 (Op::DEFAULT, input)
289 }
290}
291
292fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
293 let (mut op, text) = op(input);
294 let default_op = input.len() == text.len();
295 let text = text.trim_start_matches(' ');
296
297 let mut pos = Position::Major;
298 let (major, text) = numeric_identifier(text, pos)?;
299 let mut has_wildcard = false;
300
301 let (minor, text) = if let Some(text) = text.strip_prefix('.') {
302 pos = Position::Minor;
303 if let Some((_, text)) = wildcard(text) {
304 has_wildcard = true;
305 if default_op {
306 op = Op::Wildcard;
307 }
308 (None, text)
309 } else {
310 let (minor, text) = numeric_identifier(text, pos)?;
311 (Some(minor), text)
312 }
313 } else {
314 (None, text)
315 };
316
317 let (patch, text) = if let Some(text) = text.strip_prefix('.') {
318 pos = Position::Patch;
319 if let Some((_, text)) = wildcard(text) {
320 if default_op {
321 op = Op::Wildcard;
322 }
323 (None, text)
324 } else if has_wildcard {
325 return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
326 } else {
327 let (patch, text) = numeric_identifier(text, pos)?;
328 (Some(patch), text)
329 }
330 } else {
331 (None, text)
332 };
333
334 let (pre, text) = if patch.is_some() && text.starts_with('-') {
335 pos = Position::Pre;
336 let text = &text[1..];
337 let (pre, text) = prerelease_identifier(text)?;
338 if pre.is_empty() {
339 return Err(Error::new(ErrorKind::EmptySegment(pos)));
340 }
341 (pre, text)
342 } else {
343 (Prerelease::EMPTY, text)
344 };
345
346 let text = if patch.is_some() && text.starts_with('+') {
347 pos = Position::Build;
348 let text = &text[1..];
349 let (build, text) = build_identifier(text)?;
350 if build.is_empty() {
351 return Err(Error::new(ErrorKind::EmptySegment(pos)));
352 }
353 text
354 } else {
355 text
356 };
357
358 let text = text.trim_start_matches(' ');
359
360 let comparator = Comparator {
361 op,
362 major,
363 minor,
364 patch,
365 pre,
366 };
367
368 Ok((comparator, pos, text))
369}
370
371fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
372 let (comparator, pos, text) = match comparator(input) {
373 Ok(success) => success,
374 Err(mut error) => {
375 if let Some((ch, mut rest)) = wildcard(input) {
376 rest = rest.trim_start_matches(' ');
377 if rest.is_empty() || rest.starts_with(',') {
378 error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
379 }
380 }
381 return Err(error);
382 }
383 };
384
385 if text.is_empty() {
386 out.reserve_exact(depth + 1);
387 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
388 return Ok(depth + 1);
389 }
390
391 let text = if let Some(text) = text.strip_prefix(',') {
392 text.trim_start_matches(' ')
393 } else {
394 let unexpected = text.chars().next().unwrap();
395 return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
396 };
397
398 const MAX_COMPARATORS: usize = 32;
399 if depth + 1 == MAX_COMPARATORS {
400 return Err(Error::new(ErrorKind::ExcessiveComparators));
401 }
402
403 // Recurse to collect parsed Comparator objects on the stack. We perform a
404 // single allocation to allocate exactly the right sized Vec only once the
405 // total number of comparators is known.
406 let len = version_req(text, out, depth + 1)?;
407 unsafe { out.as_mut_ptr().add(depth).write(comparator) }
408 Ok(len)
409}
410