use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEventType {
#[serde(rename = "composio.connected_account.expired")]
ConnectionExpired,
#[serde(rename = "composio.trigger.message")]
TriggerMessage,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ConnectionStatus {
Initializing,
Initiated,
Active,
Failed,
Expired,
Inactive,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectedAccountToolkit {
pub slug: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectedAccountAuthConfigDeprecated {
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectedAccountAuthConfig {
pub id: String,
pub auth_scheme: String,
pub is_composio_managed: bool,
pub is_disabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<ConnectedAccountAuthConfigDeprecated>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionStateVal {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_in: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code_verifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub oauth_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub oauth_token_secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generic_api_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionState {
#[serde(rename = "authScheme")]
pub auth_scheme: String,
pub val: ConnectionStateVal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectedAccountDeprecated {
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uuid: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SingleConnectedAccountDetailedResponse {
pub toolkit: ConnectedAccountToolkit,
pub auth_config: ConnectedAccountAuthConfig,
pub id: String,
pub user_id: String,
pub status: String,
pub created_at: String,
pub updated_at: String,
pub state: ConnectionState,
pub data: HashMap<String, serde_json::Value>,
pub params: HashMap<String, serde_json::Value>,
pub status_reason: Option<String>,
pub is_disabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub test_request_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<ConnectedAccountDeprecated>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebhookConnectionMetadata {
pub project_id: String,
pub org_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionExpiredEvent {
pub id: String,
pub timestamp: String,
#[serde(rename = "type")]
pub event_type: String,
pub data: SingleConnectedAccountDetailedResponse,
pub metadata: WebhookConnectionMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WebhookEvent {
ConnectionExpired(ConnectionExpiredEvent),
}
pub fn is_connection_expired_event(payload: &serde_json::Value) -> bool {
payload
.get("type")
.and_then(|t| t.as_str())
.map(|t| t == "composio.connected_account.expired")
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_webhook_event_type_serialization() {
let event_type = WebhookEventType::ConnectionExpired;
let json = serde_json::to_string(&event_type).unwrap();
assert_eq!(json, "\"composio.connected_account.expired\"");
let event_type = WebhookEventType::TriggerMessage;
let json = serde_json::to_string(&event_type).unwrap();
assert_eq!(json, "\"composio.trigger.message\"");
}
#[test]
fn test_connection_status_serialization() {
let status = ConnectionStatus::Active;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"ACTIVE\"");
let status = ConnectionStatus::Expired;
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, "\"EXPIRED\"");
}
#[test]
fn test_is_connection_expired_event() {
let payload = json!({
"type": "composio.connected_account.expired",
"id": "msg_123",
"timestamp": "2024-01-01T00:00:00Z"
});
assert!(is_connection_expired_event(&payload));
let payload = json!({
"type": "composio.trigger.message",
"id": "msg_456"
});
assert!(!is_connection_expired_event(&payload));
}
#[test]
fn test_connection_expired_event_deserialization() {
let json = json!({
"id": "msg_847cdfcd-d219-4f18-a6dd-91acd42ca94a",
"timestamp": "2024-01-01T12:00:00Z",
"type": "composio.connected_account.expired",
"data": {
"toolkit": {
"slug": "github"
},
"auth_config": {
"id": "ac_123",
"auth_scheme": "OAUTH2",
"is_composio_managed": true,
"is_disabled": false
},
"id": "ca_456",
"user_id": "user_789",
"status": "EXPIRED",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T12:00:00Z",
"state": {
"authScheme": "OAUTH2",
"val": {
"status": "expired"
}
},
"data": {},
"params": {},
"status_reason": "Refresh token expired",
"is_disabled": false
},
"metadata": {
"project_id": "proj_123",
"org_id": "org_456"
}
});
let event: ConnectionExpiredEvent = serde_json::from_value(json).unwrap();
assert_eq!(event.id, "msg_847cdfcd-d219-4f18-a6dd-91acd42ca94a");
assert_eq!(event.event_type, "composio.connected_account.expired");
assert_eq!(event.data.toolkit.slug, "github");
assert_eq!(event.data.user_id, "user_789");
assert_eq!(event.data.status, "EXPIRED");
assert_eq!(event.metadata.project_id, "proj_123");
}
#[test]
fn test_connection_state_val_oauth2() {
let json = json!({
"status": "active",
"access_token": "token_123",
"refresh_token": "refresh_456",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "repo user"
});
let state_val: ConnectionStateVal = serde_json::from_value(json).unwrap();
assert_eq!(state_val.status, Some("active".to_string()));
assert_eq!(state_val.access_token, Some("token_123".to_string()));
assert_eq!(state_val.token_type, Some("Bearer".to_string()));
}
#[test]
fn test_connection_state_val_api_key() {
let json = json!({
"status": "active",
"api_key": "sk_test_123"
});
let state_val: ConnectionStateVal = serde_json::from_value(json).unwrap();
assert_eq!(state_val.api_key, Some("sk_test_123".to_string()));
}
}