Files
biggus-dickus/crates/domain/src/id.rs
T

104 lines
2.8 KiB
Rust

//! 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<Self, Self::Err> {
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
);
id_newtype!(
/// Identifier for a catalogue object (or group of objects).
ObjectId
);
id_newtype!(
/// Identifier for a flexible-field definition.
FieldDefinitionId
);
id_newtype!(
/// Identifier for a user of this organization's instance.
UserId
);
#[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::<OrgId>().is_err());
}
#[test]
fn distinct_id_types_parse_independently() {
let text = "550e8400-e29b-41d4-a716-446655440000";
assert_eq!(text.parse::<VocabularyId>().unwrap().to_string(), text);
assert_eq!(text.parse::<TermId>().unwrap().to_string(), text);
assert_eq!(text.parse::<AuthorityId>().unwrap().to_string(), text);
}
}