1#![allow(clippy::from_over_into)]
2
3use crate::spec_error::SpecError;
4use crate::unresolved_parser::*;
5use crate::version_types::*;
6use crate::{VersionSpec, clean_version_req_string, clean_version_string, is_alias_name};
7use compact_str::CompactString;
8use human_sort::compare;
9use semver::Prerelease;
10use semver::VersionReq;
11use serde::{Deserialize, Serialize};
12use std::cmp::Ordering;
13use std::fmt::{Debug, Display};
14use std::str::FromStr;
15
16#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
19#[serde(untagged, into = "String", try_from = "String")]
20pub enum UnresolvedVersionSpec {
21 Canary,
23 Alias(CompactString),
25 Req(VersionReq),
27 ReqAny(Vec<VersionReq>),
29 Calendar(CalVer),
31 Semantic(SemVer),
33}
34
35impl UnresolvedVersionSpec {
36 pub fn parse<T: AsRef<str>>(value: T) -> Result<Self, SpecError> {
48 Self::from_str(value.as_ref())
49 }
50
51 pub fn is_alias<A: AsRef<str>>(&self, name: A) -> bool {
53 match self {
54 Self::Alias(alias) => alias == name.as_ref(),
55 _ => false,
56 }
57 }
58
59 pub fn is_canary(&self) -> bool {
61 match self {
62 Self::Canary => true,
63 Self::Alias(alias) => alias == "canary",
64 _ => false,
65 }
66 }
67
68 pub fn is_fully_qualified(&self) -> bool {
71 matches!(self, Self::Calendar(_) | Self::Semantic(_))
72 }
73
74 pub fn is_latest(&self) -> bool {
76 match self {
77 Self::Alias(alias) => alias == "latest",
78 _ => false,
79 }
80 }
81
82 pub fn to_resolved_spec(&self) -> VersionSpec {
89 match self {
90 Self::Canary => VersionSpec::Canary,
91 Self::Alias(alias) => VersionSpec::Alias(CompactString::new(alias)),
92 Self::Calendar(version) => VersionSpec::Calendar(version.to_owned()),
93 Self::Semantic(version) => VersionSpec::Semantic(version.to_owned()),
94 _ => VersionSpec::default(),
95 }
96 }
97
98 pub fn to_partial_string(&self) -> String {
105 fn from_parts(
106 major: u64,
107 minor: Option<u64>,
108 patch: Option<u64>,
109 pre: &Prerelease,
110 ) -> String {
111 let mut version = format!("{major}");
112
113 minor.inspect(|m| {
114 version.push_str(&format!(".{m}"));
115 });
116
117 patch.inspect(|p| {
118 version.push_str(&format!(".{p}"));
119 });
120
121 if !pre.is_empty() {
122 version.push('-');
123 version.push_str(pre.as_str());
124 }
125
126 version
127 }
128
129 match self {
130 UnresolvedVersionSpec::Canary => "canary".into(),
131 UnresolvedVersionSpec::Alias(alias) => alias.to_string(),
132 UnresolvedVersionSpec::Req(req) => {
133 let req = req.comparators.first().unwrap();
134
135 from_parts(req.major, req.minor, req.patch, &req.pre)
136 }
137 UnresolvedVersionSpec::ReqAny(_) => "latest".into(),
138 UnresolvedVersionSpec::Calendar(ver) => {
139 from_parts(ver.major, Some(ver.minor), Some(ver.patch), &ver.pre)
140 }
141 UnresolvedVersionSpec::Semantic(ver) => {
142 from_parts(ver.major, Some(ver.minor), Some(ver.patch), &ver.pre)
143 }
144 }
145 }
146}
147
148#[cfg(feature = "schematic")]
149impl schematic::Schematic for UnresolvedVersionSpec {
150 fn schema_name() -> Option<String> {
151 Some("UnresolvedVersionSpec".into())
152 }
153
154 fn build_schema(mut schema: schematic::SchemaBuilder) -> schematic::Schema {
155 schema.set_description("Represents an unresolved version or alias that must be resolved to a fully-qualified version.");
156 schema.string_default()
157 }
158}
159
160impl Default for UnresolvedVersionSpec {
161 fn default() -> Self {
163 Self::Alias("latest".into())
164 }
165}
166
167impl FromStr for UnresolvedVersionSpec {
168 type Err = SpecError;
169
170 fn from_str(value: &str) -> Result<Self, Self::Err> {
171 if value == "canary" {
172 return Ok(UnresolvedVersionSpec::Canary);
173 }
174
175 let value = clean_version_string(value);
176
177 if is_alias_name(&value) {
178 return Ok(UnresolvedVersionSpec::Alias(CompactString::new(value)));
179 }
180
181 let value = clean_version_req_string(&value);
182
183 if value.contains("||") {
185 let mut reqs = vec![];
186
187 for result in parse_multi(&value)? {
188 reqs.push(VersionReq::parse(&result)?);
189 }
190
191 return Ok(UnresolvedVersionSpec::ReqAny(reqs));
192 }
193
194 let (result, kind) = parse(value)?;
196
197 Ok(match kind {
198 ParseKind::Req => UnresolvedVersionSpec::Req(VersionReq::parse(&result)?),
199 ParseKind::Cal => UnresolvedVersionSpec::Calendar(CalVer::parse(&result)?),
200 _ => UnresolvedVersionSpec::Semantic(SemVer::parse(&result)?),
201 })
202 }
203}
204
205impl TryFrom<String> for UnresolvedVersionSpec {
206 type Error = SpecError;
207
208 fn try_from(value: String) -> Result<Self, Self::Error> {
209 Self::from_str(&value)
210 }
211}
212
213impl Into<String> for UnresolvedVersionSpec {
214 fn into(self) -> String {
215 self.to_string()
216 }
217}
218
219impl Display for UnresolvedVersionSpec {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 match self {
222 Self::Canary => write!(f, "canary"),
223 Self::Alias(alias) => write!(f, "{alias}"),
224 Self::Req(req) => write!(f, "{req}"),
225 Self::ReqAny(reqs) => write!(
226 f,
227 "{}",
228 reqs.iter()
229 .map(|req| req.to_string())
230 .collect::<Vec<_>>()
231 .join(" || ")
232 ),
233 Self::Calendar(version) => write!(f, "{version}"),
234 Self::Semantic(version) => write!(f, "{version}"),
235 }
236 }
237}
238
239impl PartialEq<VersionSpec> for UnresolvedVersionSpec {
240 fn eq(&self, other: &VersionSpec) -> bool {
241 match (self, other) {
242 (Self::Canary, VersionSpec::Canary) => true,
243 (Self::Canary, VersionSpec::Alias(a)) => a == "canary",
244 (Self::Alias(a1), VersionSpec::Alias(a2)) => a1 == a2,
245 (Self::Calendar(v1), VersionSpec::Calendar(v2)) => v1 == v2,
246 (Self::Semantic(v1), VersionSpec::Semantic(v2)) => v1 == v2,
247 _ => false,
248 }
249 }
250}
251
252impl AsRef<UnresolvedVersionSpec> for UnresolvedVersionSpec {
253 fn as_ref(&self) -> &UnresolvedVersionSpec {
254 self
255 }
256}
257
258impl PartialOrd<UnresolvedVersionSpec> for UnresolvedVersionSpec {
259 fn partial_cmp(&self, other: &UnresolvedVersionSpec) -> Option<Ordering> {
260 Some(self.cmp(other))
261 }
262}
263
264impl Ord for UnresolvedVersionSpec {
265 fn cmp(&self, other: &Self) -> Ordering {
266 match (self, other) {
267 (Self::Canary, Self::Canary) => Ordering::Equal,
268 (Self::Alias(l), Self::Alias(r)) => l.cmp(r),
269 (Self::Calendar(l), Self::Calendar(r)) => l.cmp(r),
270 (Self::Semantic(l), Self::Semantic(r)) => l.cmp(r),
271 _ => compare(&self.to_string(), &other.to_string()),
272 }
273 }
274}