//! Strongly-typed identifiers. /// Define a UUID newtype identifier with the standard constructors and conversions. macro_rules! id_newtype { ($(#[$meta:meta])* $name:ident) => { $(#[$meta])* #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] #[serde(transparent)] pub struct $name(uuid::Uuid); impl $name { /// Generate a fresh random id. #[must_use = "generating an id and discarding it is almost certainly a mistake"] pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } /// Wrap an existing [`uuid::Uuid`]. pub fn from_uuid(uuid: uuid::Uuid) -> Self { Self(uuid) } /// The underlying [`uuid::Uuid`]. pub fn to_uuid(&self) -> uuid::Uuid { self.0 } } impl Default for $name { fn default() -> Self { Self::new() } } impl std::fmt::Display for $name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } impl std::str::FromStr for $name { type Err = uuid::Error; fn from_str(s: &str) -> Result { Ok(Self(uuid::Uuid::parse_str(s)?)) } } }; } id_newtype!( /// Identifier for an organization (tenant). OrgId ); id_newtype!( /// Identifier for a controlled vocabulary (term source). VocabularyId ); id_newtype!( /// Identifier for a term within a vocabulary. TermId ); id_newtype!( /// Identifier for an authority record (person, organisation, or place). AuthorityId ); #[cfg(test)] mod tests { use super::*; #[test] fn parses_and_displays_round_trip() { let text = "550e8400-e29b-41d4-a716-446655440000"; let id: OrgId = text.parse().expect("valid uuid should parse"); assert_eq!(id.to_string(), text); } #[test] fn rejects_invalid_uuid() { assert!("not-a-uuid".parse::().is_err()); } #[test] fn distinct_id_types_parse_independently() { let text = "550e8400-e29b-41d4-a716-446655440000"; assert_eq!(text.parse::().unwrap().to_string(), text); assert_eq!(text.parse::().unwrap().to_string(), text); assert_eq!(text.parse::().unwrap().to_string(), text); } }