Source code

Revision control

Copy as Markdown

Other Tools

use crate::backport::*;
use crate::error::{ErrorKind, Position};
use crate::identifier::Identifier;
use crate::{BuildMetadata, Comparator, Op, Prerelease, Version, VersionReq};
use core::str::FromStr;
/// Error parsing a SemVer version or version requirement.
///
/// # Example
///
/// ```
/// use semver::Version;
///
/// fn main() {
/// let err = Version::parse("1.q.r").unwrap_err();
///
/// // "unexpected character 'q' while parsing minor version number"
/// eprintln!("{}", err);
/// }
/// ```
pub struct Error {
pub(crate) kind: ErrorKind,
}
impl FromStr for Version {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let mut pos = Position::Major;
let (major, text) = numeric_identifier(text, pos)?;
let text = dot(text, pos)?;
pos = Position::Minor;
let (minor, text) = numeric_identifier(text, pos)?;
let text = dot(text, pos)?;
pos = Position::Patch;
let (patch, text) = numeric_identifier(text, pos)?;
if text.is_empty() {
return Ok(Version::new(major, minor, patch));
}
let (pre, text) = if let Some(text) = text.strip_prefix('-') {
pos = Position::Pre;
let (pre, text) = prerelease_identifier(text)?;
if pre.is_empty() {
return Err(Error::new(ErrorKind::EmptySegment(pos)));
}
(pre, text)
} else {
(Prerelease::EMPTY, text)
};
let (build, text) = if let Some(text) = text.strip_prefix('+') {
pos = Position::Build;
let (build, text) = build_identifier(text)?;
if build.is_empty() {
return Err(Error::new(ErrorKind::EmptySegment(pos)));
}
(build, text)
} else {
(BuildMetadata::EMPTY, text)
};
if let Some(unexpected) = text.chars().next() {
return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
}
Ok(Version {
major,
minor,
patch,
pre,
build,
})
}
}
impl FromStr for VersionReq {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let text = text.trim_start_matches(' ');
if let Some((ch, text)) = wildcard(text) {
let rest = text.trim_start_matches(' ');
if rest.is_empty() {
#[cfg(not(no_const_vec_new))]
return Ok(VersionReq::STAR);
#[cfg(no_const_vec_new)] // rustc <1.39
return Ok(VersionReq {
comparators: Vec::new(),
});
} else if rest.starts_with(',') {
return Err(Error::new(ErrorKind::WildcardNotTheOnlyComparator(ch)));
} else {
return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
}
}
let depth = 0;
let mut comparators = Vec::new();
let len = version_req(text, &mut comparators, depth)?;
unsafe { comparators.set_len(len) }
Ok(VersionReq { comparators })
}
}
impl FromStr for Comparator {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let text = text.trim_start_matches(' ');
let (comparator, pos, rest) = comparator(text)?;
if !rest.is_empty() {
let unexpected = rest.chars().next().unwrap();
return Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)));
}
Ok(comparator)
}
}
impl FromStr for Prerelease {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let (pre, rest) = prerelease_identifier(text)?;
if !rest.is_empty() {
return Err(Error::new(ErrorKind::IllegalCharacter(Position::Pre)));
}
Ok(pre)
}
}
impl FromStr for BuildMetadata {
type Err = Error;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let (build, rest) = build_identifier(text)?;
if !rest.is_empty() {
return Err(Error::new(ErrorKind::IllegalCharacter(Position::Build)));
}
Ok(build)
}
}
impl Error {
fn new(kind: ErrorKind) -> Self {
Error { kind }
}
}
impl Op {
const DEFAULT: Self = Op::Caret;
}
fn numeric_identifier(input: &str, pos: Position) -> Result<(u64, &str), Error> {
let mut len = 0;
let mut value = 0u64;
while let Some(&digit) = input.as_bytes().get(len) {
if digit < b'0' || digit > b'9' {
break;
}
if value == 0 && len > 0 {
return Err(Error::new(ErrorKind::LeadingZero(pos)));
}
match value
.checked_mul(10)
.and_then(|value| value.checked_add((digit - b'0') as u64))
{
Some(sum) => value = sum,
None => return Err(Error::new(ErrorKind::Overflow(pos))),
}
len += 1;
}
if len > 0 {
Ok((value, &input[len..]))
} else if let Some(unexpected) = input[len..].chars().next() {
Err(Error::new(ErrorKind::UnexpectedChar(pos, unexpected)))
} else {
Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
}
}
fn wildcard(input: &str) -> Option<(char, &str)> {
if let Some(rest) = input.strip_prefix('*') {
Some(('*', rest))
} else if let Some(rest) = input.strip_prefix('x') {
Some(('x', rest))
} else if let Some(rest) = input.strip_prefix('X') {
Some(('X', rest))
} else {
None
}
}
fn dot(input: &str, pos: Position) -> Result<&str, Error> {
if let Some(rest) = input.strip_prefix('.') {
Ok(rest)
} else if let Some(unexpected) = input.chars().next() {
Err(Error::new(ErrorKind::UnexpectedCharAfter(pos, unexpected)))
} else {
Err(Error::new(ErrorKind::UnexpectedEnd(pos)))
}
}
fn prerelease_identifier(input: &str) -> Result<(Prerelease, &str), Error> {
let (string, rest) = identifier(input, Position::Pre)?;
let identifier = unsafe { Identifier::new_unchecked(string) };
Ok((Prerelease { identifier }, rest))
}
fn build_identifier(input: &str) -> Result<(BuildMetadata, &str), Error> {
let (string, rest) = identifier(input, Position::Build)?;
let identifier = unsafe { Identifier::new_unchecked(string) };
Ok((BuildMetadata { identifier }, rest))
}
fn identifier(input: &str, pos: Position) -> Result<(&str, &str), Error> {
let mut accumulated_len = 0;
let mut segment_len = 0;
let mut segment_has_nondigit = false;
loop {
match input.as_bytes().get(accumulated_len + segment_len) {
Some(b'A'..=b'Z') | Some(b'a'..=b'z') | Some(b'-') => {
segment_len += 1;
segment_has_nondigit = true;
}
Some(b'0'..=b'9') => {
segment_len += 1;
}
boundary => {
if segment_len == 0 {
if accumulated_len == 0 && boundary != Some(&b'.') {
return Ok(("", input));
} else {
return Err(Error::new(ErrorKind::EmptySegment(pos)));
}
}
if pos == Position::Pre
&& segment_len > 1
&& !segment_has_nondigit
&& input[accumulated_len..].starts_with('0')
{
return Err(Error::new(ErrorKind::LeadingZero(pos)));
}
accumulated_len += segment_len;
if boundary == Some(&b'.') {
accumulated_len += 1;
segment_len = 0;
segment_has_nondigit = false;
} else {
return Ok(input.split_at(accumulated_len));
}
}
}
}
}
fn op(input: &str) -> (Op, &str) {
let bytes = input.as_bytes();
if bytes.first() == Some(&b'=') {
(Op::Exact, &input[1..])
} else if bytes.first() == Some(&b'>') {
if bytes.get(1) == Some(&b'=') {
(Op::GreaterEq, &input[2..])
} else {
(Op::Greater, &input[1..])
}
} else if bytes.first() == Some(&b'<') {
if bytes.get(1) == Some(&b'=') {
(Op::LessEq, &input[2..])
} else {
(Op::Less, &input[1..])
}
} else if bytes.first() == Some(&b'~') {
(Op::Tilde, &input[1..])
} else if bytes.first() == Some(&b'^') {
(Op::Caret, &input[1..])
} else {
(Op::DEFAULT, input)
}
}
fn comparator(input: &str) -> Result<(Comparator, Position, &str), Error> {
let (mut op, text) = op(input);
let default_op = input.len() == text.len();
let text = text.trim_start_matches(' ');
let mut pos = Position::Major;
let (major, text) = numeric_identifier(text, pos)?;
let mut has_wildcard = false;
let (minor, text) = if let Some(text) = text.strip_prefix('.') {
pos = Position::Minor;
if let Some((_, text)) = wildcard(text) {
has_wildcard = true;
if default_op {
op = Op::Wildcard;
}
(None, text)
} else {
let (minor, text) = numeric_identifier(text, pos)?;
(Some(minor), text)
}
} else {
(None, text)
};
let (patch, text) = if let Some(text) = text.strip_prefix('.') {
pos = Position::Patch;
if let Some((_, text)) = wildcard(text) {
if default_op {
op = Op::Wildcard;
}
(None, text)
} else if has_wildcard {
return Err(Error::new(ErrorKind::UnexpectedAfterWildcard));
} else {
let (patch, text) = numeric_identifier(text, pos)?;
(Some(patch), text)
}
} else {
(None, text)
};
let (pre, text) = if patch.is_some() && text.starts_with('-') {
pos = Position::Pre;
let text = &text[1..];
let (pre, text) = prerelease_identifier(text)?;
if pre.is_empty() {
return Err(Error::new(ErrorKind::EmptySegment(pos)));
}
(pre, text)
} else {
(Prerelease::EMPTY, text)
};
let text = if patch.is_some() && text.starts_with('+') {
pos = Position::Build;
let text = &text[1..];
let (build, text) = build_identifier(text)?;
if build.is_empty() {
return Err(Error::new(ErrorKind::EmptySegment(pos)));
}
text
} else {
text
};
let text = text.trim_start_matches(' ');
let comparator = Comparator {
op,
major,
minor,
patch,
pre,
};
Ok((comparator, pos, text))
}
fn version_req(input: &str, out: &mut Vec<Comparator>, depth: usize) -> Result<usize, Error> {
let (comparator, pos, text) = match comparator(input) {
Ok(success) => success,
Err(mut error) => {
if let Some((ch, mut rest)) = wildcard(input) {
rest = rest.trim_start_matches(' ');
if rest.is_empty() || rest.starts_with(',') {
error.kind = ErrorKind::WildcardNotTheOnlyComparator(ch);
}
}
return Err(error);
}
};
if text.is_empty() {
out.reserve_exact(depth + 1);
unsafe { out.as_mut_ptr().add(depth).write(comparator) }
return Ok(depth + 1);
}
let text = if let Some(text) = text.strip_prefix(',') {
text.trim_start_matches(' ')
} else {
let unexpected = text.chars().next().unwrap();
return Err(Error::new(ErrorKind::ExpectedCommaFound(pos, unexpected)));
};
const MAX_COMPARATORS: usize = 32;
if depth + 1 == MAX_COMPARATORS {
return Err(Error::new(ErrorKind::ExcessiveComparators));
}
// Recurse to collect parsed Comparator objects on the stack. We perform a
// single allocation to allocate exactly the right sized Vec only once the
// total number of comparators is known.
let len = version_req(text, out, depth + 1)?;
unsafe { out.as_mut_ptr().add(depth).write(comparator) }
Ok(len)
}