wdl_modules/
version_requirement.rs1use std::fmt;
5use std::ops::Deref;
6use std::str::FromStr;
7
8use semver::VersionReq;
9use serde::Deserialize;
10use serde::Serialize;
11use thiserror::Error;
12
13#[derive(Debug, Error)]
15#[error("`{0}` is not a valid semantic version requirement")]
16pub struct VersionRequirementError(String);
17
18#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(into = "String", try_from = "String")]
21pub struct VersionRequirement(VersionReq);
22
23impl VersionRequirement {
24 pub fn into_inner(self) -> VersionReq {
27 self.0
28 }
29}
30
31impl Deref for VersionRequirement {
32 type Target = VersionReq;
33
34 fn deref(&self) -> &VersionReq {
35 &self.0
36 }
37}
38
39impl fmt::Display for VersionRequirement {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 self.0.fmt(f)
42 }
43}
44
45impl TryFrom<String> for VersionRequirement {
46 type Error = VersionRequirementError;
47
48 fn try_from(s: String) -> Result<Self, Self::Error> {
49 match VersionReq::parse(s.trim()) {
50 Ok(v) => Ok(Self(v)),
51 Err(_) => Err(VersionRequirementError(s)),
52 }
53 }
54}
55
56impl FromStr for VersionRequirement {
57 type Err = VersionRequirementError;
58
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
60 Self::try_from(s.to_string())
61 }
62}
63
64impl From<VersionRequirement> for String {
65 fn from(req: VersionRequirement) -> Self {
66 req.0.to_string()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use semver::Version;
73
74 use super::*;
75
76 #[test]
77 fn accepts_spec_operators() {
78 for s in [
79 "^1.2.0",
80 "~1.2.0",
81 "=1.2.0",
82 ">=1.0.0, <2.0.0",
83 ">1.0.0",
84 "<2.0.0",
85 ">=1.0.0",
86 "<=2.0.0",
87 "*",
88 "1.2.0",
89 ] {
90 assert!(s.parse::<VersionRequirement>().is_ok(), "rejected `{s}`");
91 }
92 }
93
94 #[test]
95 fn rejects_invalid_format() {
96 for bad in ["", " ", "not-a-req"] {
97 assert!(
98 bad.parse::<VersionRequirement>().is_err(),
99 "accepted `{bad}`"
100 );
101 }
102 }
103
104 #[test]
105 fn error_message_includes_input() {
106 let err = "^1.foo".parse::<VersionRequirement>().unwrap_err();
107 assert_eq!(
108 err.to_string(),
109 "`^1.foo` is not a valid semantic version requirement"
110 );
111 }
112
113 #[test]
114 fn matches_versions_correctly() {
115 let req: VersionRequirement = "^1.2.0".parse().unwrap();
116 assert!(req.matches(&Version::parse("1.2.0").unwrap()));
117 assert!(req.matches(&Version::parse("1.9.99").unwrap()));
118 assert!(!req.matches(&Version::parse("2.0.0").unwrap()));
119 assert!(!req.matches(&Version::parse("1.1.0").unwrap()));
120 }
121
122 #[test]
123 fn round_trips_via_serde() {
124 let req: VersionRequirement = "^1.2.0".parse().unwrap();
125 let json = serde_json::to_string(&req).unwrap();
126 assert_eq!(json, r#""^1.2.0""#);
127 let parsed: VersionRequirement = serde_json::from_str(&json).unwrap();
128 assert_eq!(parsed, req);
129 }
130}