Skip to main content

webex_message_handler/
types.rs

1//! Data types for webex-message-handler.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8
9/// Networking mode for the handler.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum NetworkMode {
12    /// Use built-in HTTP and WebSocket libraries (default).
13    #[default]
14    Native,
15    /// Use provided fetch and WebSocket factory functions.
16    Injected,
17}
18
19/// HTTP request for injected fetch function.
20#[derive(Debug, Clone)]
21pub struct FetchRequest {
22    pub url: String,
23    pub method: String,
24    pub headers: HashMap<String, String>,
25    pub body: Option<String>,
26}
27
28/// HTTP response from injected fetch function.
29pub struct FetchResponse {
30    pub status: u16,
31    pub ok: bool,
32    pub body: Vec<u8>,
33}
34
35/// Custom fetch function for injected mode.
36pub type FetchFn = Arc<
37    dyn Fn(FetchRequest) -> Pin<Box<dyn Future<Output = Result<FetchResponse, Box<dyn std::error::Error + Send + Sync>>> + Send>>
38        + Send
39        + Sync,
40>;
41
42/// Boxed future result alias to reduce type complexity.
43type BoxFutureResult<'a, T> = Pin<Box<dyn Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>> + Send + 'a>>;
44
45/// WebSocket interface for injected mode.
46pub trait InjectedWebSocket: Send + Sync {
47    fn send(&self, data: String) -> BoxFutureResult<'_, ()>;
48    fn receive(&self) -> BoxFutureResult<'_, String>;
49    fn close(&self) -> BoxFutureResult<'_, ()>;
50}
51
52/// WebSocket factory for injected mode.
53pub type WebSocketFactory = Arc<
54    dyn Fn(String) -> Pin<Box<dyn Future<Output = Result<Box<dyn InjectedWebSocket>, Box<dyn std::error::Error + Send + Sync>>> + Send>>
55        + Send
56        + Sync,
57>;
58
59/// Configuration for WebexMessageHandler.
60#[derive(Clone)]
61pub struct Config {
62    /// Webex bot or user access token (required).
63    pub token: String,
64
65    /// Networking mode: Native or Injected (default: Native).
66    pub mode: NetworkMode,
67
68    /// Optional HTTP client for proxy support (native mode only).
69    /// If None, a default client will be created.
70    pub client: Option<reqwest::Client>,
71
72    /// Custom fetch function for all HTTP requests (injected mode).
73    pub fetch: Option<FetchFn>,
74
75    /// Custom WebSocket factory (injected mode).
76    pub web_socket_factory: Option<WebSocketFactory>,
77
78    /// Mercury ping interval in seconds (default: 15).
79    pub ping_interval: f64,
80
81    /// Pong response timeout in seconds (default: 14).
82    pub pong_timeout: f64,
83
84    /// Max reconnect backoff in seconds (default: 32).
85    pub reconnect_backoff_max: f64,
86
87    /// Max consecutive reconnection attempts (default: 10).
88    pub max_reconnect_attempts: u32,
89
90    /// Automatically filter out messages sent by this bot to prevent loops (default: true).
91    pub ignore_self_messages: bool,
92}
93
94impl Default for Config {
95    fn default() -> Self {
96        Self {
97            token: String::new(),
98            mode: NetworkMode::Native,
99            client: None,
100            fetch: None,
101            web_socket_factory: None,
102            ping_interval: 15.0,
103            pong_timeout: 14.0,
104            reconnect_backoff_max: 32.0,
105            max_reconnect_attempts: 10,
106            ignore_self_messages: true,
107        }
108    }
109}
110
111/// Result of WDM device registration.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct DeviceRegistration {
114    /// Mercury WebSocket URL.
115    #[serde(rename = "webSocketUrl")]
116    pub web_socket_url: String,
117
118    /// Device URL (used as clientId for KMS).
119    #[serde(rename = "url")]
120    pub device_url: String,
121
122    /// Bot's user ID.
123    #[serde(rename = "userId")]
124    pub user_id: String,
125
126    /// Service catalog from WDM.
127    #[serde(default)]
128    pub services: HashMap<String, String>,
129
130    /// Encryption service URL extracted from services.
131    #[serde(skip)]
132    pub encryption_service_url: String,
133}
134
135/// Actor in a Mercury activity.
136#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct MercuryActor {
138    #[serde(default)]
139    pub id: String,
140
141    #[serde(rename = "objectType", default)]
142    pub object_type: String,
143
144    #[serde(rename = "emailAddress", default)]
145    pub email_address: Option<String>,
146}
147
148/// Object in a Mercury activity.
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct MercuryObject {
151    #[serde(default)]
152    pub id: String,
153
154    #[serde(rename = "objectType", default)]
155    pub object_type: String,
156
157    #[serde(rename = "displayName", default)]
158    pub display_name: Option<String>,
159
160    #[serde(default)]
161    pub content: Option<String>,
162
163    #[serde(rename = "encryptionKeyUrl", default)]
164    pub encryption_key_url: Option<String>,
165
166    /// Card form input values (present on cardAction/submit activities).
167    #[serde(default)]
168    pub inputs: Option<serde_json::Value>,
169
170    /// File URLs attached to the message (present on file-share messages).
171    #[serde(default)]
172    pub files: Option<Vec<String>>,
173}
174
175/// Target in a Mercury activity.
176#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177pub struct MercuryTarget {
178    #[serde(default)]
179    pub id: String,
180
181    #[serde(rename = "objectType", default)]
182    pub object_type: String,
183
184    #[serde(rename = "encryptionKeyUrl", default)]
185    pub encryption_key_url: Option<String>,
186
187    #[serde(default)]
188    pub tags: Vec<String>,
189}
190
191/// Parent activity reference for threaded replies.
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct MercuryParent {
194    #[serde(default)]
195    pub id: String,
196
197    #[serde(rename = "type", default)]
198    pub parent_type: String,
199}
200
201/// A Mercury conversation activity.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct MercuryActivity {
204    #[serde(default)]
205    pub id: String,
206
207    #[serde(default)]
208    pub verb: String,
209
210    #[serde(default)]
211    pub actor: MercuryActor,
212
213    #[serde(default)]
214    pub object: MercuryObject,
215
216    #[serde(default)]
217    pub target: MercuryTarget,
218
219    #[serde(default)]
220    pub published: String,
221
222    #[serde(rename = "encryptionKeyUrl", default)]
223    pub encryption_key_url: Option<String>,
224
225    #[serde(default)]
226    pub parent: Option<MercuryParent>,
227}
228
229/// A decrypted Webex message.
230#[derive(Debug, Clone)]
231pub struct DecryptedMessage {
232    /// Mercury activity UUID. Works as parentId for threaded replies.
233    pub id: String,
234
235    /// Parent activity UUID for threaded replies. None if not a thread reply.
236    pub parent_id: Option<String>,
237
238    /// Person UUIDs mentioned via @mention in the message.
239    pub mentioned_people: Vec<String>,
240
241    /// Group mention types (e.g. "all") in the message.
242    pub mentioned_groups: Vec<String>,
243
244    /// Conversation/space ID.
245    pub room_id: String,
246
247    /// Sender's user ID.
248    pub person_id: String,
249
250    /// Sender's email address.
251    pub person_email: String,
252
253    /// Decrypted plain text.
254    pub text: String,
255
256    /// Decrypted HTML content (rich text messages).
257    pub html: Option<String>,
258
259    /// ISO 8601 timestamp.
260    pub created: String,
261
262    /// "direct", "group", or None.
263    pub room_type: Option<String>,
264
265    /// File URLs attached to the message.
266    pub files: Vec<String>,
267
268    /// Full decrypted activity for advanced use.
269    pub raw: MercuryActivity,
270}
271
272/// A deleted Webex message notification.
273#[derive(Debug, Clone)]
274pub struct DeletedMessage {
275    pub message_id: String,
276    pub room_id: String,
277    pub person_id: String,
278}
279
280/// A membership activity from Mercury.
281#[derive(Debug, Clone)]
282pub struct MembershipActivity {
283    /// Activity ID.
284    pub id: String,
285
286    /// ID of the person who performed the action.
287    pub actor_id: String,
288
289    /// ID of the member affected.
290    pub person_id: String,
291
292    /// Conversation/space ID.
293    pub room_id: String,
294
295    /// Membership action: "add", "leave", "assignModerator", or "unassignModerator".
296    pub action: String,
297
298    /// ISO 8601 timestamp.
299    pub created: String,
300
301    /// "direct", "group", or None.
302    pub room_type: Option<String>,
303
304    /// Full raw activity for advanced use.
305    pub raw: MercuryActivity,
306}
307
308/// An adaptive card submission from Mercury.
309#[derive(Debug, Clone)]
310pub struct AttachmentAction {
311    /// Activity ID.
312    pub id: String,
313
314    /// ID of the message the card was attached to.
315    pub message_id: String,
316
317    /// ID of the person who submitted the card.
318    pub person_id: String,
319
320    /// Email of the person who submitted the card.
321    pub person_email: String,
322
323    /// Conversation/space ID.
324    pub room_id: String,
325
326    /// Card form input values.
327    pub inputs: serde_json::Value,
328
329    /// ISO 8601 timestamp.
330    pub created: String,
331
332    /// Full raw activity for advanced use.
333    pub raw: MercuryActivity,
334}
335
336/// A room event from Mercury.
337#[derive(Debug, Clone)]
338pub struct RoomActivity {
339    /// Activity ID.
340    pub id: String,
341
342    /// Conversation/space ID.
343    pub room_id: String,
344
345    /// ID of the person who performed the action.
346    pub actor_id: String,
347
348    /// Room action: "created" or "updated".
349    pub action: String,
350
351    /// ISO 8601 timestamp.
352    pub created: String,
353
354    /// Full raw activity for advanced use.
355    pub raw: MercuryActivity,
356}
357
358/// Overall connection state.
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
360pub enum ConnectionStatus {
361    Connected,
362    Connecting,
363    Reconnecting,
364    Disconnected,
365}
366
367impl std::fmt::Display for ConnectionStatus {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        match self {
370            Self::Connected => write!(f, "connected"),
371            Self::Connecting => write!(f, "connecting"),
372            Self::Reconnecting => write!(f, "reconnecting"),
373            Self::Disconnected => write!(f, "disconnected"),
374        }
375    }
376}
377
378/// Structured health check of all connection subsystems.
379#[derive(Debug, Clone)]
380pub struct HandlerStatus {
381    /// Overall connection state.
382    pub status: ConnectionStatus,
383
384    /// Whether the Mercury WebSocket is currently open.
385    pub web_socket_open: bool,
386
387    /// Whether the KMS encryption context has been established.
388    pub kms_initialized: bool,
389
390    /// Whether the device is registered with WDM.
391    pub device_registered: bool,
392
393    /// Current auto-reconnect attempt number (0 if not reconnecting).
394    pub reconnect_attempt: u32,
395}