Skip to main content

composio_sdk/models/
response.rs

1//! Response models from Composio API
2
3use serde::Deserialize;
4
5use super::enums::AuthScheme;
6use super::request::SessionConfig;
7
8/// Response from session creation
9#[derive(Debug, Clone, Deserialize)]
10pub struct SessionResponse {
11    pub session_id: String,
12    pub mcp: McpInfo,
13    pub tool_router_tools: Vec<String>,
14    pub config: SessionConfig,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub assistive_prompt: Option<String>,
17}
18
19/// MCP server information
20#[derive(Debug, Clone, Deserialize)]
21pub struct McpInfo {
22    pub url: String,
23}
24
25/// Tool schema information
26#[derive(Debug, Clone, Deserialize)]
27pub struct ToolSchema {
28    pub slug: String,
29    pub name: String,
30    pub description: String,
31    pub toolkit: String,
32    pub input_parameters: serde_json::Value,
33    pub output_parameters: serde_json::Value,
34    #[serde(default)]
35    pub scopes: Vec<String>,
36    #[serde(default)]
37    pub tags: Vec<String>,
38    pub version: String,
39    #[serde(default)]
40    pub available_versions: Vec<String>,
41    #[serde(default)]
42    pub is_deprecated: bool,
43    #[serde(default)]
44    pub no_auth: bool,
45}
46
47/// Response from tool execution
48#[derive(Debug, Clone, Deserialize)]
49pub struct ToolExecutionResponse {
50    pub data: serde_json::Value,
51    pub error: Option<String>,
52    pub log_id: String,
53}
54
55/// Response from meta tool execution
56pub type MetaToolExecutionResponse = ToolExecutionResponse;
57
58/// Response from listing toolkits
59#[derive(Debug, Clone, Deserialize)]
60pub struct ToolkitListResponse {
61    pub items: Vec<ToolkitInfo>,
62    pub next_cursor: Option<String>,
63    pub total_pages: u32,
64    pub current_page: u32,
65    pub total_items: u32,
66}
67
68/// Information about a toolkit
69#[derive(Debug, Clone, Deserialize)]
70pub struct ToolkitInfo {
71    pub name: String,
72    pub slug: String,
73    pub enabled: bool,
74    pub is_no_auth: bool,
75    pub composio_managed_auth_schemes: Vec<AuthScheme>,
76    pub meta: ToolkitMeta,
77    pub connected_account: Option<ConnectedAccountInfo>,
78}
79
80/// Metadata about a toolkit
81#[derive(Debug, Clone, Deserialize)]
82pub struct ToolkitMeta {
83    pub logo: String,
84    pub description: String,
85    #[serde(default)]
86    pub categories: Vec<String>,
87    #[serde(default)]
88    pub tools_count: u32,
89    #[serde(default)]
90    pub triggers_count: u32,
91    #[serde(default)]
92    pub version: String,
93}
94
95/// Information about a connected account
96#[derive(Debug, Clone, Deserialize)]
97pub struct ConnectedAccountInfo {
98    pub id: String,
99    pub status: String,
100    pub created_at: String,
101}
102
103/// Response from creating an auth link
104#[derive(Debug, Clone, Deserialize)]
105pub struct LinkResponse {
106    pub link_token: String,
107    pub redirect_url: String,
108    pub connected_account_id: Option<String>,
109}
110
111/// Error response from API
112#[derive(Debug, Clone, Deserialize)]
113pub struct ErrorResponse {
114    pub message: String,
115    pub code: Option<String>,
116    pub slug: Option<String>,
117    pub status: u16,
118    pub request_id: Option<String>,
119    pub suggested_fix: Option<String>,
120    pub errors: Option<Vec<crate::error::ErrorDetail>>,
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use serde_json;
127
128    #[test]
129    fn test_session_response_deserialization() {
130        let json = r#"{
131            "session_id": "sess_abc123",
132            "mcp": {
133                "url": "https://mcp.composio.dev/sess_abc123"
134            },
135            "tool_router_tools": [
136                "COMPOSIO_SEARCH_TOOLS",
137                "COMPOSIO_MULTI_EXECUTE_TOOL"
138            ],
139            "config": {
140                "user_id": "user_123"
141            }
142        }"#;
143
144        let response: SessionResponse = serde_json::from_str(json).unwrap();
145        assert_eq!(response.session_id, "sess_abc123");
146        assert_eq!(response.mcp.url, "https://mcp.composio.dev/sess_abc123");
147        assert_eq!(response.tool_router_tools.len(), 2);
148        assert_eq!(response.config.user_id, "user_123");
149        assert!(response.assistive_prompt.is_none());
150    }
151
152    #[test]
153    fn test_session_response_with_assistive_prompt() {
154        let json = r#"{
155            "session_id": "sess_abc123",
156            "mcp": {
157                "url": "https://mcp.composio.dev/sess_abc123"
158            },
159            "tool_router_tools": [],
160            "config": {
161                "user_id": "user_123"
162            },
163            "assistive_prompt": "Use COMPOSIO_SEARCH_TOOLS to discover tools"
164        }"#;
165
166        let response: SessionResponse = serde_json::from_str(json).unwrap();
167        assert_eq!(
168            response.assistive_prompt,
169            Some("Use COMPOSIO_SEARCH_TOOLS to discover tools".to_string())
170        );
171    }
172
173    #[test]
174    fn test_mcp_info_deserialization() {
175        let json = r#"{
176            "url": "https://mcp.composio.dev/session_123"
177        }"#;
178
179        let mcp: McpInfo = serde_json::from_str(json).unwrap();
180        assert_eq!(mcp.url, "https://mcp.composio.dev/session_123");
181    }
182
183    #[test]
184    fn test_tool_schema_deserialization() {
185        let json = r#"{
186            "slug": "GITHUB_CREATE_ISSUE",
187            "name": "Create Issue",
188            "description": "Create a new issue in a GitHub repository",
189            "toolkit": "github",
190            "input_parameters": {
191                "type": "object",
192                "properties": {
193                    "owner": {"type": "string"},
194                    "repo": {"type": "string"},
195                    "title": {"type": "string"}
196                }
197            },
198            "output_parameters": {
199                "type": "object",
200                "properties": {
201                    "id": {"type": "number"}
202                }
203            },
204            "scopes": ["repo"],
205            "tags": ["write"],
206            "version": "1.0.0",
207            "available_versions": ["1.0.0", "0.9.0"],
208            "is_deprecated": false,
209            "no_auth": false
210        }"#;
211
212        let schema: ToolSchema = serde_json::from_str(json).unwrap();
213        assert_eq!(schema.slug, "GITHUB_CREATE_ISSUE");
214        assert_eq!(schema.name, "Create Issue");
215        assert_eq!(schema.toolkit, "github");
216        assert_eq!(schema.scopes.len(), 1);
217        assert_eq!(schema.tags.len(), 1);
218        assert_eq!(schema.version, "1.0.0");
219        assert_eq!(schema.available_versions.len(), 2);
220        assert!(!schema.is_deprecated);
221        assert!(!schema.no_auth);
222    }
223
224    #[test]
225    fn test_tool_schema_minimal_deserialization() {
226        let json = r#"{
227            "slug": "SIMPLE_TOOL",
228            "name": "Simple Tool",
229            "description": "A simple tool",
230            "toolkit": "simple",
231            "input_parameters": {},
232            "output_parameters": {},
233            "version": "1.0.0"
234        }"#;
235
236        let schema: ToolSchema = serde_json::from_str(json).unwrap();
237        assert_eq!(schema.slug, "SIMPLE_TOOL");
238        assert!(schema.scopes.is_empty());
239        assert!(schema.tags.is_empty());
240        assert!(schema.available_versions.is_empty());
241        assert!(!schema.is_deprecated);
242        assert!(!schema.no_auth);
243    }
244
245    #[test]
246    fn test_tool_execution_response_deserialization() {
247        let json = r#"{
248            "data": {
249                "issue_id": 123,
250                "url": "https://github.com/owner/repo/issues/123"
251            },
252            "error": null,
253            "log_id": "log_xyz789"
254        }"#;
255
256        let response: ToolExecutionResponse = serde_json::from_str(json).unwrap();
257        assert!(response.data.is_object());
258        assert_eq!(response.data["issue_id"], 123);
259        assert!(response.error.is_none());
260        assert_eq!(response.log_id, "log_xyz789");
261    }
262
263    #[test]
264    fn test_tool_execution_response_with_error() {
265        let json = r#"{
266            "data": null,
267            "error": "Failed to create issue: Invalid repository",
268            "log_id": "log_error123"
269        }"#;
270
271        let response: ToolExecutionResponse = serde_json::from_str(json).unwrap();
272        assert!(response.data.is_null());
273        assert_eq!(
274            response.error,
275            Some("Failed to create issue: Invalid repository".to_string())
276        );
277        assert_eq!(response.log_id, "log_error123");
278    }
279
280    #[test]
281    fn test_toolkit_list_response_deserialization() {
282        let json = r#"{
283            "items": [
284                {
285                    "name": "GitHub",
286                    "slug": "github",
287                    "enabled": true,
288                    "is_no_auth": false,
289                    "composio_managed_auth_schemes": ["OAUTH2"],
290                    "meta": {
291                        "logo": "https://logo.url",
292                        "description": "GitHub integration",
293                        "categories": ["development"],
294                        "tools_count": 50,
295                        "triggers_count": 10,
296                        "version": "1.0.0"
297                    },
298                    "connected_account": {
299                        "id": "ca_123",
300                        "status": "ACTIVE",
301                        "created_at": "2024-01-01T00:00:00Z"
302                    }
303                }
304            ],
305            "next_cursor": "cursor_abc",
306            "total_pages": 5,
307            "current_page": 1,
308            "total_items": 100
309        }"#;
310
311        let response: ToolkitListResponse = serde_json::from_str(json).unwrap();
312        assert_eq!(response.items.len(), 1);
313        assert_eq!(response.next_cursor, Some("cursor_abc".to_string()));
314        assert_eq!(response.total_pages, 5);
315        assert_eq!(response.current_page, 1);
316        assert_eq!(response.total_items, 100);
317    }
318
319    #[test]
320    fn test_toolkit_info_deserialization() {
321        let json = r#"{
322            "name": "Gmail",
323            "slug": "gmail",
324            "enabled": true,
325            "is_no_auth": false,
326            "composio_managed_auth_schemes": ["OAUTH2"],
327            "meta": {
328                "logo": "https://gmail.logo",
329                "description": "Gmail integration",
330                "categories": ["communication"],
331                "tools_count": 30,
332                "triggers_count": 5,
333                "version": "2.0.0"
334            },
335            "connected_account": null
336        }"#;
337
338        let info: ToolkitInfo = serde_json::from_str(json).unwrap();
339        assert_eq!(info.name, "Gmail");
340        assert_eq!(info.slug, "gmail");
341        assert!(info.enabled);
342        assert!(!info.is_no_auth);
343        assert_eq!(info.composio_managed_auth_schemes.len(), 1);
344        assert!(info.connected_account.is_none());
345    }
346
347    #[test]
348    fn test_toolkit_meta_deserialization() {
349        let json = r#"{
350            "logo": "https://logo.url",
351            "description": "Test toolkit",
352            "categories": ["test", "development"],
353            "tools_count": 25,
354            "triggers_count": 3,
355            "version": "1.5.0"
356        }"#;
357
358        let meta: ToolkitMeta = serde_json::from_str(json).unwrap();
359        assert_eq!(meta.logo, "https://logo.url");
360        assert_eq!(meta.description, "Test toolkit");
361        assert_eq!(meta.categories.len(), 2);
362        assert_eq!(meta.tools_count, 25);
363        assert_eq!(meta.triggers_count, 3);
364        assert_eq!(meta.version, "1.5.0");
365    }
366
367    #[test]
368    fn test_toolkit_meta_minimal_deserialization() {
369        let json = r#"{
370            "logo": "https://logo.url",
371            "description": "Minimal toolkit"
372        }"#;
373
374        let meta: ToolkitMeta = serde_json::from_str(json).unwrap();
375        assert_eq!(meta.logo, "https://logo.url");
376        assert_eq!(meta.description, "Minimal toolkit");
377        assert!(meta.categories.is_empty());
378        assert_eq!(meta.tools_count, 0);
379        assert_eq!(meta.triggers_count, 0);
380        assert_eq!(meta.version, "");
381    }
382
383    #[test]
384    fn test_connected_account_info_deserialization() {
385        let json = r#"{
386            "id": "ca_abc123",
387            "status": "ACTIVE",
388            "created_at": "2024-01-15T10:30:00Z"
389        }"#;
390
391        let info: ConnectedAccountInfo = serde_json::from_str(json).unwrap();
392        assert_eq!(info.id, "ca_abc123");
393        assert_eq!(info.status, "ACTIVE");
394        assert_eq!(info.created_at, "2024-01-15T10:30:00Z");
395    }
396
397    #[test]
398    fn test_link_response_deserialization() {
399        let json = r#"{
400            "link_token": "lt_xyz789",
401            "redirect_url": "https://auth.composio.dev/link?token=lt_xyz789",
402            "connected_account_id": "ca_existing123"
403        }"#;
404
405        let response: LinkResponse = serde_json::from_str(json).unwrap();
406        assert_eq!(response.link_token, "lt_xyz789");
407        assert_eq!(response.redirect_url, "https://auth.composio.dev/link?token=lt_xyz789");
408        assert_eq!(response.connected_account_id, Some("ca_existing123".to_string()));
409    }
410
411    #[test]
412    fn test_link_response_without_connected_account() {
413        let json = r#"{
414            "link_token": "lt_new456",
415            "redirect_url": "https://auth.composio.dev/link?token=lt_new456",
416            "connected_account_id": null
417        }"#;
418
419        let response: LinkResponse = serde_json::from_str(json).unwrap();
420        assert_eq!(response.link_token, "lt_new456");
421        assert!(response.connected_account_id.is_none());
422    }
423
424    #[test]
425    fn test_error_response_deserialization() {
426        let json = r#"{
427            "message": "Validation failed",
428            "code": "VALIDATION_ERROR",
429            "slug": "validation-failed",
430            "status": 400,
431            "request_id": "req_abc123",
432            "suggested_fix": "Check your input parameters",
433            "errors": [
434                {
435                    "field": "user_id",
436                    "message": "User ID is required"
437                }
438            ]
439        }"#;
440
441        let response: ErrorResponse = serde_json::from_str(json).unwrap();
442        assert_eq!(response.message, "Validation failed");
443        assert_eq!(response.code, Some("VALIDATION_ERROR".to_string()));
444        assert_eq!(response.slug, Some("validation-failed".to_string()));
445        assert_eq!(response.status, 400);
446        assert_eq!(response.request_id, Some("req_abc123".to_string()));
447        assert_eq!(response.suggested_fix, Some("Check your input parameters".to_string()));
448        assert!(response.errors.is_some());
449        assert_eq!(response.errors.as_ref().unwrap().len(), 1);
450    }
451
452    #[test]
453    fn test_error_response_minimal_deserialization() {
454        let json = r#"{
455            "message": "Internal server error",
456            "status": 500
457        }"#;
458
459        let response: ErrorResponse = serde_json::from_str(json).unwrap();
460        assert_eq!(response.message, "Internal server error");
461        assert_eq!(response.status, 500);
462        assert!(response.code.is_none());
463        assert!(response.slug.is_none());
464        assert!(response.request_id.is_none());
465        assert!(response.suggested_fix.is_none());
466        assert!(response.errors.is_none());
467    }
468
469    #[test]
470    fn test_auth_scheme_deserialization() {
471        let json = r#"["OAUTH2", "API_KEY", "BEARER_TOKEN"]"#;
472        let schemes: Vec<AuthScheme> = serde_json::from_str(json).unwrap();
473        
474        assert_eq!(schemes.len(), 3);
475        assert!(matches!(schemes[0], AuthScheme::Oauth2));
476        assert!(matches!(schemes[1], AuthScheme::ApiKey));
477        assert!(matches!(schemes[2], AuthScheme::BearerToken));
478    }
479
480    #[test]
481    fn test_meta_tool_execution_response_alias() {
482        let json = r#"{
483            "data": {"result": "success"},
484            "error": null,
485            "log_id": "log_meta123"
486        }"#;
487
488        let response: MetaToolExecutionResponse = serde_json::from_str(json).unwrap();
489        assert!(response.data.is_object());
490        assert!(response.error.is_none());
491        assert_eq!(response.log_id, "log_meta123");
492    }
493
494    #[test]
495    fn test_toolkit_list_response_empty_items() {
496        let json = r#"{
497            "items": [],
498            "next_cursor": null,
499            "total_pages": 0,
500            "current_page": 0,
501            "total_items": 0
502        }"#;
503
504        let response: ToolkitListResponse = serde_json::from_str(json).unwrap();
505        assert!(response.items.is_empty());
506        assert!(response.next_cursor.is_none());
507        assert_eq!(response.total_pages, 0);
508        assert_eq!(response.current_page, 0);
509        assert_eq!(response.total_items, 0);
510    }
511
512    #[test]
513    fn test_session_response_clone() {
514        let json = r#"{
515            "session_id": "sess_123",
516            "mcp": {"url": "https://mcp.url"},
517            "tool_router_tools": [],
518            "config": {"user_id": "user_123"}
519        }"#;
520
521        let response: SessionResponse = serde_json::from_str(json).unwrap();
522        let cloned = response.clone();
523        
524        assert_eq!(response.session_id, cloned.session_id);
525        assert_eq!(response.mcp.url, cloned.mcp.url);
526    }
527
528    #[test]
529    fn test_tool_schema_debug() {
530        let json = r#"{
531            "slug": "TEST_TOOL",
532            "name": "Test",
533            "description": "Test tool",
534            "toolkit": "test",
535            "input_parameters": {},
536            "output_parameters": {},
537            "version": "1.0.0"
538        }"#;
539
540        let schema: ToolSchema = serde_json::from_str(json).unwrap();
541        let debug_str = format!("{:?}", schema);
542        
543        assert!(debug_str.contains("TEST_TOOL"));
544        assert!(debug_str.contains("Test"));
545    }
546}