objects/object/
operation_id.rs1use std::{fmt, str::FromStr};
11
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(transparent)]
17pub struct OperationId(pub Uuid);
18
19impl OperationId {
20 pub fn new() -> Self {
21 Self(Uuid::new_v4())
22 }
23
24 pub fn from_uuid(uuid: Uuid) -> Self {
25 Self(uuid)
26 }
27
28 pub fn as_uuid(&self) -> Uuid {
29 self.0
30 }
31
32 pub fn as_bytes(&self) -> &[u8; 16] {
33 self.0.as_bytes()
34 }
35}
36
37impl Default for OperationId {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl fmt::Display for OperationId {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 self.0.fmt(f)
46 }
47}
48
49#[derive(Debug, thiserror::Error)]
50pub enum OperationIdParseError {
51 #[error("invalid operation id: {0}")]
52 InvalidUuid(#[from] uuid::Error),
53}
54
55impl FromStr for OperationId {
56 type Err = OperationIdParseError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 Ok(Self(Uuid::parse_str(s)?))
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn new_generates_distinct_ids() {
69 let a = OperationId::new();
70 let b = OperationId::new();
71 assert_ne!(a, b);
72 }
73
74 #[test]
75 fn display_round_trips_through_from_str() {
76 let id = OperationId::new();
77 let parsed: OperationId = id.to_string().parse().unwrap();
78 assert_eq!(id, parsed);
79 }
80
81 #[test]
82 fn rejects_garbage() {
83 assert!("not-a-uuid".parse::<OperationId>().is_err());
84 }
85
86 #[test]
87 fn serde_roundtrip() {
88 let id = OperationId::new();
89 let json = serde_json::to_string(&id).unwrap();
90 let back: OperationId = serde_json::from_str(&json).unwrap();
91 assert_eq!(id, back);
92 }
93}