stygian_plugin/domain/
idempotency.rs1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
23pub struct IdempotencyKey(ulid::Ulid);
24
25impl IdempotencyKey {
26 #[must_use]
28 pub fn new() -> Self {
29 Self(ulid::Ulid::new())
30 }
31
32 #[must_use]
34 pub const fn from_ulid(ulid: ulid::Ulid) -> Self {
35 Self(ulid)
36 }
37
38 #[must_use]
40 pub const fn inner(&self) -> ulid::Ulid {
41 self.0
42 }
43
44 #[must_use]
46 pub const fn timestamp(&self) -> u64 {
47 self.0.timestamp_ms()
48 }
49}
50
51impl FromStr for IdempotencyKey {
52 type Err = crate::error::PluginError;
53
54 fn from_str(s: &str) -> crate::Result<Self> {
55 ulid::Ulid::from_str(s)
56 .map(Self)
57 .map_err(|e| crate::error::PluginError::Other(format!("Invalid ULID: {e}")))
58 }
59}
60
61impl Default for IdempotencyKey {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl fmt::Display for IdempotencyKey {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}", self.0)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_new_key_is_unique() {
79 let key1 = IdempotencyKey::new();
80 let key2 = IdempotencyKey::new();
81 assert_ne!(key1, key2);
82 }
83
84 #[test]
85 fn test_key_serialization() -> std::result::Result<(), Box<dyn std::error::Error>> {
86 let key = IdempotencyKey::new();
87 let json = serde_json::to_string(&key)?;
88 let key2: IdempotencyKey = serde_json::from_str(&json)?;
89 assert_eq!(key, key2);
90 Ok(())
91 }
92
93 #[test]
94 fn test_key_display() {
95 let key = IdempotencyKey::new();
96 let s = format!("{key}");
97 assert!(!s.is_empty());
98 }
99
100 #[test]
101 fn test_key_timestamp() {
102 let key = IdempotencyKey::new();
103 let ts = key.timestamp();
104 assert!(ts > 0);
105 }
106}