use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OperationId(pub Uuid);
impl OperationId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
pub fn from_uuid(uuid: Uuid) -> Self {
Self(uuid)
}
pub fn as_uuid(&self) -> Uuid {
self.0
}
pub fn as_bytes(&self) -> &[u8; 16] {
self.0.as_bytes()
}
}
impl Default for OperationId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for OperationId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, thiserror::Error)]
pub enum OperationIdParseError {
#[error("invalid operation id: {0}")]
InvalidUuid(#[from] uuid::Error),
}
impl FromStr for OperationId {
type Err = OperationIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Uuid::parse_str(s)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_generates_distinct_ids() {
let a = OperationId::new();
let b = OperationId::new();
assert_ne!(a, b);
}
#[test]
fn display_round_trips_through_from_str() {
let id = OperationId::new();
let parsed: OperationId = id.to_string().parse().unwrap();
assert_eq!(id, parsed);
}
#[test]
fn rejects_garbage() {
assert!("not-a-uuid".parse::<OperationId>().is_err());
}
#[test]
fn serde_roundtrip() {
let id = OperationId::new();
let json = serde_json::to_string(&id).unwrap();
let back: OperationId = serde_json::from_str(&json).unwrap();
assert_eq!(id, back);
}
}