Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1055 lines (962 sloc) 25.4 KB
//! Version range and requirements data and functions (used for version comparison).
//!
//! This module contains [`Predicate`] struct which holds data for comparison
//! [`version::Version`] structs: [`VersionReq`] struct as a collection of
//! [`Predicate`]s, functions for parsing those structs and some helper data structures
//! and functions.
//!
//! # Examples
//!
//! Parsing version range and matching it with concrete version:
//!
//! ```
//! use semver_parser::range;
//! use semver_parser::version;
//!
//! # fn try_main() -> Result<(), String> {
//! let r = range::parse("1.0.0")?;
//!
//! assert_eq!(range::Predicate {
//! op: range::Op::Compatible,
//! major: 1,
//! minor: Some(0),
//! patch: Some(0),
//! pre: Vec::new(),
//! },
//! r.predicates[0]
//! );
//!
//! let m = version::parse("1.0.0")?;
//! for p in &r.predicates {
//! match p.op {
//! range::Op::Compatible => {
//! assert_eq!(p.major, m.major);
//! }
//! _ => {
//! unimplemented!();
//! }
//! }
//! }
//! # Ok(())
//! # }
//! #
//! # fn main() {
//! # try_main().unwrap();
//! # }
//! ```
//!
//! [`Predicate`]: ./struct.Predicate.html
//! [`VersionReq`]: ./struct.VersionReq.html
//! [`version::Version`]: ../version/struct.Version.html
use parser::{self, Parser};
use std::str::FromStr;
use version::Identifier;
/// Struct holding collection of version requirements.
///
/// High-level collection of requirements for versions. Requirements are [`Predicate`] structs.
///
/// # Examples
///
/// Simple single-predicate `VersionReq`:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let r = range::parse("1.0.0")?;
///
/// assert_eq!(range::Predicate {
/// op: range::Op::Compatible,
/// major: 1,
/// minor: Some(0),
/// patch: Some(0),
/// pre: Vec::new(),
/// },
/// r.predicates[0]
/// );
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
///
/// Multiple predicates in `VersionReq`:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let r = range::parse("> 0.0.9, <= 2.5.3")?;
///
/// assert_eq!(range::Predicate {
/// op: range::Op::Gt,
/// major: 0,
/// minor: Some(0),
/// patch: Some(9),
/// pre: Vec::new(),
/// },
/// r.predicates[0]
/// );
///
/// assert_eq!(range::Predicate {
/// op: range::Op::LtEq,
/// major: 2,
/// minor: Some(5),
/// patch: Some(3),
/// pre: Vec::new(),
/// },
/// r.predicates[1]
/// );
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
///
/// [`Predicate`]: ./struct.Predicate.html
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct VersionReq {
/// Collection of predicates.
pub predicates: Vec<Predicate>,
}
/// Enum representing a `*` version part.
///
/// This is one of variants of the [`Op`] enum wich is part of [`Predicate`] enum.
/// All variants represent some "match-all" cases for specific numeric parts of version.
///
/// # Examples
///
/// Parsing wildcard predicate and checking that its predicates are empty.
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let r = range::parse("*")?;
///
/// assert!(r.predicates.is_empty());
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
/// [`Op`]: ./enum.Op.html
/// [`Predicate`]: ./struct.Predicate.html
#[derive(PartialOrd, Ord, PartialEq, Eq, Debug, Hash, Clone)]
pub enum WildcardVersion {
/// Wildcard minor version `1.*.3`.
Minor,
/// Wildcard patch version `1.2.*`.
Patch,
}
/// Enum representing operation in [`Predicate`].
///
/// This enum represents an operation for comparing two [`version::Version`]s.
///
/// # Examples
///
/// Parsing `Op` from string:
///
/// ```
/// use semver_parser::range;
/// use std::str::FromStr;
///
/// # fn try_main() -> Result<(), String> {
/// let exact = range::Op::from_str("=")?;
/// assert_eq!(exact, range::Op::Ex);
/// let gt_eq = range::Op::from_str(">=")?;
/// assert_eq!(gt_eq, range::Op::GtEq);
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
/// [`Predicate`]: ./struct.Predicate.html
/// [`version::Version`]: ../version/struct.Version.html
#[derive(PartialOrd, Ord, PartialEq, Eq, Debug, Clone, Hash)]
pub enum Op {
/// Exact, `=`.
Ex,
/// Greater than, `>`.
Gt,
/// Greater than or equal to, `>=`.
GtEq,
/// Less than, `<`.
Lt,
/// Less than or equal to, `<=`.
LtEq,
/// [Tilde](http://doc.crates.io/specifying-dependencies.html#tilde-requirements)
/// requirements, like `~1.0.0` - a minimal version with some ability to update.
Tilde,
/// [Compatible](http://doc.crates.io/specifying-dependencies.html#caret-requirements)
/// by definition of semver, indicated by `^`.
Compatible,
/// `x.y.*`, `x.*`, `*`.
Wildcard(WildcardVersion),
}
impl FromStr for Op {
type Err = String;
fn from_str(s: &str) -> Result<Op, String> {
match s {
"=" => Ok(Op::Ex),
">" => Ok(Op::Gt),
">=" => Ok(Op::GtEq),
"<" => Ok(Op::Lt),
"<=" => Ok(Op::LtEq),
"~" => Ok(Op::Tilde),
"^" => Ok(Op::Compatible),
_ => Err(String::from("Could not parse Op")),
}
}
}
/// Struct representing a version comparison predicate.
///
/// Struct contaions operation code and data for comparison of [`version::Version`]s.
///
/// # Examples
///
/// Parsing [`Predicate`] from string and checking its fields:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let p = range::parse_predicate(">=1.1")?.expect("non-empty");
/// assert_eq!(p.op, range::Op::GtEq);
/// assert_eq!(p.major, 1);
/// assert_eq!(p.minor.unwrap(), 1);
/// assert!(p.patch.is_none());
/// assert!(p.pre.is_empty());
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
/// [`Predicate`]: ./struct.Predicate.html
/// [`version::Version`]: ../version/struct.Version.html
#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
pub struct Predicate {
/// Operation code for this predicate, like "greater than" or "exact match".
pub op: Op,
/// Major version.
pub major: u64,
/// Optional minor version.
pub minor: Option<u64>,
/// Optional patch version.
pub patch: Option<u64>,
/// Collection of `Identifier`s of version, like `"alpha1"` in `"1.2.3-alpha1"`.
pub pre: Vec<Identifier>,
}
/// Function parsing [`Predicate`] from string.
///
/// Function parsing [`Predicate`] from string to `Result<`[`Predicate`]`, String>`,
/// where `Err` will contain error message in case of failed parsing.
///
/// # Examples
///
/// Parsing [`Predicate`] from string and cheking its fields:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let p = range::parse_predicate(">=1.1")?.expect("non-empty");
/// assert_eq!(p.op, range::Op::GtEq);
/// assert_eq!(p.major, 1);
/// assert_eq!(p.minor.unwrap(), 1);
/// assert!(p.patch.is_none());
/// assert!(p.pre.is_empty());
///
/// let f = range::parse_predicate("not-a-version-predicate");
/// assert!(f.is_err());
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
/// [`Predicate`]: ./struct.Predicate.html
pub fn parse_predicate<'input>(
input: &'input str,
) -> Result<Option<Predicate>, parser::Error<'input>> {
let mut parser = Parser::new(input)?;
let predicate = parser.predicate()?;
if !parser.is_eof() {
return Err(parser::Error::MoreInput(parser.tail()?));
}
Ok(predicate)
}
/// Function for parsing [`VersionReq`] from string.
///
/// Function for parsing [`VersionReq`] from string to `Result<`[`VersionReq`]`, String>`,
/// where `Err` will contain error message in case of failed parsing.
///
/// # Examples
///
/// Simple single-predicate [`VersionReq`]:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let r = range::parse("1.0.0")?;
///
/// assert_eq!(range::Predicate {
/// op: range::Op::Compatible,
/// major: 1,
/// minor: Some(0),
/// patch: Some(0),
/// pre: Vec::new(),
/// },
/// r.predicates[0]
/// );
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
///
/// Multiple predicates in [`VersionReq`]:
///
/// ```
/// use semver_parser::range;
///
/// # fn try_main() -> Result<(), String> {
/// let r = range::parse("> 0.0.9, <= 2.5.3")?;
///
/// assert_eq!(range::Predicate {
/// op: range::Op::Gt,
/// major: 0,
/// minor: Some(0),
/// patch: Some(9),
/// pre: Vec::new(),
/// },
/// r.predicates[0]
/// );
///
/// assert_eq!(range::Predicate {
/// op: range::Op::LtEq,
/// major: 2,
/// minor: Some(5),
/// patch: Some(3),
/// pre: Vec::new(),
/// },
/// r.predicates[1]
/// );
/// # Ok(())
/// # }
/// #
/// # fn main() {
/// # try_main().unwrap();
/// # }
/// ```
///
/// [`VersionReq`]: ./struct.VersionReq.html
pub fn parse<'input>(input: &'input str) -> Result<VersionReq, parser::Error<'input>> {
let mut parser = Parser::new(input)?;
let range = parser.range()?;
if !parser.is_eof() {
return Err(parser::Error::MoreInput(parser.tail()?));
}
Ok(range)
}
#[cfg(test)]
mod tests {
use super::*;
use range;
use version::Identifier;
#[test]
fn test_parsing_wildcards() {
assert_eq!(
Op::Wildcard(WildcardVersion::Patch),
range::parse("1.0.*").unwrap().predicates[0].op
);
assert_eq!(
Op::Wildcard(WildcardVersion::Patch),
range::parse("1.*.*").unwrap().predicates[0].op
);
assert_eq!(
Op::Wildcard(WildcardVersion::Minor),
parse("1.*.0").unwrap().predicates[0].op
);
}
#[test]
fn test_parsing_default() {
let r = range::parse("1.0.0").unwrap();
assert_eq!(
Predicate {
op: Op::Compatible,
major: 1,
minor: Some(0),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_exact_01() {
let r = range::parse("=1.0.0").unwrap();
assert_eq!(
Predicate {
op: Op::Ex,
major: 1,
minor: Some(0),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_exact_02() {
let r = range::parse("=0.9.0").unwrap();
assert_eq!(
Predicate {
op: Op::Ex,
major: 0,
minor: Some(9),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_exact_03() {
let r = range::parse("=0.1.0-beta2.a").unwrap();
assert_eq!(
Predicate {
op: Op::Ex,
major: 0,
minor: Some(1),
patch: Some(0),
pre: vec![
Identifier::AlphaNumeric(String::from("beta2")),
Identifier::AlphaNumeric(String::from("a")),
],
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_greater_than() {
let r = range::parse("> 1.0.0").unwrap();
assert_eq!(
Predicate {
op: Op::Gt,
major: 1,
minor: Some(0),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_greater_than_01() {
let r = range::parse(">= 1.0.0").unwrap();
assert_eq!(
Predicate {
op: Op::GtEq,
major: 1,
minor: Some(0),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_greater_than_02() {
let r = range::parse(">= 2.1.0-alpha2").unwrap();
assert_eq!(
Predicate {
op: Op::GtEq,
major: 2,
minor: Some(1),
patch: Some(0),
pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_less_than() {
let r = range::parse("< 1.0.0").unwrap();
assert_eq!(
Predicate {
op: Op::Lt,
major: 1,
minor: Some(0),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_less_than_eq() {
let r = range::parse("<= 2.1.0-alpha2").unwrap();
assert_eq!(
Predicate {
op: Op::LtEq,
major: 2,
minor: Some(1),
patch: Some(0),
pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_tilde() {
let r = range::parse("~1").unwrap();
assert_eq!(
Predicate {
op: Op::Tilde,
major: 1,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
pub fn test_parsing_compatible() {
let r = range::parse("^0").unwrap();
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_blank() {
let r = range::parse("").unwrap();
assert!(r.predicates.is_empty());
}
#[test]
fn test_parsing_wildcard() {
let r = range::parse("*").unwrap();
assert!(r.predicates.is_empty());
}
#[test]
fn test_uppercase_prereleases() {
assert_eq!(
vec![Identifier::AlphaNumeric("Foo".to_string())],
range::parse("0-Foo").unwrap().predicates[0].pre
);
assert_eq!(
vec![Identifier::AlphaNumeric("X".to_string())],
range::parse("0-X").unwrap().predicates[0].pre
);
}
#[test]
fn test_empty_prerelease() {
assert!(range::parse("0-").is_err());
}
#[test]
fn test_parsing_x() {
let r = range::parse("x").unwrap();
assert!(r.predicates.is_empty());
}
#[test]
fn test_parsing_capital_x() {
let r = range::parse("X").unwrap();
assert!(r.predicates.is_empty());
}
/// TODO: this should probably be using WildcardVersion::Minor
#[test]
fn test_parsing_wildcard_star_star() {
let r = range::parse("1.*.*").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Patch),
major: 1,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_minor_wildcard_star() {
let r = range::parse("1.*").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Minor),
major: 1,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_minor_wildcard_star_patch() {
let r = range::parse("1.*.0").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Minor),
major: 1,
minor: None,
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_minor_wildcard_x() {
let r = range::parse("1.x").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Minor),
major: 1,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_minor_wildcard_capital_x() {
let r = range::parse("1.X").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Minor),
major: 1,
minor: None,
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_patch_wildcard_star() {
let r = range::parse("1.2.*").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Patch),
major: 1,
minor: Some(2),
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_patch_wildcard_x() {
let r = range::parse("1.2.x").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Patch),
major: 1,
minor: Some(2),
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
fn test_parsing_patch_wildcard_capital_x() {
let r = range::parse("1.2.X").unwrap();
assert_eq!(
Predicate {
op: Op::Wildcard(WildcardVersion::Patch),
major: 1,
minor: Some(2),
patch: None,
pre: Vec::new(),
},
r.predicates[0]
);
}
#[test]
pub fn test_multiple_01() {
let r = range::parse("> 0.0.9, <= 2.5.3").unwrap();
assert_eq!(
Predicate {
op: Op::Gt,
major: 0,
minor: Some(0),
patch: Some(9),
pre: Vec::new(),
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::LtEq,
major: 2,
minor: Some(5),
patch: Some(3),
pre: Vec::new(),
},
r.predicates[1]
);
}
#[test]
pub fn test_multiple_02() {
let r = range::parse("0.3.0, 0.4.0").unwrap();
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: Some(3),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: Some(4),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[1]
);
}
#[test]
pub fn test_multiple_03() {
let r = range::parse("<= 0.2.0, >= 0.5.0").unwrap();
assert_eq!(
Predicate {
op: Op::LtEq,
major: 0,
minor: Some(2),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::GtEq,
major: 0,
minor: Some(5),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[1]
);
}
#[test]
pub fn test_multiple_04() {
let r = range::parse("0.1.0, 0.1.4, 0.1.6").unwrap();
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: Some(1),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: Some(1),
patch: Some(4),
pre: Vec::new(),
},
r.predicates[1]
);
assert_eq!(
Predicate {
op: Op::Compatible,
major: 0,
minor: Some(1),
patch: Some(6),
pre: Vec::new(),
},
r.predicates[2]
);
}
#[test]
pub fn test_multiple_05() {
let r = range::parse(">=0.5.1-alpha3, <0.6").unwrap();
assert_eq!(
Predicate {
op: Op::GtEq,
major: 0,
minor: Some(5),
patch: Some(1),
pre: vec![Identifier::AlphaNumeric(String::from("alpha3"))],
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::Lt,
major: 0,
minor: Some(6),
patch: None,
pre: Vec::new(),
},
r.predicates[1]
);
}
#[test]
pub fn test_multiple_06() {
let r = range::parse("<= 0.2.0 >= 0.5.0").unwrap();
assert_eq!(
Predicate {
op: Op::LtEq,
major: 0,
minor: Some(2),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[0]
);
assert_eq!(
Predicate {
op: Op::GtEq,
major: 0,
minor: Some(5),
patch: Some(0),
pre: Vec::new(),
},
r.predicates[1]
);
}
#[test]
fn test_parse_build_metadata_with_predicate() {
assert_eq!(
range::parse("^1.2.3+meta").unwrap().predicates[0].op,
Op::Compatible
);
assert_eq!(
range::parse("~1.2.3+meta").unwrap().predicates[0].op,
Op::Tilde
);
assert_eq!(
range::parse("=1.2.3+meta").unwrap().predicates[0].op,
Op::Ex
);
assert_eq!(
range::parse("<=1.2.3+meta").unwrap().predicates[0].op,
Op::LtEq
);
assert_eq!(
range::parse(">=1.2.3+meta").unwrap().predicates[0].op,
Op::GtEq
);
assert_eq!(
range::parse("<1.2.3+meta").unwrap().predicates[0].op,
Op::Lt
);
assert_eq!(
range::parse(">1.2.3+meta").unwrap().predicates[0].op,
Op::Gt
);
}
#[test]
pub fn test_parse_errors() {
assert!(range::parse("\0").is_err());
assert!(range::parse(">= >= 0.0.2").is_err());
assert!(range::parse(">== 0.0.2").is_err());
assert!(range::parse("a.0.0").is_err());
assert!(range::parse("1.0.0-").is_err());
assert!(range::parse(">=").is_err());
assert!(range::parse("> 0.1.0,").is_err());
assert!(range::parse("> 0.3.0, ,").is_err());
assert!(range::parse("> 0. 1").is_err());
}
#[test]
pub fn test_large_major_version() {
assert!(range::parse("18446744073709551617.0.0").is_err());
}
#[test]
pub fn test_large_minor_version() {
assert!(range::parse("0.18446744073709551617.0").is_err());
}
#[test]
pub fn test_large_patch_version() {
assert!(range::parse("0.0.18446744073709551617").is_err());
}
#[test]
pub fn test_op_partialord_lt() {
let expect_less = Op::Ex;
let other = Op::Gt;
assert!(expect_less.lt(&other));
}
#[test]
pub fn test_op_partialord_le() {
let strictly_lt = Op::Ex;
let other = Op::Lt;
assert!(strictly_lt.le(&other));
assert!(other.le(&other));
}
#[test]
pub fn test_op_partialord_gt() {
let expect_gt = Op::Compatible;
let other = Op::GtEq;
assert!(expect_gt.gt(&other));
}
#[test]
pub fn test_op_partialord_ge() {
let strictly_gt = Op::Compatible;
let other = Op::Tilde;
assert!(strictly_gt.ge(&other));
assert!(other.ge(&other));
}
#[test]
pub fn test_wildcard_partialord_lt() {
let expect_less = WildcardVersion::Minor;
let other = WildcardVersion::Patch;
assert!(expect_less.lt(&other));
}
#[test]
pub fn test_wildcard_partialord_le() {
let strictly_lt = WildcardVersion::Minor;
let other = WildcardVersion::Patch;
assert!(strictly_lt.le(&other));
assert!(other.le(&other));
}
#[test]
pub fn test_wildcard_partialord_gt() {
let expect_greater = WildcardVersion::Patch;
let other = WildcardVersion::Minor;
assert!(expect_greater.gt(&other));
}
#[test]
pub fn test_wildcard_partialord_ge() {
let strictly_gt = WildcardVersion::Patch;
let other = WildcardVersion::Minor;
assert!(strictly_gt.ge(&other));
assert!(other.ge(&other));
}
}