feat(domain): id macro + vocabulary/authority/label value types
This commit is contained in:
+67
-43
@@ -1,53 +1,69 @@
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
//! Strongly-typed identifiers.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
/// 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);
|
||||
|
||||
/// 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 $name {
|
||||
/// Generate a fresh random id.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(uuid::Uuid::new_v4())
|
||||
}
|
||||
|
||||
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::Uuid`].
|
||||
pub fn from_uuid(uuid: uuid::Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
|
||||
/// Wrap an existing [`Uuid`].
|
||||
pub fn from_uuid(uuid: Uuid) -> Self {
|
||||
Self(uuid)
|
||||
}
|
||||
/// The underlying [`uuid::Uuid`].
|
||||
pub fn to_uuid(&self) -> uuid::Uuid {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the underlying [`Uuid`].
|
||||
pub fn to_uuid(&self) -> 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)?))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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<Self, Self::Err> {
|
||||
Ok(Self(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 {
|
||||
@@ -64,4 +80,12 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user