1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4use std::fmt;
5use ulid::Ulid;
6
7use crate::types::{AgentName, IsoTimestamp, TaskId, TeamName};
8
9#[derive(Debug, Clone)]
10pub struct AtmMessageIdParseError(String);
11
12impl fmt::Display for AtmMessageIdParseError {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 f.write_str(&self.0)
15 }
16}
17
18impl std::error::Error for AtmMessageIdParseError {}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21#[serde(transparent)]
22pub struct AtmMessageId(Ulid);
23
24impl AtmMessageId {
25 pub fn new() -> Self {
26 Self(Ulid::new())
27 }
28
29 pub fn into_ulid(self) -> Ulid {
30 self.0
31 }
32
33 pub fn timestamp(self) -> IsoTimestamp {
34 let datetime: DateTime<Utc> = self.0.datetime().into();
35 IsoTimestamp::from_datetime(datetime)
36 }
37
38 pub fn new_with_timestamp() -> (Self, IsoTimestamp) {
39 let message_id = Self::new();
40 let timestamp = message_id.timestamp();
41 (message_id, timestamp)
42 }
43}
44
45impl Default for AtmMessageId {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl From<Ulid> for AtmMessageId {
52 fn from(value: Ulid) -> Self {
53 Self(value)
54 }
55}
56
57impl From<AtmMessageId> for Ulid {
58 fn from(value: AtmMessageId) -> Self {
59 value.0
60 }
61}
62
63impl std::str::FromStr for AtmMessageId {
64 type Err = AtmMessageIdParseError;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
67 Ulid::from_string(s)
68 .map(Self)
69 .map_err(|error| AtmMessageIdParseError(error.to_string()))
70 }
71}
72
73impl fmt::Display for AtmMessageId {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 self.0.fmt(f)
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "kebab-case")]
81pub enum ThreadMode {
82 AddDetails,
83 Supersede,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum AlertKind {
88 MissingTeamConfig,
89 Unknown(String),
90}
91
92impl AlertKind {
93 pub fn as_str(&self) -> &str {
94 match self {
95 Self::MissingTeamConfig => "missing_team_config",
96 Self::Unknown(value) => value,
97 }
98 }
99}
100
101impl From<String> for AlertKind {
102 fn from(value: String) -> Self {
103 match value.as_str() {
104 "missing_team_config" => Self::MissingTeamConfig,
105 _ => Self::Unknown(value),
106 }
107 }
108}
109
110impl From<AlertKind> for String {
111 fn from(value: AlertKind) -> Self {
112 match value {
113 AlertKind::MissingTeamConfig => "missing_team_config".to_string(),
114 AlertKind::Unknown(value) => value,
115 }
116 }
117}
118
119impl Serialize for AlertKind {
120 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
121 where
122 S: serde::Serializer,
123 {
124 serializer.serialize_str(self.as_str())
125 }
126}
127
128impl<'de> Deserialize<'de> for AlertKind {
129 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130 where
131 D: serde::Deserializer<'de>,
132 {
133 Ok(Self::from(String::deserialize(deserializer)?))
134 }
135}
136
137#[derive(Debug, Clone, Serialize, PartialEq)]
138pub struct MessageEnvelope {
139 pub from: AgentName,
140 pub text: String,
141 pub timestamp: IsoTimestamp,
142 pub read: bool,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub source_team: Option<TeamName>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub summary: Option<String>,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub message_id: Option<AtmMessageId>,
149 pub requires_ack: bool,
150 #[serde(rename = "pendingAckAt", skip_serializing_if = "Option::is_none")]
151 pub pending_ack_at: Option<IsoTimestamp>,
152 #[serde(rename = "acknowledgedAt", skip_serializing_if = "Option::is_none")]
153 pub acknowledged_at: Option<IsoTimestamp>,
154 #[serde(
155 rename = "acknowledgesMessageId",
156 default,
157 skip_serializing_if = "Option::is_none"
158 )]
159 pub acknowledges_message_id: Option<AtmMessageId>,
160 #[serde(
161 rename = "parentMessageId",
162 default,
163 skip_serializing_if = "Option::is_none"
164 )]
165 pub parent_message_id: Option<AtmMessageId>,
166 #[serde(rename = "threadMode", skip_serializing_if = "Option::is_none")]
167 pub thread_mode: Option<ThreadMode>,
168 #[serde(rename = "expiresAt", skip_serializing_if = "Option::is_none")]
169 pub expires_at: Option<IsoTimestamp>,
170 #[serde(rename = "taskId", skip_serializing_if = "Option::is_none")]
171 pub task_id: Option<TaskId>,
172 #[serde(flatten)]
173 pub extra: Map<String, Value>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177struct RawMessageEnvelope {
178 from: AgentName,
179 text: String,
180 timestamp: IsoTimestamp,
181 read: bool,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 source_team: Option<TeamName>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 summary: Option<String>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 message_id: Option<AtmMessageId>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 requires_ack: Option<bool>,
190 #[serde(rename = "pendingAckAt", skip_serializing_if = "Option::is_none")]
191 pending_ack_at: Option<IsoTimestamp>,
192 #[serde(rename = "acknowledgedAt", skip_serializing_if = "Option::is_none")]
193 acknowledged_at: Option<IsoTimestamp>,
194 #[serde(
195 rename = "acknowledgesMessageId",
196 default,
197 skip_serializing_if = "Option::is_none"
198 )]
199 acknowledges_message_id: Option<AtmMessageId>,
200 #[serde(
201 rename = "parentMessageId",
202 default,
203 skip_serializing_if = "Option::is_none"
204 )]
205 parent_message_id: Option<AtmMessageId>,
206 #[serde(rename = "threadMode", skip_serializing_if = "Option::is_none")]
207 thread_mode: Option<ThreadMode>,
208 #[serde(rename = "expiresAt", skip_serializing_if = "Option::is_none")]
209 expires_at: Option<IsoTimestamp>,
210 #[serde(rename = "taskId", skip_serializing_if = "Option::is_none")]
211 task_id: Option<TaskId>,
212 #[serde(flatten)]
213 extra: Map<String, Value>,
214}
215
216impl From<MessageEnvelope> for RawMessageEnvelope {
217 fn from(value: MessageEnvelope) -> Self {
218 Self {
219 from: value.from,
220 text: value.text,
221 timestamp: value.timestamp,
222 read: value.read,
223 source_team: value.source_team,
224 summary: value.summary,
225 message_id: value.message_id,
226 requires_ack: Some(value.requires_ack),
227 pending_ack_at: value.pending_ack_at,
228 acknowledged_at: value.acknowledged_at,
229 acknowledges_message_id: value.acknowledges_message_id,
230 parent_message_id: value.parent_message_id,
231 thread_mode: value.thread_mode,
232 expires_at: value.expires_at,
233 task_id: value.task_id,
234 extra: value.extra,
235 }
236 }
237}
238
239impl From<RawMessageEnvelope> for MessageEnvelope {
240 fn from(value: RawMessageEnvelope) -> Self {
241 let requires_ack = value
242 .requires_ack
243 .unwrap_or(value.pending_ack_at.is_some() && value.acknowledges_message_id.is_none());
244 Self {
245 from: value.from,
246 text: value.text,
247 timestamp: value.timestamp,
248 read: value.read,
249 source_team: value.source_team,
250 summary: value.summary,
251 message_id: value.message_id,
252 requires_ack,
253 pending_ack_at: value.pending_ack_at,
254 acknowledged_at: value.acknowledged_at,
255 acknowledges_message_id: value.acknowledges_message_id,
256 parent_message_id: value.parent_message_id,
257 thread_mode: value.thread_mode,
258 expires_at: value.expires_at,
259 task_id: value.task_id,
260 extra: value.extra,
261 }
262 }
263}
264
265impl<'de> Deserialize<'de> for MessageEnvelope {
266 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
267 where
268 D: serde::Deserializer<'de>,
269 {
270 RawMessageEnvelope::deserialize(deserializer).map(Into::into)
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
275pub struct PendingAck {
276 pub message_id: AtmMessageId,
277 pub from: AgentName,
278 pub acked: bool,
279 pub acked_at: Option<IsoTimestamp>,
280}