agentmux_srv/messaging/slack/
types.rs1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum SocketEnvelope {
22 #[allow(dead_code)]
26 Hello(HelloFrame),
27 EventsApi(EventsApiEnvelope),
28 Disconnect(DisconnectFrame),
29 SlashCommands(SlashCommandsEnvelope),
30 #[serde(other)]
31 Unknown,
32}
33
34#[derive(Debug, Deserialize)]
35pub struct HelloFrame {
36 #[serde(default)]
37 #[allow(dead_code)]
38 pub num_connections: Option<u32>,
39}
40
41#[derive(Debug, Deserialize)]
42pub struct EventsApiEnvelope {
43 pub envelope_id: String,
44 #[serde(default)]
45 pub payload: Option<EventsApiPayload>,
46}
47
48#[derive(Debug, Deserialize)]
49pub struct EventsApiPayload {
50 #[serde(default)]
51 pub event: Option<SlackEvent>,
52}
53
54#[derive(Debug, Clone, Deserialize)]
55pub struct SlackEvent {
56 #[serde(rename = "type")]
57 pub type_: String,
58 #[serde(default)]
59 pub channel: Option<String>,
60 #[serde(default)]
61 pub user: Option<String>,
62 #[serde(default)]
63 pub text: Option<String>,
64 #[serde(default)]
65 pub ts: Option<String>,
66 #[serde(default)]
69 pub bot_id: Option<String>,
70}
71
72#[derive(Debug, Deserialize)]
73pub struct DisconnectFrame {
74 #[serde(default)]
75 pub reason: Option<String>,
76}
77
78#[derive(Debug, Deserialize)]
80#[allow(dead_code)]
81pub struct SlashCommandsEnvelope {
82 pub envelope_id: String,
83 #[serde(default)]
84 pub payload: Option<serde_json::Value>,
85}
86
87#[derive(Debug, Serialize)]
93pub struct AckFrame {
94 pub envelope_id: String,
95}
96
97#[derive(Debug, Deserialize)]
100pub struct OpenConnectionResponse {
101 pub ok: bool,
102 #[serde(default)]
103 pub url: Option<String>,
104 #[serde(default)]
105 pub error: Option<String>,
106}
107
108#[derive(Debug, Serialize)]
111pub struct PostMessageBody {
112 pub channel: String,
113 pub text: String,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub blocks: Option<Vec<serde_json::Value>>,
118}
119
120#[derive(Debug, Deserialize)]
121pub struct PostMessageResponse {
122 pub ok: bool,
123 #[serde(default)]
124 pub error: Option<String>,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn parses_hello_frame() {
133 let json = r#"{"type": "hello", "num_connections": 1, "connection_info": {"app_id": "A1"}}"#;
134 let env: SocketEnvelope = serde_json::from_str(json).unwrap();
135 assert!(matches!(env, SocketEnvelope::Hello(_)));
136 }
137
138 #[test]
139 fn parses_disconnect_warning_frame() {
140 let json = r#"{"type": "disconnect", "reason": "warning"}"#;
141 let env: SocketEnvelope = serde_json::from_str(json).unwrap();
142 match env {
143 SocketEnvelope::Disconnect(d) => assert_eq!(d.reason.as_deref(), Some("warning")),
144 other => panic!("expected Disconnect, got {other:?}"),
145 }
146 }
147
148 #[test]
149 fn parses_events_api_envelope_with_message_event() {
150 let json = r#"{
151 "envelope_id": "95869",
152 "type": "events_api",
153 "accepts_response_payload": false,
154 "payload": {
155 "event": {"type": "message", "channel": "C1", "user": "U1", "text": "hi", "ts": "123.4"}
156 }
157 }"#;
158 let env: SocketEnvelope = serde_json::from_str(json).unwrap();
159 match env {
160 SocketEnvelope::EventsApi(e) => {
161 assert_eq!(e.envelope_id, "95869");
162 let event = e.payload.unwrap().event.unwrap();
163 assert_eq!(event.type_, "message");
164 assert_eq!(event.channel.as_deref(), Some("C1"));
165 assert_eq!(event.text.as_deref(), Some("hi"));
166 assert!(event.bot_id.is_none());
167 }
168 other => panic!("expected EventsApi, got {other:?}"),
169 }
170 }
171
172 #[test]
173 fn parses_events_api_envelope_with_bot_id() {
174 let json = r#"{
175 "envelope_id": "1",
176 "type": "events_api",
177 "payload": {"event": {"type": "message", "channel": "C1", "bot_id": "B1"}}
178 }"#;
179 let env: SocketEnvelope = serde_json::from_str(json).unwrap();
180 match env {
181 SocketEnvelope::EventsApi(e) => {
182 let event = e.payload.unwrap().event.unwrap();
183 assert_eq!(event.bot_id.as_deref(), Some("B1"));
184 }
185 other => panic!("expected EventsApi, got {other:?}"),
186 }
187 }
188
189 #[test]
190 fn unknown_envelope_type_does_not_error() {
191 let json = r#"{"type": "some_future_type", "foo": "bar"}"#;
192 let env: SocketEnvelope = serde_json::from_str(json).unwrap();
193 assert!(matches!(env, SocketEnvelope::Unknown));
194 }
195
196 #[test]
197 fn ack_frame_serializes_envelope_id_only() {
198 let ack = AckFrame { envelope_id: "95869".to_string() };
199 let json = serde_json::to_string(&ack).unwrap();
200 assert_eq!(json, r#"{"envelope_id":"95869"}"#);
201 }
202
203 #[test]
204 fn post_message_body_omits_blocks_when_none() {
205 let body = PostMessageBody {
206 channel: "C1".to_string(),
207 text: "hi".to_string(),
208 blocks: None,
209 };
210 let json = serde_json::to_string(&body).unwrap();
211 assert!(!json.contains("blocks"));
212 }
213}