Skip to main content

composio_sdk/
config.rs

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