use crate::resolved_spec::VersionSpec;
use crate::spec_error::SpecError;
use crate::syntax::*;
use crate::syntax_parser::parse_alias;
use crate::syntax_traits::{FormatOptions, FormatsVersion};
use compact_str::CompactString;
use human_sort::compare;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Debug, Display};
use std::str::FromStr;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(untagged, into = "String", try_from = "String")]
pub enum UnresolvedVersionSpec {
Canary,
Alias(CompactString),
Range(Range),
Requirement(Requirement),
Version(Version),
}
impl UnresolvedVersionSpec {
pub fn parse<T: AsRef<str>>(value: T) -> Result<Self, SpecError> {
Self::from_str(value.as_ref())
}
pub fn get_scope(&self) -> Option<&str> {
match self {
Self::Range(range) => range.get_scope(),
Self::Requirement(req) => req.scope.as_deref(),
Self::Version(version) => version.scope.as_deref(),
_ => None,
}
}
pub fn is_alias<A: AsRef<str>>(&self, name: A) -> bool {
match self {
Self::Alias(alias) => alias == name.as_ref(),
_ => false,
}
}
pub fn is_canary(&self) -> bool {
match self {
Self::Canary => true,
Self::Alias(alias) => alias == "canary",
_ => false,
}
}
pub fn is_fully_qualified(&self) -> bool {
matches!(self, Self::Version(_))
}
pub fn is_latest(&self) -> bool {
match self {
Self::Alias(alias) => alias == "latest",
_ => false,
}
}
pub fn set_scope(&mut self, scope: impl AsRef<str>) {
match self {
Self::Range(range) => {
range.set_scope(scope);
}
Self::Requirement(req) => {
req.scope = Some(scope.as_ref().into());
}
Self::Version(version) => {
version.scope = Some(scope.as_ref().into());
}
_ => {}
}
}
pub fn to_resolved_spec(&self) -> VersionSpec {
match self {
Self::Canary => VersionSpec::Canary,
Self::Alias(alias) => VersionSpec::Alias(alias.to_owned()),
Self::Version(version) => VersionSpec::Version(version.to_owned()),
_ => VersionSpec::default(),
}
}
pub fn to_partial_string(&self) -> String {
match self {
UnresolvedVersionSpec::Canary => "canary".into(),
UnresolvedVersionSpec::Alias(alias) => alias.to_string(),
UnresolvedVersionSpec::Range(_) => "latest".into(),
UnresolvedVersionSpec::Requirement(req) if req.major.is_none() => "latest".into(),
UnresolvedVersionSpec::Requirement(req) => {
let mut options = FormatOptions::new(req.kind);
options.include_op = false;
options.include_build = false;
req.to_formatted_string(&options)
}
UnresolvedVersionSpec::Version(ver) => {
let mut options = FormatOptions::new(ver.kind);
options.include_build = false;
ver.to_formatted_string(&options)
}
}
}
}
#[cfg(feature = "schematic")]
impl schematic::Schematic for UnresolvedVersionSpec {
fn schema_name() -> Option<String> {
Some("UnresolvedVersionSpec".into())
}
fn build_schema(mut schema: schematic::SchemaBuilder) -> schematic::Schema {
schema.set_description("Represents an unresolved version or alias that must be resolved to a fully-qualified version.");
schema.string_default()
}
}
impl Default for UnresolvedVersionSpec {
fn default() -> Self {
Self::Alias("latest".into())
}
}
impl FromStr for UnresolvedVersionSpec {
type Err = SpecError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value == "canary" {
return Ok(UnresolvedVersionSpec::Canary);
}
if let Ok(version) = Version::parse(value) {
return Ok(Self::Version(version));
}
let error = match Requirement::parse(value) {
Ok(req) => return Ok(Self::Requirement(req)),
Err(error) => error,
};
if let Ok(range) = Range::parse(value) {
return Ok(Self::Range(range));
}
match parse_alias(value) {
Ok(alias) => Ok(Self::Alias(alias)),
Err(_) => Err(error),
}
}
}
impl TryFrom<String> for UnresolvedVersionSpec {
type Error = SpecError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_str(&value)
}
}
impl From<UnresolvedVersionSpec> for String {
fn from(value: UnresolvedVersionSpec) -> Self {
value.to_string()
}
}
impl Display for UnresolvedVersionSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Canary => write!(f, "canary"),
Self::Alias(alias) => write!(f, "{alias}"),
Self::Range(range) => write!(f, "{range}"),
Self::Requirement(req) => write!(f, "{req}"),
Self::Version(version) => write!(f, "{version}"),
}
}
}
impl PartialEq<VersionSpec> for UnresolvedVersionSpec {
fn eq(&self, other: &VersionSpec) -> bool {
match (self, other) {
(Self::Canary, VersionSpec::Canary) => true,
(Self::Canary, VersionSpec::Alias(a)) => a == "canary",
(Self::Alias(a1), VersionSpec::Alias(a2)) => a1 == a2,
(Self::Version(v1), VersionSpec::Version(v2)) => v1 == v2,
_ => false,
}
}
}
impl AsRef<UnresolvedVersionSpec> for UnresolvedVersionSpec {
fn as_ref(&self) -> &UnresolvedVersionSpec {
self
}
}
impl PartialOrd<UnresolvedVersionSpec> for UnresolvedVersionSpec {
fn partial_cmp(&self, other: &UnresolvedVersionSpec) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for UnresolvedVersionSpec {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Canary, Self::Canary) => Ordering::Equal,
(Self::Alias(l), Self::Alias(r)) => l.cmp(r),
(Self::Version(l), Self::Version(r)) => l.cmp(r),
_ => compare(&self.to_string(), &other.to_string()),
}
}
}