Skip to main content

composio_sdk/
config.rs

1//! Configuration for Composio SDK
2
3use crate::error::ComposioError;
4use crate::models::versioning::ToolkitVersionParam;
5use crate::retry::RetryPolicy;
6use std::path::PathBuf;
7use std::time::Duration;
8
9/// Configuration for Composio client
10#[derive(Debug, Clone)]
11pub struct ComposioConfig {
12    /// API key for authenticating with the Composio API
13    pub api_key: String,
14    /// Base URL for the Composio API (default: https://backend.composio.dev/api/v3)
15    pub base_url: String,
16    /// Timeout duration for HTTP requests (default: 30 seconds)
17    pub timeout: Duration,
18    /// Retry policy for handling transient failures
19    pub retry_policy: RetryPolicy,
20    /// Toolkit version configuration (default: None, uses "latest")
21    pub toolkit_versions: Option<ToolkitVersionParam>,
22    /// Directory for downloading files (optional)
23    pub file_download_dir: Option<PathBuf>,
24    /// Whether to automatically upload and download files (default: true)
25    pub auto_upload_download_files: bool,
26    /// Whether to enable telemetry tracking (opt-in, default: false for privacy)
27    pub telemetry_enabled: bool,
28}
29
30impl ComposioConfig {
31    /// Create a new configuration with the given API key
32    /// 
33    /// # Arguments
34    /// 
35    /// * `api_key` - The Composio API key
36    /// 
37    /// # Defaults
38    /// 
39    /// * `base_url`: <https://backend.composio.dev/api/v3>
40    /// * `timeout`: 30 seconds
41    /// * `retry_policy`: 3 retries, 1s initial delay, 10s max delay
42    /// 
43    /// # Example
44    /// 
45    /// ```
46    /// use composio_sdk::config::ComposioConfig;
47    /// 
48    /// let config = ComposioConfig::new("my_api_key");
49    /// assert_eq!(config.api_key, "my_api_key");
50    /// assert_eq!(config.base_url, "https://backend.composio.dev/api/v3");
51    /// ```
52    pub fn new(api_key: impl Into<String>) -> Self {
53        Self {
54            api_key: api_key.into(),
55            base_url: "https://backend.composio.dev/api/v3".to_string(),
56            timeout: Duration::from_secs(30),
57            retry_policy: RetryPolicy::default(),
58            toolkit_versions: None,
59            file_download_dir: None,
60            auto_upload_download_files: true,
61            telemetry_enabled: false, // Opt-in for privacy
62        }
63    }
64
65    /// Validate the configuration
66    /// 
67    /// # Errors
68    /// 
69    /// Returns an error if:
70    /// * API key is empty
71    /// * Base URL doesn't start with http:// or https://
72    /// 
73    /// # Example
74    /// 
75    /// ```
76    /// use composio_sdk::config::ComposioConfig;
77    /// 
78    /// let config = ComposioConfig::new("my_api_key");
79    /// assert!(config.validate().is_ok());
80    /// 
81    /// let invalid_config = ComposioConfig::new("");
82    /// assert!(invalid_config.validate().is_err());
83    /// ```
84    pub fn validate(&self) -> Result<(), ComposioError> {
85        if self.api_key.is_empty() {
86            return Err(ComposioError::InvalidInput(
87                "API key cannot be empty".to_string(),
88            ));
89        }
90
91        if !self.base_url.starts_with("http") {
92            return Err(ComposioError::ConfigError(
93                "Base URL must start with http:// or https://".to_string(),
94            ));
95        }
96
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_new_config_with_defaults() {
107        let config = ComposioConfig::new("test_api_key");
108        
109        assert_eq!(config.api_key, "test_api_key");
110        assert_eq!(config.base_url, "https://backend.composio.dev/api/v3");
111        assert_eq!(config.timeout, Duration::from_secs(30));
112        assert_eq!(config.retry_policy.max_retries, 3);
113        assert_eq!(config.retry_policy.initial_delay, Duration::from_secs(1));
114        assert_eq!(config.retry_policy.max_delay, Duration::from_secs(10));
115    }
116
117    #[test]
118    fn test_new_config_accepts_string() {
119        let config = ComposioConfig::new("test_key".to_string());
120        assert_eq!(config.api_key, "test_key");
121    }
122
123    #[test]
124    fn test_new_config_accepts_str() {
125        let config = ComposioConfig::new("test_key");
126        assert_eq!(config.api_key, "test_key");
127    }
128
129    #[test]
130    fn test_validate_valid_config() {
131        let config = ComposioConfig::new("valid_api_key");
132        assert!(config.validate().is_ok());
133    }
134
135    #[test]
136    fn test_validate_empty_api_key() {
137        let config = ComposioConfig::new("");
138        let result = config.validate();
139        
140        assert!(result.is_err());
141        match result {
142            Err(ComposioError::InvalidInput(msg)) => {
143                assert_eq!(msg, "API key cannot be empty");
144            }
145            _ => panic!("Expected InvalidInput error"),
146        }
147    }
148
149    #[test]
150    fn test_validate_invalid_base_url() {
151        let mut config = ComposioConfig::new("test_key");
152        config.base_url = "invalid-url".to_string();
153        
154        let result = config.validate();
155        assert!(result.is_err());
156        match result {
157            Err(ComposioError::ConfigError(msg)) => {
158                assert_eq!(msg, "Base URL must start with http:// or https://");
159            }
160            _ => panic!("Expected ConfigError"),
161        }
162    }
163
164    #[test]
165    fn test_validate_http_base_url() {
166        let mut config = ComposioConfig::new("test_key");
167        config.base_url = "http://localhost:8080".to_string();
168        
169        assert!(config.validate().is_ok());
170    }
171
172    #[test]
173    fn test_validate_https_base_url() {
174        let mut config = ComposioConfig::new("test_key");
175        config.base_url = "https://api.example.com".to_string();
176        
177        assert!(config.validate().is_ok());
178    }
179
180    #[test]
181    fn test_config_is_cloneable() {
182        let config = ComposioConfig::new("test_key");
183        let cloned = config.clone();
184        
185        assert_eq!(config.api_key, cloned.api_key);
186        assert_eq!(config.base_url, cloned.base_url);
187        assert_eq!(config.timeout, cloned.timeout);
188    }
189
190    #[test]
191    fn test_config_is_debuggable() {
192        let config = ComposioConfig::new("test_key");
193        let debug_str = format!("{:?}", config);
194        
195        assert!(debug_str.contains("ComposioConfig"));
196        assert!(debug_str.contains("test_key"));
197    }
198
199    #[test]
200    fn test_default_base_url() {
201        let config = ComposioConfig::new("key");
202        assert_eq!(config.base_url, "https://backend.composio.dev/api/v3");
203    }
204
205    #[test]
206    fn test_default_timeout() {
207        let config = ComposioConfig::new("key");
208        assert_eq!(config.timeout, Duration::from_secs(30));
209    }
210
211    #[test]
212    fn test_default_retry_policy() {
213        let config = ComposioConfig::new("key");
214        assert_eq!(config.retry_policy.max_retries, 3);
215        assert_eq!(config.retry_policy.initial_delay, Duration::from_secs(1));
216        assert_eq!(config.retry_policy.max_delay, Duration::from_secs(10));
217    }
218
219    #[test]
220    fn test_custom_base_url() {
221        let mut config = ComposioConfig::new("key");
222        config.base_url = "https://custom.api.com".to_string();
223        
224        assert!(config.validate().is_ok());
225        assert_eq!(config.base_url, "https://custom.api.com");
226    }
227
228    #[test]
229    fn test_custom_timeout() {
230        let mut config = ComposioConfig::new("key");
231        config.timeout = Duration::from_secs(60);
232        
233        assert_eq!(config.timeout, Duration::from_secs(60));
234    }
235
236    #[test]
237    fn test_custom_retry_policy() {
238        let mut config = ComposioConfig::new("key");
239        config.retry_policy = RetryPolicy {
240            max_retries: 5,
241            initial_delay: Duration::from_secs(2),
242            max_delay: Duration::from_secs(20),
243        };
244        
245        assert_eq!(config.retry_policy.max_retries, 5);
246        assert_eq!(config.retry_policy.initial_delay, Duration::from_secs(2));
247        assert_eq!(config.retry_policy.max_delay, Duration::from_secs(20));
248    }
249}