use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ItemId {
Uuid(uuid::Uuid),
Slug(String),
}
impl ItemId {
#[must_use]
pub fn new_uuid() -> Self {
ItemId::Uuid(uuid::Uuid::new_v4())
}
#[must_use]
pub fn from_uuid(uuid: uuid::Uuid) -> Self {
ItemId::Uuid(uuid)
}
#[must_use]
pub fn from_slug(slug: impl Into<String>) -> Self {
ItemId::Slug(slug.into())
}
#[must_use]
pub fn parse(s: &str) -> Self {
match uuid::Uuid::parse_str(s) {
Ok(uuid) => ItemId::Uuid(uuid),
Err(_) => ItemId::Slug(s.to_string()),
}
}
#[must_use]
pub fn as_uuid(&self) -> Option<&uuid::Uuid> {
match self {
ItemId::Uuid(uuid) => Some(uuid),
ItemId::Slug(_) => None,
}
}
#[must_use]
pub fn as_slug(&self) -> Option<&str> {
match self {
ItemId::Uuid(_) => None,
ItemId::Slug(slug) => Some(slug),
}
}
#[must_use]
pub fn is_uuid(&self) -> bool {
matches!(self, ItemId::Uuid(_))
}
#[must_use]
pub fn is_slug(&self) -> bool {
matches!(self, ItemId::Slug(_))
}
#[must_use]
pub fn to_storage_name(&self) -> String {
match self {
ItemId::Uuid(uuid) => uuid.to_string(),
ItemId::Slug(slug) => slug.clone(),
}
}
}
impl fmt::Display for ItemId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ItemId::Uuid(uuid) => write!(f, "{uuid}"),
ItemId::Slug(slug) => write!(f, "{slug}"),
}
}
}
impl From<uuid::Uuid> for ItemId {
fn from(uuid: uuid::Uuid) -> Self {
ItemId::Uuid(uuid)
}
}
impl From<String> for ItemId {
fn from(s: String) -> Self {
ItemId::parse(&s)
}
}
impl From<&str> for ItemId {
fn from(s: &str) -> Self {
ItemId::parse(s)
}
}
impl serde::Serialize for ItemId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for ItemId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(ItemId::parse(&s))
}
}
pub trait Identifiable {
fn id(&self) -> ItemId;
fn id_str(&self) -> &str;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_item_id_uuid() {
let uuid = uuid::Uuid::new_v4();
let id = ItemId::from_uuid(uuid);
assert!(id.is_uuid());
assert!(!id.is_slug());
assert_eq!(id.as_uuid(), Some(&uuid));
assert_eq!(id.as_slug(), None);
}
#[test]
fn test_item_id_slug() {
let id = ItemId::from_slug("my-doc");
assert!(id.is_slug());
assert!(!id.is_uuid());
assert_eq!(id.as_slug(), Some("my-doc"));
assert_eq!(id.as_uuid(), None);
}
#[test]
fn test_item_id_parse_uuid() {
let uuid = uuid::Uuid::new_v4();
let id = ItemId::parse(&uuid.to_string());
assert!(id.is_uuid());
assert_eq!(id.as_uuid(), Some(&uuid));
}
#[test]
fn test_item_id_parse_slug() {
let id = ItemId::parse("not-a-uuid");
assert!(id.is_slug());
assert_eq!(id.as_slug(), Some("not-a-uuid"));
}
#[test]
fn test_item_id_storage_name() {
let uuid = uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let id = ItemId::from_uuid(uuid);
assert_eq!(id.to_storage_name(), "550e8400-e29b-41d4-a716-446655440000");
let id = ItemId::from_slug("my-document");
assert_eq!(id.to_storage_name(), "my-document");
}
#[test]
fn test_item_id_serde() {
let uuid = uuid::Uuid::new_v4();
let id = ItemId::from_uuid(uuid);
let json = serde_json::to_string(&id).unwrap();
let parsed: ItemId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
let id = ItemId::from_slug("test-slug");
let json = serde_json::to_string(&id).unwrap();
let parsed: ItemId = serde_json::from_str(&json).unwrap();
assert_eq!(id, parsed);
}
}