use std::{
fmt::{self, Display},
str::FromStr,
};
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use crate::{
error::{Error, Result},
version::Version,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub enum Op {
Exact,
Greater,
GreaterEq,
Less,
LessEq,
Compatible,
}
impl Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Op::Exact => write!(f, "="),
Op::Greater => write!(f, ">"),
Op::GreaterEq => write!(f, ">="),
Op::Less => write!(f, "<"),
Op::LessEq => write!(f, "<="),
Op::Compatible => write!(f, "~>"),
}
}
}
impl FromStr for Op {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"=" => Ok(Op::Exact),
">" => Ok(Op::Greater),
">=" => Ok(Op::GreaterEq),
"<" => Ok(Op::Less),
"<=" => Ok(Op::LessEq),
"~>" => Ok(Op::Compatible),
"" => Ok(Op::Exact), op => Err(Error::InvalidOperator {
operator: op.to_string(),
}),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Comparator {
pub op: Op,
pub version: Version,
}
impl Comparator {
pub fn new(op: Op, version: Version) -> Self {
Self {
op,
version,
}
}
pub fn parse(input: &str) -> Result<Self> {
if input.is_empty() {
return Err(Error::EmptyRequirement);
}
let (op_str, version_str) = if let Some(stripped) = input.strip_prefix(">=") {
(">=", stripped)
} else if let Some(stripped) = input.strip_prefix(">") {
(">", stripped)
} else if let Some(stripped) = input.strip_prefix("<=") {
("<=", stripped)
} else if let Some(stripped) = input.strip_prefix("<") {
("<", stripped)
} else if let Some(stripped) = input.strip_prefix("=") {
("=", stripped)
} else if let Some(stripped) = input.strip_prefix("~>") {
("~>", stripped)
} else {
("", input)
};
let op = op_str.parse::<Op>()?;
let version_str = version_str.trim();
if version_str.is_empty() {
return Err(Error::invalid_version("empty version after operator"));
}
let version = Version::parse(version_str).map_err(|_| Error::invalid_requirement(input))?;
Ok(Self {
op,
version,
})
}
pub fn matches(&self, version: &Version) -> bool {
match self.op {
Op::Exact => version == &self.version,
Op::Greater => version > &self.version,
Op::GreaterEq => version >= &self.version,
Op::Less => version < &self.version,
Op::LessEq => version <= &self.version,
Op::Compatible =>
version.year == self.version.year
&& version.week == self.version.week
&& version.patch >= self.version.patch,
}
}
}
impl Display for Comparator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.op, self.version)
}
}
impl FromStr for Comparator {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct VersionReq {
pub comparators: Vec<Comparator>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct AnyVersionReq {
requirements: Vec<VersionReq>,
}
impl VersionReq {
pub fn new() -> Self {
Self {
comparators: Vec::new(),
}
}
pub fn parse<S: AsRef<str>>(input: S) -> Result<Self> {
let input = input.as_ref().trim();
if input == "*" {
return Ok(Self {
comparators: vec![],
});
}
if input.is_empty() {
return Ok(Self {
comparators: vec![],
});
}
let mut comparators = Vec::new();
for part in input.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
let comparator =
Comparator::parse(part).map_err(|_| Error::invalid_requirement(input))?;
comparators.push(comparator);
}
if comparators.is_empty() {
return Err(Error::EmptyRequirement);
}
Ok(Self {
comparators,
})
}
pub fn matches(&self, version: &Version) -> bool {
if self.comparators.is_empty() {
return true;
}
self.comparators.iter().all(|c| c.matches(version))
}
pub fn add_comparator(&mut self, comparator: Comparator) -> &mut Self {
self.comparators.push(comparator);
self
}
pub fn with<S: AsRef<str>>(&mut self, comparator: S) -> Result<&mut Self> {
let comp = Comparator::parse(comparator.as_ref())?;
self.comparators.push(comp);
Ok(self)
}
pub fn any<I, S>(requirements: I) -> Result<AnyVersionReq>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let mut result = Vec::new();
for req_str in requirements {
let req = Self::parse(req_str)?;
result.push(req);
}
Ok(AnyVersionReq {
requirements: result,
})
}
}
impl AnyVersionReq {
pub fn new() -> Self {
Self {
requirements: Vec::new(),
}
}
pub fn matches(&self, version: &Version) -> bool {
if self.requirements.is_empty() {
return true;
}
self.requirements.iter().any(|req| req.matches(version))
}
pub fn with<S: AsRef<str>>(&mut self, requirement: S) -> Result<&mut Self> {
let req = VersionReq::parse(requirement.as_ref())?;
self.requirements.push(req);
Ok(self)
}
}
impl Default for VersionReq {
fn default() -> Self {
Self::new()
}
}
impl Default for AnyVersionReq {
fn default() -> Self {
Self::new()
}
}
impl Display for VersionReq {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.comparators.is_empty() {
return write!(f, "*");
}
let mut first = true;
for comparator in &self.comparators {
if !first {
write!(f, ",")?;
}
first = false;
write!(f, "{comparator}")?;
}
Ok(())
}
}
impl Display for AnyVersionReq {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.requirements.is_empty() {
return write!(f, "*");
}
let mut first = true;
for req in &self.requirements {
if !first {
write!(f, " || ")?;
}
first = false;
write!(f, "{req}")?;
}
Ok(())
}
}
impl FromStr for VersionReq {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::parse(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_op_parse() {
assert_eq!("=".parse::<Op>().unwrap(), Op::Exact);
assert_eq!(">".parse::<Op>().unwrap(), Op::Greater);
assert_eq!(">=".parse::<Op>().unwrap(), Op::GreaterEq);
assert_eq!("<".parse::<Op>().unwrap(), Op::Less);
assert_eq!("<=".parse::<Op>().unwrap(), Op::LessEq);
assert_eq!("~>".parse::<Op>().unwrap(), Op::Compatible);
assert_eq!("".parse::<Op>().unwrap(), Op::Exact);
assert!("!!".parse::<Op>().is_err());
}
#[test]
fn test_comparator_parse() {
let c = Comparator::parse(">=25.10.0").unwrap();
assert_eq!(c.op, Op::GreaterEq);
assert_eq!(c.version, Version::parse("25.10.0").unwrap());
let c = Comparator::parse("25.10.0").unwrap(); assert_eq!(c.op, Op::Exact);
assert_eq!(c.version, Version::parse("25.10.0").unwrap());
assert!(Comparator::parse("").is_err());
assert!(Comparator::parse(">=").is_err());
assert!(Comparator::parse("!25.10.0").is_err());
}
#[test]
fn test_matches_exact() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse("=25.10.1").unwrap();
assert!(req.matches(&version));
let version2 = Version::parse("25.10.2").unwrap();
assert!(!req.matches(&version2));
}
#[test]
fn test_matches_greater() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse(">25.9.0").unwrap();
assert!(req.matches(&version));
let req2 = VersionReq::parse(">25.10.1").unwrap();
assert!(!req2.matches(&version));
}
#[test]
fn test_matches_greater_eq() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse(">=25.10.1").unwrap();
assert!(req.matches(&version));
let req2 = VersionReq::parse(">=25.10.2").unwrap();
assert!(!req2.matches(&version));
}
#[test]
fn test_matches_less() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse("<25.11.0").unwrap();
assert!(req.matches(&version));
let req2 = VersionReq::parse("<25.10.1").unwrap();
assert!(!req2.matches(&version));
}
#[test]
fn test_matches_less_eq() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse("<=25.10.1").unwrap();
assert!(req.matches(&version));
let req2 = VersionReq::parse("<=25.10.0").unwrap();
assert!(!req2.matches(&version));
}
#[test]
fn test_matches_compatible() {
let version = Version::parse("25.10.1").unwrap();
let req = VersionReq::parse("~>25.10.0").unwrap();
assert!(req.matches(&version));
let version2 = Version::parse("25.10.0").unwrap();
assert!(req.matches(&version2));
let version3 = Version::parse("25.11.0").unwrap();
assert!(!req.matches(&version3));
}
#[test]
fn test_multiple_comparators() {
let version = Version::parse("25.10.5").unwrap();
let req = VersionReq::parse(">=25.10.0,<25.11.0").unwrap();
assert!(req.matches(&version));
let version2 = Version::parse("25.9.0").unwrap();
assert!(!req.matches(&version2));
let version3 = Version::parse("25.11.0").unwrap();
assert!(!req.matches(&version3));
}
#[test]
fn test_display() {
let req = VersionReq::parse(">=25.10.0,<25.11.0").unwrap();
assert_eq!(req.to_string(), ">=25.10.0,<25.11.0");
let req2 = VersionReq::parse("").unwrap();
assert_eq!(req2.to_string(), "*");
}
#[test]
fn test_with_method() {
let mut req = VersionReq::new();
req.with(">=25.10.0").unwrap().with("<25.11.0").unwrap();
assert_eq!(req.to_string(), ">=25.10.0,<25.11.0");
}
#[test]
fn test_any() {
let req = VersionReq::any([">25.15.0", "<25.5.0"]).unwrap();
let v1 = Version::parse("25.16.0").unwrap();
let v2 = Version::parse("25.4.0").unwrap();
let v3 = Version::parse("25.10.0").unwrap();
assert!(req.matches(&v1)); assert!(req.matches(&v2)); assert!(!req.matches(&v3)); }
#[test]
fn test_any_display() {
let req = VersionReq::any([">25.15.0", "<25.5.0"]).unwrap();
assert_eq!(req.to_string(), ">25.15.0 || <25.5.0");
let empty: [&str; 0] = [];
let req2 = VersionReq::any(empty).unwrap();
assert_eq!(req2.to_string(), "*");
}
}