Skip to main content

composio_sdk/models/
internal.rs

1//! Internal SDK models and resources
2//!
3//! This module provides internal SDK functionality for realtime credentials
4//! and other internal operations.
5
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9use crate::client::ComposioClient;
10use crate::error::ComposioError;
11use crate::models::base::{BaseResource, Resource, TelemetryContext};
12
13/// Endpoint for SDK realtime credentials
14const INTERNAL_SDK_REALTIME_CREDENTIALS_ENDPOINT: &str = "/api/v3/internal/sdk/realtime/credentials";
15
16/// Response containing SDK realtime credentials for Pusher
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct SDKRealtimeCredentialsResponse {
19    /// Pusher API key
20    pub pusher_key: String,
21    /// Project ID
22    pub project_id: String,
23    /// Pusher cluster region
24    pub pusher_cluster: String,
25}
26
27/// Internal resource for SDK operations
28///
29/// This resource provides access to internal SDK functionality,
30/// including realtime credentials for WebSocket connections.
31#[derive(Clone)]
32pub struct Internal {
33    base: BaseResource,
34}
35
36impl Internal {
37    /// Create a new Internal resource
38    pub fn new(client: Arc<ComposioClient>) -> Self {
39        Self {
40            base: BaseResource::new(client),
41        }
42    }
43
44    /// Create a new Internal resource with custom telemetry context
45    pub fn with_telemetry_context(
46        client: Arc<ComposioClient>,
47        telemetry_context: TelemetryContext,
48    ) -> Self {
49        Self {
50            base: BaseResource::with_telemetry_context(client, telemetry_context),
51        }
52    }
53
54    /// Get SDK realtime credentials
55    ///
56    /// Retrieves credentials for establishing realtime WebSocket connections
57    /// using Pusher. These credentials are used for receiving real-time events
58    /// and updates from the Composio platform.
59    ///
60    /// # Returns
61    ///
62    /// Returns `SDKRealtimeCredentialsResponse` containing:
63    /// - `pusher_key`: The Pusher API key for authentication
64    /// - `project_id`: The project identifier
65    /// - `pusher_cluster`: The Pusher cluster region (e.g., "us2", "eu")
66    ///
67    /// # Errors
68    ///
69    /// Returns `ComposioError` if:
70    /// - The API request fails
71    /// - Authentication is invalid
72    /// - The response cannot be deserialized
73    ///
74    /// # Example
75    ///
76    /// ```no_run
77    /// use composio_sdk::client::ComposioClient;
78    /// use composio_sdk::config::ComposioConfig;
79    /// use composio_sdk::models::internal::Internal;
80    /// use std::sync::Arc;
81    ///
82    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
83    /// let config = ComposioConfig {
84    ///     api_key: "your-api-key".to_string(),
85    ///     ..Default::default()
86    /// };
87    /// let client = Arc::new(ComposioClient::from_config(config)?);
88    /// let internal = Internal::new(client);
89    ///
90    /// let credentials = internal.get_sdk_realtime_credentials().await?;
91    /// println!("Pusher Key: {}", credentials.pusher_key);
92    /// println!("Project ID: {}", credentials.project_id);
93    /// println!("Cluster: {}", credentials.pusher_cluster);
94    /// # Ok(())
95    /// # }
96    /// ```
97    pub async fn get_sdk_realtime_credentials(
98        &self,
99    ) -> Result<SDKRealtimeCredentialsResponse, ComposioError> {
100        let start_time = std::time::SystemTime::now();
101        let mut telemetry_data = self.create_method_event(
102            "Internal.get_sdk_realtime_credentials",
103            None,
104        );
105
106        let url = format!(
107            "{}{}",
108            self.client().config().base_url,
109            INTERNAL_SDK_REALTIME_CREDENTIALS_ENDPOINT
110        );
111
112        // Execute request with retry logic
113        let result = crate::retry::with_retry(&self.client().config().retry_policy, || async {
114            let response = self
115                .client()
116                .http_client()
117                .get(&url)
118                .header("x-api-key", &self.client().config().api_key)
119                .send()
120                .await
121                .map_err(ComposioError::NetworkError)?;
122
123            // Check for errors
124            if !response.status().is_success() {
125                return Err(ComposioError::from_response(response).await);
126            }
127
128            // Parse response
129            let credentials: SDKRealtimeCredentialsResponse = response
130                .json()
131                .await
132                .map_err(ComposioError::NetworkError)?;
133
134            Ok(credentials)
135        })
136        .await;
137
138        // Update telemetry with duration and error info
139        if let Some(ref mut data) = telemetry_data {
140            let duration_ms = std::time::SystemTime::now()
141                .duration_since(start_time)
142                .unwrap()
143                .as_millis() as f64;
144
145            data.duration_ms = Some(duration_ms);
146
147            let event_type = if let Err(ref e) = result {
148                data.error = Some(crate::models::telemetry::ErrorData {
149                    name: "ComposioError".to_string(),
150                    code: None,
151                    error_id: None,
152                    message: Some(e.to_string()),
153                    stack: None,
154                });
155                crate::models::telemetry::EventType::Error
156            } else {
157                crate::models::telemetry::EventType::Metric
158            };
159
160            self.push_telemetry_event((event_type, data.clone()));
161        }
162
163        result
164    }
165}
166
167impl Resource for Internal {
168    fn client(&self) -> &ComposioClient {
169        self.base.client()
170    }
171
172    fn telemetry_context(&self) -> &TelemetryContext {
173        self.base.telemetry_context()
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use crate::client::ComposioClient;
181    use wiremock::{
182        matchers::{method, path},
183        Mock, MockServer, ResponseTemplate,
184    };
185
186    #[tokio::test]
187    async fn test_get_sdk_realtime_credentials_success() {
188        let mock_server = MockServer::start().await;
189
190        let response_body = serde_json::json!({
191            "pusher_key": "test_pusher_key_123",
192            "project_id": "proj_test_456",
193            "pusher_cluster": "us2"
194        });
195
196        Mock::given(method("GET"))
197            .and(path("/api/v3/internal/sdk/realtime/credentials"))
198            .respond_with(ResponseTemplate::new(200).set_body_json(&response_body))
199            .mount(&mock_server)
200            .await;
201
202        let client = Arc::new(
203            ComposioClient::builder()
204                .api_key("test_key")
205                .base_url(mock_server.uri())
206                .build()
207                .unwrap(),
208        );
209        let internal = Internal::new(client);
210
211        let result = internal.get_sdk_realtime_credentials().await;
212        assert!(result.is_ok());
213
214        let credentials = result.unwrap();
215        assert_eq!(credentials.pusher_key, "test_pusher_key_123");
216        assert_eq!(credentials.project_id, "proj_test_456");
217        assert_eq!(credentials.pusher_cluster, "us2");
218    }
219
220    #[tokio::test]
221    async fn test_get_sdk_realtime_credentials_unauthorized() {
222        let mock_server = MockServer::start().await;
223
224        Mock::given(method("GET"))
225            .and(path("/api/v3/internal/sdk/realtime/credentials"))
226            .respond_with(
227                ResponseTemplate::new(401).set_body_json(serde_json::json!({
228                    "message": "Invalid API key",
229                    "code": "UNAUTHORIZED",
230                    "status": 401
231                })),
232            )
233            .mount(&mock_server)
234            .await;
235
236        let client = Arc::new(
237            ComposioClient::builder()
238                .api_key("invalid_key")
239                .base_url(mock_server.uri())
240                .build()
241                .unwrap(),
242        );
243        let internal = Internal::new(client);
244
245        let result = internal.get_sdk_realtime_credentials().await;
246        assert!(result.is_err());
247
248        if let Err(ComposioError::ApiError { status, .. }) = result {
249            assert_eq!(status, 401);
250        } else {
251            panic!("Expected ApiError with status 401");
252        }
253    }
254
255    #[tokio::test]
256    async fn test_sdk_realtime_credentials_response_deserialization() {
257        let json = r#"{
258            "pusher_key": "abc123",
259            "project_id": "proj_xyz",
260            "pusher_cluster": "eu"
261        }"#;
262
263        let response: SDKRealtimeCredentialsResponse = serde_json::from_str(json).unwrap();
264        assert_eq!(response.pusher_key, "abc123");
265        assert_eq!(response.project_id, "proj_xyz");
266        assert_eq!(response.pusher_cluster, "eu");
267    }
268}