1use serde::{Deserialize, Serialize};
33use std::collections::HashMap;
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "snake_case")]
38pub enum WebhookEventType {
39 #[serde(rename = "composio.connected_account.expired")]
41 ConnectionExpired,
42
43 #[serde(rename = "composio.trigger.message")]
45 TriggerMessage,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
51pub enum ConnectionStatus {
52 Initializing,
54
55 Initiated,
57
58 Active,
60
61 Failed,
63
64 Expired,
66
67 Inactive,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ConnectedAccountToolkit {
78 pub slug: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ConnectedAccountAuthConfigDeprecated {
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub uuid: Option<String>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ConnectedAccountAuthConfig {
93 pub id: String,
95
96 pub auth_scheme: String,
98
99 pub is_composio_managed: bool,
101
102 pub is_disabled: bool,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub deprecated: Option<ConnectedAccountAuthConfigDeprecated>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct ConnectionStateVal {
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub status: Option<String>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
120 pub access_token: Option<String>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub refresh_token: Option<String>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub token_type: Option<String>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub expires_in: Option<serde_json::Value>, #[serde(skip_serializing_if = "Option::is_none")]
136 pub scope: Option<serde_json::Value>, #[serde(skip_serializing_if = "Option::is_none")]
140 pub id_token: Option<String>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub code_verifier: Option<String>,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub callback_url: Option<String>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
153 pub oauth_token: Option<String>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub oauth_token_secret: Option<String>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
162 pub api_key: Option<String>,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub generic_api_key: Option<String>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
171 pub token: Option<String>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
176 pub username: Option<String>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub password: Option<String>,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ConnectionState {
186 #[serde(rename = "authScheme")]
188 pub auth_scheme: String,
189
190 pub val: ConnectionStateVal,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ConnectedAccountDeprecated {
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub labels: Option<Vec<String>>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub uuid: Option<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct SingleConnectedAccountDetailedResponse {
215 pub toolkit: ConnectedAccountToolkit,
217
218 pub auth_config: ConnectedAccountAuthConfig,
220
221 pub id: String,
223
224 pub user_id: String,
226
227 pub status: String, pub created_at: String,
232
233 pub updated_at: String,
235
236 pub state: ConnectionState,
238
239 pub data: HashMap<String, serde_json::Value>,
241
242 pub params: HashMap<String, serde_json::Value>,
244
245 pub status_reason: Option<String>,
247
248 pub is_disabled: bool,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub test_request_endpoint: Option<String>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub deprecated: Option<ConnectedAccountDeprecated>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct WebhookConnectionMetadata {
267 pub project_id: String,
269
270 pub org_id: String,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct ConnectionExpiredEvent {
295 pub id: String,
297
298 pub timestamp: String,
300
301 #[serde(rename = "type")]
303 pub event_type: String,
304
305 pub data: SingleConnectedAccountDetailedResponse,
307
308 pub metadata: WebhookConnectionMetadata,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
317#[serde(untagged)]
318pub enum WebhookEvent {
319 ConnectionExpired(ConnectionExpiredEvent),
321}
322
323pub fn is_connection_expired_event(payload: &serde_json::Value) -> bool {
355 payload
356 .get("type")
357 .and_then(|t| t.as_str())
358 .map(|t| t == "composio.connected_account.expired")
359 .unwrap_or(false)
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use serde_json::json;
366
367 #[test]
368 fn test_webhook_event_type_serialization() {
369 let event_type = WebhookEventType::ConnectionExpired;
370 let json = serde_json::to_string(&event_type).unwrap();
371 assert_eq!(json, "\"composio.connected_account.expired\"");
372
373 let event_type = WebhookEventType::TriggerMessage;
374 let json = serde_json::to_string(&event_type).unwrap();
375 assert_eq!(json, "\"composio.trigger.message\"");
376 }
377
378 #[test]
379 fn test_connection_status_serialization() {
380 let status = ConnectionStatus::Active;
381 let json = serde_json::to_string(&status).unwrap();
382 assert_eq!(json, "\"ACTIVE\"");
383
384 let status = ConnectionStatus::Expired;
385 let json = serde_json::to_string(&status).unwrap();
386 assert_eq!(json, "\"EXPIRED\"");
387 }
388
389 #[test]
390 fn test_is_connection_expired_event() {
391 let payload = json!({
392 "type": "composio.connected_account.expired",
393 "id": "msg_123",
394 "timestamp": "2024-01-01T00:00:00Z"
395 });
396
397 assert!(is_connection_expired_event(&payload));
398
399 let payload = json!({
400 "type": "composio.trigger.message",
401 "id": "msg_456"
402 });
403
404 assert!(!is_connection_expired_event(&payload));
405 }
406
407 #[test]
408 fn test_connection_expired_event_deserialization() {
409 let json = json!({
410 "id": "msg_847cdfcd-d219-4f18-a6dd-91acd42ca94a",
411 "timestamp": "2024-01-01T12:00:00Z",
412 "type": "composio.connected_account.expired",
413 "data": {
414 "toolkit": {
415 "slug": "github"
416 },
417 "auth_config": {
418 "id": "ac_123",
419 "auth_scheme": "OAUTH2",
420 "is_composio_managed": true,
421 "is_disabled": false
422 },
423 "id": "ca_456",
424 "user_id": "user_789",
425 "status": "EXPIRED",
426 "created_at": "2024-01-01T00:00:00Z",
427 "updated_at": "2024-01-01T12:00:00Z",
428 "state": {
429 "authScheme": "OAUTH2",
430 "val": {
431 "status": "expired"
432 }
433 },
434 "data": {},
435 "params": {},
436 "status_reason": "Refresh token expired",
437 "is_disabled": false
438 },
439 "metadata": {
440 "project_id": "proj_123",
441 "org_id": "org_456"
442 }
443 });
444
445 let event: ConnectionExpiredEvent = serde_json::from_value(json).unwrap();
446 assert_eq!(event.id, "msg_847cdfcd-d219-4f18-a6dd-91acd42ca94a");
447 assert_eq!(event.event_type, "composio.connected_account.expired");
448 assert_eq!(event.data.toolkit.slug, "github");
449 assert_eq!(event.data.user_id, "user_789");
450 assert_eq!(event.data.status, "EXPIRED");
451 assert_eq!(event.metadata.project_id, "proj_123");
452 }
453
454 #[test]
455 fn test_connection_state_val_oauth2() {
456 let json = json!({
457 "status": "active",
458 "access_token": "token_123",
459 "refresh_token": "refresh_456",
460 "token_type": "Bearer",
461 "expires_in": 3600,
462 "scope": "repo user"
463 });
464
465 let state_val: ConnectionStateVal = serde_json::from_value(json).unwrap();
466 assert_eq!(state_val.status, Some("active".to_string()));
467 assert_eq!(state_val.access_token, Some("token_123".to_string()));
468 assert_eq!(state_val.token_type, Some("Bearer".to_string()));
469 }
470
471 #[test]
472 fn test_connection_state_val_api_key() {
473 let json = json!({
474 "status": "active",
475 "api_key": "sk_test_123"
476 });
477
478 let state_val: ConnectionStateVal = serde_json::from_value(json).unwrap();
479 assert_eq!(state_val.api_key, Some("sk_test_123".to_string()));
480 }
481}