1use serde::{Deserialize, Serialize};
19use std::time::{Duration, SystemTime};
20
21pub const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_secs(60);
23
24#[derive(Debug, Clone)]
26pub struct ConnectionRequest {
27 pub id: String,
29
30 pub status: ConnectionStatus,
32
33 pub redirect_url: Option<String>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
40pub enum ConnectionStatus {
41 Initializing,
43
44 Initiated,
46
47 Active,
49
50 Expired,
52
53 Failed,
55
56 Inactive,
58}
59
60impl ConnectionRequest {
61 pub fn new(id: String, status: ConnectionStatus, redirect_url: Option<String>) -> Self {
63 Self {
64 id,
65 status,
66 redirect_url,
67 }
68 }
69
70 pub async fn wait_for_connection(
100 &mut self,
101 timeout: Option<Duration>,
102 ) -> Result<(), ConnectionError> {
103 let timeout = timeout.unwrap_or(DEFAULT_WAIT_TIMEOUT);
104 let deadline = SystemTime::now() + timeout;
105
106 while SystemTime::now() < deadline {
107 if self.status == ConnectionStatus::Active {
110 return Ok(());
111 }
112
113 if self.status == ConnectionStatus::Failed {
114 return Err(ConnectionError::Failed(format!(
115 "Connection {} failed",
116 self.id
117 )));
118 }
119
120 tokio::time::sleep(Duration::from_secs(1)).await;
122 }
123
124 Err(ConnectionError::Timeout(format!(
125 "Timeout while waiting for connection {} to be active",
126 self.id
127 )))
128 }
129}
130
131#[derive(Debug, thiserror::Error)]
133pub enum ConnectionError {
134 #[error("Connection timeout: {0}")]
136 Timeout(String),
137
138 #[error("Connection failed: {0}")]
140 Failed(String),
141
142 #[error("Multiple connected accounts found: {0}")]
144 MultipleAccounts(String),
145
146 #[error("API error: {0}")]
148 ApiError(String),
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
154pub enum AuthScheme {
155 Oauth1,
157
158 Oauth2,
160
161 ComposioLink,
163
164 ApiKey,
166
167 Basic,
169
170 BearerToken,
172
173 GoogleServiceAccount,
175
176 NoAuth,
178
179 CalcomAuth,
181
182 BillcomAuth,
184
185 BasicWithJwt,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ConnectionState {
192 pub auth_scheme: AuthScheme,
194
195 pub status: ConnectionStatus,
197
198 #[serde(flatten)]
200 pub config: serde_json::Value,
201}
202
203pub struct AuthSchemeHelper;
205
206impl AuthSchemeHelper {
207 pub fn oauth1(config: serde_json::Value) -> ConnectionState {
209 ConnectionState {
210 auth_scheme: AuthScheme::Oauth1,
211 status: ConnectionStatus::Initializing,
212 config,
213 }
214 }
215
216 pub fn oauth2(config: serde_json::Value) -> ConnectionState {
218 ConnectionState {
219 auth_scheme: AuthScheme::Oauth2,
220 status: ConnectionStatus::Initializing,
221 config,
222 }
223 }
224
225 pub fn composio_link(config: serde_json::Value) -> ConnectionState {
227 ConnectionState {
228 auth_scheme: AuthScheme::ComposioLink,
229 status: ConnectionStatus::Initializing,
230 config,
231 }
232 }
233
234 pub fn api_key(config: serde_json::Value) -> ConnectionState {
236 ConnectionState {
237 auth_scheme: AuthScheme::ApiKey,
238 status: ConnectionStatus::Active,
239 config,
240 }
241 }
242
243 pub fn basic(config: serde_json::Value) -> ConnectionState {
245 ConnectionState {
246 auth_scheme: AuthScheme::Basic,
247 status: ConnectionStatus::Active,
248 config,
249 }
250 }
251
252 pub fn bearer_token(config: serde_json::Value) -> ConnectionState {
254 ConnectionState {
255 auth_scheme: AuthScheme::BearerToken,
256 status: ConnectionStatus::Active,
257 config,
258 }
259 }
260
261 pub fn google_service_account(config: serde_json::Value) -> ConnectionState {
263 ConnectionState {
264 auth_scheme: AuthScheme::GoogleServiceAccount,
265 status: ConnectionStatus::Active,
266 config,
267 }
268 }
269
270 pub fn no_auth(config: serde_json::Value) -> ConnectionState {
272 ConnectionState {
273 auth_scheme: AuthScheme::NoAuth,
274 status: ConnectionStatus::Active,
275 config,
276 }
277 }
278
279 pub fn calcom_auth(config: serde_json::Value) -> ConnectionState {
281 ConnectionState {
282 auth_scheme: AuthScheme::CalcomAuth,
283 status: ConnectionStatus::Active,
284 config,
285 }
286 }
287
288 pub fn billcom_auth(config: serde_json::Value) -> ConnectionState {
290 ConnectionState {
291 auth_scheme: AuthScheme::BillcomAuth,
292 status: ConnectionStatus::Active,
293 config,
294 }
295 }
296
297 pub fn basic_with_jwt(config: serde_json::Value) -> ConnectionState {
299 ConnectionState {
300 auth_scheme: AuthScheme::BasicWithJwt,
301 status: ConnectionStatus::Active,
302 config,
303 }
304 }
305}
306
307pub static AUTH_SCHEME: AuthSchemeHelper = AuthSchemeHelper;
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct InitiateConnectionParams {
313 pub user_id: String,
315
316 pub auth_config_id: String,
318
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub callback_url: Option<String>,
322
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub allow_multiple: Option<bool>,
326
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub config: Option<ConnectionState>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct LinkConnectionParams {
335 pub user_id: String,
337
338 pub auth_config_id: String,
340
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub callback_url: Option<String>,
344}
345
346#[derive(Debug, Clone, Default, Serialize, Deserialize)]
348pub struct ConnectedAccountListParams {
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub user_ids: Option<Vec<String>>,
352
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub auth_config_ids: Option<Vec<String>>,
356
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub statuses: Option<Vec<ConnectionStatus>>,
360
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub toolkit_slugs: Option<Vec<String>>,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub connected_account_ids: Option<Vec<String>>,
368
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub show_disabled: Option<bool>,
372
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub limit: Option<u32>,
376
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub cursor: Option<String>,
380
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub order_by: Option<String>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
387 pub order_direction: Option<String>,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct ConnectedAccountInfo {
393 pub id: String,
395
396 pub user_id: String,
398
399 pub auth_config_id: String,
401
402 pub toolkit: String,
404
405 pub status: ConnectionStatus,
407
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub status_reason: Option<String>,
411
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub is_disabled: Option<bool>,
415
416 pub created_at: String,
418
419 pub updated_at: String,
421
422 #[serde(skip_serializing_if = "Option::is_none")]
424 pub state: Option<serde_json::Value>,
425
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub data: Option<serde_json::Value>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct ConnectedAccountListResponse {
434 pub items: Vec<ConnectedAccountInfo>,
436
437 #[serde(skip_serializing_if = "Option::is_none")]
439 pub next_cursor: Option<String>,
440
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub total_pages: Option<u32>,
444
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub current_page: Option<u32>,
448
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub total_items: Option<u32>,
452}
453
454#[derive(Debug, Clone, Default, Serialize, Deserialize)]
456pub struct ConnectedAccountRefreshParams {
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub redirect_url: Option<String>,
460
461 #[serde(skip_serializing_if = "Option::is_none")]
463 pub validate_credentials: Option<bool>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct ConnectedAccountRefreshResponse {
469 pub id: String,
471
472 pub status: ConnectionStatus,
474
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub redirect_url: Option<String>,
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct ConnectedAccountUpdateStatusParams {
483 pub enabled: bool,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct ConnectedAccountDeleteResponse {
490 pub success: bool,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct ConnectedAccountUpdateStatusResponse {
497 pub success: bool,
499}
500
501#[cfg(test)]
505mod tests {
506 use super::*;
507
508 #[test]
509 fn test_connection_status_serialization() {
510 let status = ConnectionStatus::Active;
511 let json = serde_json::to_string(&status).unwrap();
512 assert_eq!(json, "\"ACTIVE\"");
513 }
514
515 #[test]
516 fn test_connection_status_deserialization() {
517 let json = "\"ACTIVE\"";
518 let status: ConnectionStatus = serde_json::from_str(json).unwrap();
519 assert_eq!(status, ConnectionStatus::Active);
520 }
521
522 #[test]
523 fn test_auth_scheme_serialization() {
524 let scheme = AuthScheme::Oauth2;
525 let json = serde_json::to_string(&scheme).unwrap();
526 assert_eq!(json, "\"OAUTH2\"");
527 }
528
529 #[test]
530 fn test_auth_scheme_helper_oauth2() {
531 let config = serde_json::json!({"client_id": "test"});
532 let state = AuthSchemeHelper::oauth2(config);
533 assert_eq!(state.auth_scheme, AuthScheme::Oauth2);
534 assert_eq!(state.status, ConnectionStatus::Initializing);
535 }
536
537 #[test]
538 fn test_auth_scheme_helper_api_key() {
539 let config = serde_json::json!({"api_key": "test_key"});
540 let state = AuthSchemeHelper::api_key(config);
541 assert_eq!(state.auth_scheme, AuthScheme::ApiKey);
542 assert_eq!(state.status, ConnectionStatus::Active);
543 }
544
545 #[test]
546 fn test_connection_request_new() {
547 let request = ConnectionRequest::new(
548 "ca_test123".to_string(),
549 ConnectionStatus::Initiated,
550 Some("https://auth.example.com".to_string()),
551 );
552 assert_eq!(request.id, "ca_test123");
553 assert_eq!(request.status, ConnectionStatus::Initiated);
554 assert!(request.redirect_url.is_some());
555 }
556
557 #[test]
558 fn test_connected_account_list_params_default() {
559 let params = ConnectedAccountListParams::default();
560 assert!(params.user_ids.is_none());
561 assert!(params.auth_config_ids.is_none());
562 assert!(params.statuses.is_none());
563 }
564
565 #[test]
566 fn test_initiate_connection_params_serialization() {
567 let params = InitiateConnectionParams {
568 user_id: "user_123".to_string(),
569 auth_config_id: "ac_abc".to_string(),
570 callback_url: Some("https://callback.example.com".to_string()),
571 allow_multiple: Some(false),
572 config: None,
573 };
574
575 let json = serde_json::to_string(¶ms).unwrap();
576 assert!(json.contains("user_123"));
577 assert!(json.contains("ac_abc"));
578 assert!(json.contains("callback"));
579 }
580}