Skip to main content

atm_storage/schema/
inbox_message.rs

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}