emergent_client/types/
causation_id.rs1use super::MessageId;
4use mti::prelude::*;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::fmt;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct CausationId(MagicTypeId);
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum InvalidCausationId {
22 Parse(MagicTypeIdError),
24 WrongPrefix {
26 expected: &'static str,
27 actual: String,
28 },
29}
30
31impl fmt::Display for InvalidCausationId {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::Parse(e) => write!(f, "invalid causation ID: {e}"),
35 Self::WrongPrefix { expected, actual } => {
36 write!(f, "expected prefix '{expected}', got '{actual}'")
37 }
38 }
39 }
40}
41
42impl std::error::Error for InvalidCausationId {}
43
44impl CausationId {
45 pub const PREFIX: &'static str = "cau";
47
48 #[must_use]
50 pub fn new() -> Self {
51 Self(Self::PREFIX.create_type_id::<V7>())
52 }
53
54 pub fn parse(s: &str) -> Result<Self, InvalidCausationId> {
63 let id = MagicTypeId::from_str(s).map_err(InvalidCausationId::Parse)?;
64
65 let prefix = id.prefix().as_str();
66 if prefix != Self::PREFIX && prefix != "msg" {
67 return Err(InvalidCausationId::WrongPrefix {
68 expected: "cau or msg",
69 actual: prefix.to_string(),
70 });
71 }
72
73 Ok(Self(id))
74 }
75
76 #[must_use]
78 pub fn inner(&self) -> &MagicTypeId {
79 &self.0
80 }
81
82 #[must_use]
84 pub fn suffix(&self) -> String {
85 self.0.suffix().to_string()
86 }
87}
88
89impl Default for CausationId {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl fmt::Display for CausationId {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{}", self.0)
98 }
99}
100
101impl FromStr for CausationId {
102 type Err = InvalidCausationId;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 Self::parse(s)
106 }
107}
108
109impl AsRef<MagicTypeId> for CausationId {
110 fn as_ref(&self) -> &MagicTypeId {
111 &self.0
112 }
113}
114
115impl From<MessageId> for CausationId {
120 fn from(msg_id: MessageId) -> Self {
121 Self(msg_id.inner().clone())
122 }
123}
124
125impl From<&MessageId> for CausationId {
127 fn from(msg_id: &MessageId) -> Self {
128 Self(msg_id.inner().clone())
129 }
130}
131
132impl Serialize for CausationId {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: Serializer,
136 {
137 self.0.to_string().serialize(serializer)
138 }
139}
140
141impl<'de> Deserialize<'de> for CausationId {
142 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143 where
144 D: Deserializer<'de>,
145 {
146 let s = String::deserialize(deserializer)?;
147 Self::parse(&s).map_err(serde::de::Error::custom)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn new_creates_valid_causation_id() {
157 let id = CausationId::new();
158 assert!(id.to_string().starts_with("cau_"));
159 }
160
161 #[test]
162 fn parse_valid_causation_id() {
163 let id_str = CausationId::new().to_string();
164 let parsed = CausationId::parse(&id_str);
165 assert!(parsed.is_ok());
166 }
167
168 #[test]
169 fn parse_accepts_msg_prefix() {
170 let result = CausationId::parse("msg_01h455vb4pex5vsknk084sn02q");
171 assert!(result.is_ok());
172 }
173
174 #[test]
175 fn parse_wrong_prefix_fails() {
176 let result = CausationId::parse("cor_01h455vb4pex5vsknk084sn02q");
177 assert!(matches!(
178 result,
179 Err(InvalidCausationId::WrongPrefix { .. })
180 ));
181 }
182
183 #[test]
184 fn causation_ids_are_unique() {
185 let id1 = CausationId::new();
186 let id2 = CausationId::new();
187 assert_ne!(id1, id2);
188 }
189
190 #[test]
191 fn from_message_id() {
192 let msg_id = MessageId::new();
193 let msg_id_str = msg_id.to_string();
194 let causation_id: CausationId = msg_id.into();
195 assert_eq!(causation_id.to_string(), msg_id_str);
197 }
198
199 #[test]
200 fn from_message_id_ref() {
201 let msg_id = MessageId::new();
202 let causation_id: CausationId = (&msg_id).into();
203 assert_eq!(causation_id.to_string(), msg_id.to_string());
204 }
205
206 #[test]
207 fn serde_roundtrip() -> Result<(), serde_json::Error> {
208 let id = CausationId::new();
209 let json = serde_json::to_string(&id)?;
210 let restored: CausationId = serde_json::from_str(&json)?;
211 assert_eq!(id, restored);
212 Ok(())
213 }
214}