Skip to main content

composio_sdk/models/
triggers.rs

1//! Triggers management
2//!
3//! This module provides functionality to manage triggers in Composio.
4//! Triggers are event listeners that notify your application when specific
5//! events occur in connected services.
6//!
7//! # Overview
8//!
9//! Triggers can be:
10//! - Webhook-based (real-time notifications)
11//! - Poll-based (periodic checks)
12//!
13//! # Trigger Types vs Trigger Instances
14//!
15//! - **Trigger Type**: Template defining what event to listen for (e.g., "GITHUB_COMMIT_EVENT")
16//! - **Trigger Instance**: Active listener for a specific user and connected account
17
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20
21/// Webhook payload version
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
24pub enum WebhookVersion {
25    /// Version 1 (legacy)
26    V1,
27    /// Version 2 (legacy)
28    V2,
29    /// Version 3 (current)
30    V3,
31}
32
33/// Trigger event data
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct TriggerEvent {
36    /// Trigger instance ID
37    pub id: String,
38
39    /// Trigger instance UUID
40    pub uuid: String,
41
42    /// User ID
43    pub user_id: String,
44
45    /// Toolkit slug
46    pub toolkit_slug: String,
47
48    /// Trigger slug
49    pub trigger_slug: String,
50
51    /// Event metadata
52    pub metadata: TriggerMetadata,
53
54    /// Event payload
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub payload: Option<serde_json::Value>,
57
58    /// Original payload from the service
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub original_payload: Option<serde_json::Value>,
61}
62
63/// Trigger event metadata
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct TriggerMetadata {
66    /// Trigger instance ID
67    pub id: String,
68
69    /// Trigger instance UUID
70    pub uuid: String,
71
72    /// Toolkit slug
73    pub toolkit_slug: String,
74
75    /// Trigger slug
76    pub trigger_slug: String,
77
78    /// Trigger data (JSON string)
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub trigger_data: Option<String>,
81
82    /// Trigger configuration
83    pub trigger_config: serde_json::Value,
84
85    /// Connected account information
86    pub connected_account: TriggerConnectedAccount,
87}
88
89/// Connected account information in trigger metadata
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct TriggerConnectedAccount {
92    /// Connected account ID
93    pub id: String,
94
95    /// Connected account UUID
96    pub uuid: String,
97
98    /// Auth config ID
99    pub auth_config_id: String,
100
101    /// Auth config UUID
102    pub auth_config_uuid: String,
103
104    /// User ID
105    pub user_id: String,
106
107    /// Connection status
108    pub status: String,
109}
110
111/// Webhook verification result
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct VerifyWebhookResult {
114    /// Detected webhook version
115    pub version: WebhookVersion,
116
117    /// Normalized trigger event
118    pub payload: TriggerEvent,
119
120    /// Raw webhook payload
121    pub raw_payload: serde_json::Value,
122}
123
124/// Trigger type information
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct TriggerType {
127    /// Trigger slug
128    pub slug: String,
129
130    /// Trigger name
131    pub name: String,
132
133    /// Trigger description
134    pub description: String,
135
136    /// Trigger instructions
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub instructions: Option<String>,
139
140    /// Trigger type (webhook or poll)
141    #[serde(rename = "type")]
142    pub trigger_type: String,
143
144    /// Toolkit information
145    pub toolkit: TriggerToolkitRef,
146
147    /// Configuration schema
148    pub config: serde_json::Value,
149
150    /// Payload schema
151    pub payload: serde_json::Value,
152
153    /// Trigger version
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub version: Option<String>,
156}
157
158/// Toolkit reference in trigger type
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct TriggerToolkitRef {
161    /// Toolkit slug
162    pub slug: String,
163
164    /// Toolkit name
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub name: Option<String>,
167
168    /// Toolkit logo URL
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub logo: Option<String>,
171}
172
173/// Trigger instance information
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct TriggerInstance {
176    /// Trigger instance ID
177    pub id: String,
178
179    /// Trigger instance UUID
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub uuid: Option<String>,
182
183    /// Trigger name/slug
184    pub trigger_name: String,
185
186    /// Connected account ID
187    pub connected_account_id: String,
188
189    /// User ID
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub user_id: Option<String>,
192
193    /// Trigger configuration
194    pub trigger_config: serde_json::Value,
195
196    /// Trigger state
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub state: Option<String>,
199
200    /// Creation timestamp
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub created_at: Option<String>,
203
204    /// Update timestamp
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub updated_at: Option<String>,
207
208    /// Disabled timestamp
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub disabled_at: Option<String>,
211}
212
213/// Parameters for retrieving a trigger type
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
215pub struct TriggerTypeRetrieveParams {
216    /// Toolkit version specification (for example: "latest" or bracket notation).
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub toolkit_versions: Option<String>,
219}
220
221/// Trigger type enum response
222///
223/// Contains all available trigger type slug enumeration values.
224pub type TriggerTypeRetrieveEnumResponse = Vec<String>;
225
226/// Parameters for listing trigger types
227#[derive(Debug, Clone, Default, Serialize, Deserialize)]
228pub struct TriggerTypeListParams {
229    /// Pagination cursor
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub cursor: Option<String>,
232
233    /// Maximum number of results
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub limit: Option<u32>,
236
237    /// Filter by toolkit slugs
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub toolkit_slugs: Option<Vec<String>>,
240
241    /// Toolkit versions
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub toolkit_versions: Option<String>,
244}
245
246/// Response from listing trigger types
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct TriggerTypeListResponse {
249    /// List of trigger types
250    pub items: Vec<TriggerType>,
251
252    /// Next cursor for pagination
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub next_cursor: Option<String>,
255
256    /// Total number of pages
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub total_pages: Option<u32>,
259
260    /// Current page number
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub current_page: Option<u32>,
263
264    /// Total number of items
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub total_items: Option<u32>,
267}
268
269/// Parameters for listing active trigger instances
270#[derive(Debug, Clone, Default, Serialize, Deserialize)]
271pub struct TriggerInstanceListParams {
272    /// Filter by trigger IDs
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub trigger_ids: Option<Vec<String>>,
275
276    /// Filter by trigger names
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub trigger_names: Option<Vec<String>>,
279
280    /// Filter by auth config IDs
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub auth_config_ids: Option<Vec<String>>,
283
284    /// Filter by connected account IDs
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub connected_account_ids: Option<Vec<String>>,
287
288    /// Show disabled triggers
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub show_disabled: Option<bool>,
291
292    /// Maximum number of results
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub limit: Option<u32>,
295
296    /// Pagination cursor
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub cursor: Option<String>,
299}
300
301/// Response from listing active trigger instances
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct TriggerInstanceListResponse {
304    /// List of trigger instances
305    pub items: Vec<TriggerInstance>,
306
307    /// Next cursor for pagination
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub next_cursor: Option<String>,
310
311    /// Total number of pages
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub total_pages: Option<u32>,
314
315    /// Current page number
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub current_page: Option<u32>,
318
319    /// Total number of items
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub total_items: Option<u32>,
322}
323
324/// Parameters for creating a trigger instance
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct TriggerCreateParams {
327    /// Trigger slug
328    pub slug: String,
329
330    /// Connected account ID (required if user_id not provided)
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub connected_account_id: Option<String>,
333
334    /// User ID (will auto-find connected account)
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub user_id: Option<String>,
337
338    /// Trigger configuration
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub trigger_config: Option<HashMap<String, serde_json::Value>>,
341
342    /// Toolkit versions
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub toolkit_versions: Option<String>,
345}
346
347/// Response from creating or updating a trigger instance
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct TriggerCreateResponse {
350    /// Trigger instance ID
351    pub id: String,
352
353    /// Trigger name/slug
354    pub trigger_name: String,
355
356    /// Connected account ID
357    pub connected_account_id: String,
358
359    /// User ID
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub user_id: Option<String>,
362
363    /// Trigger configuration
364    pub trigger_config: serde_json::Value,
365
366    /// Trigger state
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub state: Option<String>,
369}
370
371/// Parameters for webhook verification
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct WebhookVerifyParams {
374    /// Webhook message ID from 'webhook-id' header
375    pub id: String,
376
377    /// Raw webhook payload as string
378    pub payload: String,
379
380    /// Webhook secret from Composio dashboard
381    pub secret: String,
382
383    /// Signature from 'webhook-signature' header
384    pub signature: String,
385
386    /// Timestamp from 'webhook-timestamp' header
387    pub timestamp: String,
388
389    /// Maximum allowed age in seconds (default: 300)
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub tolerance: Option<u32>,
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn test_webhook_version_serialization() {
400        let v1 = WebhookVersion::V1;
401        let json = serde_json::to_string(&v1).unwrap();
402        assert_eq!(json, "\"V1\"");
403
404        let v3 = WebhookVersion::V3;
405        let json = serde_json::to_string(&v3).unwrap();
406        assert_eq!(json, "\"V3\"");
407    }
408
409    #[test]
410    fn test_trigger_type_retrieve_params_serialization() {
411        let params = TriggerTypeRetrieveParams {
412            toolkit_versions: Some("latest".to_string()),
413        };
414
415        let value = serde_json::to_value(&params).unwrap();
416        assert_eq!(value["toolkit_versions"], "latest");
417    }
418
419    #[test]
420    fn test_trigger_type_retrieve_enum_response_deserialization() {
421        let payload = serde_json::json!(["GITHUB_COMMIT_EVENT", "SLACK_NEW_MESSAGE"]);
422
423        let response: TriggerTypeRetrieveEnumResponse = serde_json::from_value(payload).unwrap();
424        assert_eq!(response.len(), 2);
425        assert_eq!(response[0], "GITHUB_COMMIT_EVENT");
426    }
427
428    #[test]
429    fn test_trigger_type_list_params_default() {
430        let params = TriggerTypeListParams::default();
431        assert!(params.cursor.is_none());
432        assert!(params.limit.is_none());
433        assert!(params.toolkit_slugs.is_none());
434    }
435
436    #[test]
437    fn test_trigger_instance_list_params_default() {
438        let params = TriggerInstanceListParams::default();
439        assert!(params.trigger_ids.is_none());
440        assert!(params.trigger_names.is_none());
441        assert!(params.show_disabled.is_none());
442    }
443
444    #[test]
445    fn test_trigger_event_deserialization() {
446        let json = r#"{
447            "id": "ti_123",
448            "uuid": "uuid_123",
449            "user_id": "user_456",
450            "toolkit_slug": "github",
451            "trigger_slug": "GITHUB_COMMIT_EVENT",
452            "metadata": {
453                "id": "ti_123",
454                "uuid": "uuid_123",
455                "toolkit_slug": "github",
456                "trigger_slug": "GITHUB_COMMIT_EVENT",
457                "trigger_data": null,
458                "trigger_config": {},
459                "connected_account": {
460                    "id": "ca_789",
461                    "uuid": "ca_uuid_789",
462                    "auth_config_id": "ac_101",
463                    "auth_config_uuid": "ac_uuid_101",
464                    "user_id": "user_456",
465                    "status": "ACTIVE"
466                }
467            },
468            "payload": {"test": "data"},
469            "original_payload": null
470        }"#;
471
472        let event: TriggerEvent = serde_json::from_str(json).unwrap();
473        assert_eq!(event.id, "ti_123");
474        assert_eq!(event.user_id, "user_456");
475        assert_eq!(event.trigger_slug, "GITHUB_COMMIT_EVENT");
476        assert_eq!(event.metadata.connected_account.id, "ca_789");
477    }
478
479    #[test]
480    fn test_trigger_create_params() {
481        let params = TriggerCreateParams {
482            slug: "GITHUB_COMMIT_EVENT".to_string(),
483            connected_account_id: Some("ca_123".to_string()),
484            user_id: None,
485            trigger_config: Some(HashMap::from([
486                ("repo".to_string(), serde_json::json!("composio")),
487                ("owner".to_string(), serde_json::json!("composio")),
488            ])),
489            toolkit_versions: None,
490        };
491
492        assert_eq!(params.slug, "GITHUB_COMMIT_EVENT");
493        assert!(params.connected_account_id.is_some());
494        assert!(params.trigger_config.is_some());
495    }
496}