use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use semver::VersionReq;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Error)]
#[error("`{0}` is not a valid semantic version requirement")]
pub struct VersionRequirementError(String);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
pub struct VersionRequirement(VersionReq);
impl VersionRequirement {
pub fn into_inner(self) -> VersionReq {
self.0
}
}
impl Deref for VersionRequirement {
type Target = VersionReq;
fn deref(&self) -> &VersionReq {
&self.0
}
}
impl fmt::Display for VersionRequirement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl TryFrom<String> for VersionRequirement {
type Error = VersionRequirementError;
fn try_from(s: String) -> Result<Self, Self::Error> {
match VersionReq::parse(s.trim()) {
Ok(v) => Ok(Self(v)),
Err(_) => Err(VersionRequirementError(s)),
}
}
}
impl FromStr for VersionRequirement {
type Err = VersionRequirementError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s.to_string())
}
}
impl From<VersionRequirement> for String {
fn from(req: VersionRequirement) -> Self {
req.0.to_string()
}
}
#[cfg(test)]
mod tests {
use semver::Version;
use super::*;
#[test]
fn accepts_spec_operators() {
for s in [
"^1.2.0",
"~1.2.0",
"=1.2.0",
">=1.0.0, <2.0.0",
">1.0.0",
"<2.0.0",
">=1.0.0",
"<=2.0.0",
"*",
"1.2.0",
] {
assert!(s.parse::<VersionRequirement>().is_ok(), "rejected `{s}`");
}
}
#[test]
fn rejects_invalid_format() {
for bad in ["", " ", "not-a-req"] {
assert!(
bad.parse::<VersionRequirement>().is_err(),
"accepted `{bad}`"
);
}
}
#[test]
fn error_message_includes_input() {
let err = "^1.foo".parse::<VersionRequirement>().unwrap_err();
assert_eq!(
err.to_string(),
"`^1.foo` is not a valid semantic version requirement"
);
}
#[test]
fn matches_versions_correctly() {
let req: VersionRequirement = "^1.2.0".parse().unwrap();
assert!(req.matches(&Version::parse("1.2.0").unwrap()));
assert!(req.matches(&Version::parse("1.9.99").unwrap()));
assert!(!req.matches(&Version::parse("2.0.0").unwrap()));
assert!(!req.matches(&Version::parse("1.1.0").unwrap()));
}
#[test]
fn round_trips_via_serde() {
let req: VersionRequirement = "^1.2.0".parse().unwrap();
let json = serde_json::to_string(&req).unwrap();
assert_eq!(json, r#""^1.2.0""#);
let parsed: VersionRequirement = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, req);
}
}