use std::borrow::{Borrow, Cow}; use std::fmt::Debug; use std::path::Path; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::util::Version; use crate::Error; pub const SYSTEM_ID: Id<&'static str> = Id("SYSTEM"); #[derive(Debug, thiserror::Error)] #[error("Invalid ID")] pub struct InvalidId; impl From for Error { fn from(err: InvalidId) -> Self { Error::new(err, crate::error::ErrorKind::InvalidPackageId) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct IdUnchecked>(pub S); impl<'de> Deserialize<'de> for IdUnchecked> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = IdUnchecked>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "a valid ID") } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { Ok(IdUnchecked(Cow::Owned(v.to_owned()))) } fn visit_string(self, v: String) -> Result where E: serde::de::Error, { Ok(IdUnchecked(Cow::Owned(v))) } fn visit_borrowed_str(self, v: &'de str) -> Result where E: serde::de::Error, { Ok(IdUnchecked(Cow::Borrowed(v))) } } deserializer.deserialize_any(Visitor) } } impl<'de> Deserialize<'de> for IdUnchecked { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Ok(IdUnchecked(String::deserialize(deserializer)?)) } } impl<'de> Deserialize<'de> for IdUnchecked<&'de str> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Ok(IdUnchecked(<&'de str>::deserialize(deserializer)?)) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Id = String>(S); impl> Id { pub fn try_from(value: S) -> Result { if value .as_ref() .chars() .all(|c| c.is_ascii_lowercase() || c == '-') { Ok(Id(value)) } else { Err(InvalidId) } } } impl<'a> Id<&'a str> { pub fn owned(&self) -> Id { Id(self.0.to_owned()) } } impl> std::ops::Deref for Id { type Target = S; fn deref(&self) -> &Self::Target { &self.0 } } impl> std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0.as_ref()) } } impl> AsRef for Id { fn as_ref(&self) -> &str { self.0.as_ref() } } impl> Borrow for Id { fn borrow(&self) -> &str { self.0.as_ref() } } impl<'de, S> Deserialize<'de> for Id where S: AsRef, IdUnchecked: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let unchecked: IdUnchecked = Deserialize::deserialize(deserializer)?; Id::try_from(unchecked.0).map_err(serde::de::Error::custom) } } impl> Serialize for Id { fn serialize(&self, serializer: Ser) -> Result where Ser: Serializer, { serializer.serialize_str(self.as_ref()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] pub struct ImageId = String>(Id); impl> std::fmt::Display for ImageId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", &self.0) } } impl> ImageId { pub fn for_package>, S0: AsRef>( &self, pkg_id: PkgId, pkg_version: Option<&Version>, ) -> String { format!( "start9/{}/{}:{}", pkg_id.as_ref(), self.0, pkg_version.map(|v| { v.as_str() }).unwrap_or("latest") ) } } impl<'de, S> Deserialize<'de> for ImageId where S: AsRef, Id: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Ok(ImageId(Deserialize::deserialize(deserializer)?)) } }