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