use std::{fmt, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _}; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct OpaqueId(String); impl OpaqueId { pub fn parse(value: impl Into) -> Result { let value = value.into(); let mut chars = value.chars(); let Some(first) = chars.next() else { return Err(IdError); }; if !(8..=128).contains(&value.len()) || !first.is_ascii_alphanumeric() || !chars.all(|character| { character.is_ascii_alphanumeric() || matches!(character, '_' | '-') }) { return Err(IdError); } Ok(Self(value)) } #[must_use] pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for OpaqueId { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(formatter) } } impl FromStr for OpaqueId { type Err = IdError; fn from_str(value: &str) -> Result { Self::parse(value) } } impl Serialize for OpaqueId { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.0) } } impl<'de> Deserialize<'de> for OpaqueId { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Self::parse(String::deserialize(deserializer)?).map_err(D::Error::custom) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct IdError; impl fmt::Display for IdError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("invalid opaque identifier") } } impl std::error::Error for IdError {}