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