use std::fmt; use std::str::FromStr; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Identifier for an organization (tenant). /// /// A newtype over [`Uuid`] so it can never be confused with another entity's id. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct OrgId(Uuid); impl OrgId { /// Generate a fresh random id. #[must_use = "generating an OrgId and discarding it is almost certainly a mistake"] pub fn new() -> Self { Self(Uuid::new_v4()) } /// Wrap an existing [`Uuid`]. pub fn from_uuid(uuid: Uuid) -> Self { Self(uuid) } /// Return the underlying [`Uuid`]. pub fn to_uuid(&self) -> Uuid { self.0 } } impl Default for OrgId { fn default() -> Self { Self::new() } } impl fmt::Display for OrgId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl FromStr for OrgId { type Err = uuid::Error; fn from_str(s: &str) -> Result { Ok(Self(Uuid::parse_str(s)?)) } } #[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()); } }