Coverage Report

Created: 2025-10-10 07:11

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