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 { 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 { 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 { 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 { 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 { 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, depth: usize) -> Result { 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) }