zero_trust_sdk/
client.rs

1//! Main client for the Zero Trust SDK
2
3use crate::{
4    auth::AuthManager,
5    config::Config,
6    database::DatabaseManager,
7    error::{Result, ZeroTrustError},
8    types::{HealthStatus, SystemStats},
9};
10
11#[cfg(feature = "migration")]
12use crate::migration::MigrationManager;
13
14#[cfg(feature = "sync")]
15use crate::sync::SyncManager;
16
17use reqwest::{Client, Response};
18use std::sync::Arc;
19
20/// Main client for interacting with Zero Trust Database API
21///
22/// The `ZeroTrustClient` provides a high-level interface for all Zero Trust operations
23/// including authentication, database management, migrations, and synchronization.
24///
25/// # Examples
26///
27/// ```rust,no_run
28/// use zero_trust_sdk::{ZeroTrustClient, Config};
29///
30/// #[tokio::main]
31/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
32///     let config = Config::new("https://api.zerotrust.com")?;
33///     let client = ZeroTrustClient::new(config).await?;
34///     
35///     // Authenticate
36///     client.auth().login("user@example.com", "password").await?;
37///     
38///     // Check system status
39///     let health = client.health().await?;
40///     println!("System status: {}", health.status);
41///     
42///     Ok(())
43/// }
44/// ```
45#[derive(Debug, Clone)]
46pub struct ZeroTrustClient {
47    inner: Arc<ClientInner>,
48}
49
50#[derive(Debug)]
51struct ClientInner {
52    config: Config,
53    http_client: Client,
54    auth: AuthManager,
55    database: DatabaseManager,
56    #[cfg(feature = "migration")]
57    migration: MigrationManager,
58    #[cfg(feature = "sync")]
59    sync: SyncManager,
60}
61
62impl ZeroTrustClient {
63    /// Create a new Zero Trust client
64    ///
65    /// # Arguments
66    ///
67    /// * `config` - Client configuration
68    ///
69    /// # Examples
70    ///
71    /// ```rust,no_run
72    /// use zero_trust_sdk::{ZeroTrustClient, Config};
73    ///
74    /// #[tokio::main]
75    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
76    ///     let config = Config::new("https://api.zerotrust.com")?;
77    ///     let client = ZeroTrustClient::new(config).await?;
78    ///     Ok(())
79    /// }
80    /// ```
81    pub async fn new(config: Config) -> Result<Self> {
82        // Validate configuration
83        config.validate()?;
84        
85        // Build HTTP client
86        let client_builder = Client::builder()
87            .user_agent(&config.user_agent)
88            .timeout(config.timeout)
89            .danger_accept_invalid_certs(!config.verify_ssl);
90        
91        let http_client = client_builder.build()?;
92        
93        // Test connectivity
94        let health_url = format!("{}/health", config.api_url);
95        let response = http_client.get(&health_url).send().await;
96        
97        match response {
98            Ok(resp) if resp.status().is_success() => {
99                // Connection successful
100            }
101            Ok(resp) => {
102                return Err(ZeroTrustError::server_error(
103                    resp.status().as_u16(),
104                    format!("Server returned status: {}", resp.status()),
105                ));
106            }
107            Err(e) => {
108                if e.is_connect() {
109                    return Err(ZeroTrustError::generic(format!(
110                        "Could not connect to Zero Trust API at {}. Please check the URL and network connectivity.",
111                        config.api_url
112                    )));
113                } else if e.is_timeout() {
114                    return Err(ZeroTrustError::Timeout);
115                } else {
116                    return Err(ZeroTrustError::from(e));
117                }
118            }
119        }
120        
121        let config_arc = Arc::new(config);
122        let http_arc = Arc::new(http_client);
123        
124        // Initialize managers
125        let auth = AuthManager::new(config_arc.clone(), http_arc.clone());
126        let database = DatabaseManager::new(config_arc.clone(), http_arc.clone());
127        
128        #[cfg(feature = "migration")]
129        let migration = MigrationManager::new(config_arc.clone(), http_arc.clone());
130        
131        #[cfg(feature = "sync")]
132        let sync = SyncManager::new(config_arc.clone(), http_arc.clone());
133        
134        let inner = ClientInner {
135            config: (*config_arc).clone(),
136            http_client: (*http_arc).clone(),
137            auth,
138            database,
139            #[cfg(feature = "migration")]
140            migration,
141            #[cfg(feature = "sync")]
142            sync,
143        };
144        
145        Ok(Self {
146            inner: Arc::new(inner),
147        })
148    }
149    
150    /// Create a client with default configuration
151    ///
152    /// This will attempt to load configuration from environment variables
153    /// or the default config file location.
154    ///
155    /// # Examples
156    ///
157    /// ```rust,no_run
158    /// use zero_trust_sdk::ZeroTrustClient;
159    ///
160    /// #[tokio::main]
161    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
162    ///     let client = ZeroTrustClient::with_default_config().await?;
163    ///     Ok(())
164    /// }
165    /// ```
166    pub async fn with_default_config() -> Result<Self> {
167        let config = Config::load_default()?;
168        Self::new(config).await
169    }
170    
171    /// Get the authentication manager
172    ///
173    /// # Examples
174    ///
175    /// ```rust,no_run
176    /// use zero_trust_sdk::{ZeroTrustClient, Config};
177    ///
178    /// #[tokio::main]
179    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
180    ///     let config = Config::new("https://api.zerotrust.com")?;
181    ///     let client = ZeroTrustClient::new(config).await?;
182    ///     
183    ///     // Login
184    ///     client.auth().login("user@example.com", "password").await?;
185    ///     
186    ///     Ok(())
187    /// }
188    /// ```
189    pub fn auth(&self) -> &AuthManager {
190        &self.inner.auth
191    }
192    
193    /// Get the database manager
194    ///
195    /// # Examples
196    ///
197    /// ```rust,no_run
198    /// use zero_trust_sdk::{ZeroTrustClient, Config};
199    ///
200    /// #[tokio::main]
201    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
202    ///     let config = Config::new("https://api.zerotrust.com")?;
203    ///     let client = ZeroTrustClient::new(config).await?;
204    ///     
205    ///     // List databases
206    ///     let databases = client.databases().list().await?;
207    ///     
208    ///     Ok(())
209    /// }
210    /// ```
211    pub fn databases(&self) -> &DatabaseManager {
212        &self.inner.database
213    }
214    
215    /// Get the migration manager (requires 'migration' feature)
216    ///
217    /// # Examples
218    ///
219    /// ```rust,no_run
220    /// # #[cfg(feature = "migration")]
221    /// use zero_trust_sdk::{ZeroTrustClient, Config};
222    ///
223    /// # #[cfg(feature = "migration")]
224    /// #[tokio::main]
225    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
226    ///     let config = Config::new("https://api.zerotrust.com")?;
227    ///     let client = ZeroTrustClient::new(config).await?;
228    ///     
229    ///     // Import data
230    ///     client.migration()
231    ///         .import_csv("data.csv")
232    ///         .to_database("mydb")
233    ///         .to_table("users")
234    ///         .execute()
235    ///         .await?;
236    ///     
237    ///     Ok(())
238    /// }
239    /// ```
240    #[cfg(feature = "migration")]
241    pub fn migration(&self) -> &MigrationManager {
242        &self.inner.migration
243    }
244    
245    /// Get the sync manager (requires 'sync' feature)
246    ///
247    /// # Examples
248    ///
249    /// ```rust,no_run
250    /// # #[cfg(feature = "sync")]
251    /// use zero_trust_sdk::{ZeroTrustClient, Config};
252    ///
253    /// # #[cfg(feature = "sync")]
254    /// #[tokio::main]
255    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
256    ///     let config = Config::new("https://api.zerotrust.com")?;
257    ///     let client = ZeroTrustClient::new(config).await?;
258    ///     
259    ///     // Set up sync
260    ///     let sync = client.sync()
261    ///         .from_database("postgresql://localhost:5432/source")
262    ///         .to_database("target-db")
263    ///         .with_interval(Duration::from_secs(300))
264    ///         .create("my-sync")
265    ///         .await?;
266    ///     
267    ///     Ok(())
268    /// }
269    /// ```
270    #[cfg(feature = "sync")]
271    pub fn sync(&self) -> &SyncManager {
272        &self.inner.sync
273    }
274    
275    /// Check system health
276    ///
277    /// # Examples
278    ///
279    /// ```rust,no_run
280    /// use zero_trust_sdk::{ZeroTrustClient, Config};
281    ///
282    /// #[tokio::main]
283    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
284    ///     let config = Config::new("https://api.zerotrust.com")?;
285    ///     let client = ZeroTrustClient::new(config).await?;
286    ///     
287    ///     let health = client.health().await?;
288    ///     println!("Status: {}, Version: {}", health.status, health.version);
289    ///     
290    ///     Ok(())
291    /// }
292    /// ```
293    pub async fn health(&self) -> Result<HealthStatus> {
294        let url = format!("{}/health", self.inner.config.api_url);
295        let response = self.inner.http_client.get(&url).send().await?;
296        
297        self.handle_response(response).await
298    }
299    
300    /// Get system statistics
301    ///
302    /// # Examples
303    ///
304    /// ```rust,no_run
305    /// use zero_trust_sdk::{ZeroTrustClient, Config};
306    ///
307    /// #[tokio::main]
308    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
309    ///     let config = Config::new("https://api.zerotrust.com")?;
310    ///     let client = ZeroTrustClient::new(config).await?;
311    ///     
312    ///     let stats = client.stats().await?;
313    ///     println!("Databases: {}, Tables: {}", stats.databases, stats.tables);
314    ///     
315    ///     Ok(())
316    /// }
317    /// ```
318    pub async fn stats(&self) -> Result<SystemStats> {
319        let url = format!("{}/api/v1/stats", self.inner.config.api_url);
320        let response = self.send_authenticated_request(
321            self.inner.http_client.get(&url)
322        ).await?;
323        
324        self.handle_response(response).await
325    }
326    
327    /// Get the current configuration
328    pub fn config(&self) -> &Config {
329        &self.inner.config
330    }
331    
332    /// Update the authentication token
333    ///
334    /// # Arguments
335    ///
336    /// * `token` - New JWT authentication token
337    ///
338    /// # Examples
339    ///
340    /// ```rust,no_run
341    /// use zero_trust_sdk::{ZeroTrustClient, Config};
342    ///
343    /// #[tokio::main]
344    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
345    ///     let config = Config::new("https://api.zerotrust.com")?;
346    ///     let mut client = ZeroTrustClient::new(config).await?;
347    ///     
348    ///     client.set_token("new-jwt-token");
349    ///     
350    ///     Ok(())
351    /// }
352    /// ```
353    pub fn set_token<S: Into<String>>(&mut self, token: S) {
354        // Create new inner with updated token
355        let mut new_config = self.inner.config.clone();
356        new_config.token = Some(token.into());
357        
358        // Note: In a real implementation, we'd need to update all managers
359        // This is a simplified version for demonstration
360    }
361    
362    /// Check if the client is authenticated
363    pub fn is_authenticated(&self) -> bool {
364        self.inner.config.is_authenticated()
365    }
366}
367
368// Internal helper methods
369impl ZeroTrustClient {
370    /// Send an authenticated HTTP request
371    pub(crate) async fn send_authenticated_request(
372        &self,
373        mut request: reqwest::RequestBuilder,
374    ) -> Result<Response> {
375        if let Some(token) = &self.inner.config.token {
376            request = request.header("Authorization", format!("Bearer {}", token));
377        }
378        
379        let response = request
380            .header("Content-Type", "application/json")
381            .send()
382            .await?;
383        
384        Ok(response)
385    }
386    
387    /// Handle HTTP response and convert errors
388    pub(crate) async fn handle_response<T>(&self, response: Response) -> Result<T>
389    where
390        T: serde::de::DeserializeOwned,
391    {
392        let status = response.status();
393        
394        if status.is_success() {
395            let data = response.json::<T>().await?;
396            Ok(data)
397        } else {
398            let error_text = response.text().await.unwrap_or_default();
399            
400            match status.as_u16() {
401                401 => Err(ZeroTrustError::auth("Authentication failed. Please login again.")),
402                403 => Err(ZeroTrustError::permission_denied("Insufficient permissions")),
403                404 => Err(ZeroTrustError::not_found("Resource not found")),
404                429 => {
405                    // Extract retry-after header if present
406                    let retry_after = 60; // Default to 60 seconds
407                    Err(ZeroTrustError::rate_limit(retry_after))
408                }
409                400..=499 => Err(ZeroTrustError::client_error(status.as_u16(), error_text)),
410                500..=599 => Err(ZeroTrustError::server_error(status.as_u16(), error_text)),
411                _ => Err(ZeroTrustError::generic(format!("HTTP {}: {}", status, error_text))),
412            }
413        }
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420    use mockito::{Matcher, Server};
421    
422    #[tokio::test]
423    async fn test_client_creation() {
424        let mut server = Server::new_async().await;
425        let url = server.url();
426        
427        // Mock health endpoint
428        let mock = server
429            .mock("GET", "/health")
430            .with_status(200)
431            .with_header("content-type", "application/json")
432            .with_body(r#"{"status": "healthy", "version": "0.1.0", "database": "connected", "blockchain": "connected"}"#)
433            .create_async()
434            .await;
435        
436        let config = Config::new(&url).unwrap();
437        let client = ZeroTrustClient::new(config).await.unwrap();
438        
439        assert!(!client.is_authenticated());
440        mock.assert_async().await;
441    }
442    
443    #[tokio::test]
444    async fn test_health_check() {
445        let mut server = Server::new_async().await;
446        let url = server.url();
447        
448        // Mock health endpoint for initial connection
449        let _init_mock = server
450            .mock("GET", "/health")
451            .with_status(200)
452            .with_header("content-type", "application/json")
453            .with_body(r#"{"status": "healthy", "version": "0.1.0", "database": "connected", "blockchain": "connected"}"#)
454            .create_async()
455            .await;
456        
457        // Mock health endpoint for actual health check
458        let health_mock = server
459            .mock("GET", "/health")
460            .with_status(200)
461            .with_header("content-type", "application/json")
462            .with_body(r#"{"status": "healthy", "version": "0.1.0", "database": "connected", "blockchain": "connected"}"#)
463            .create_async()
464            .await;
465        
466        let config = Config::new(&url).unwrap();
467        let client = ZeroTrustClient::new(config).await.unwrap();
468        
469        let health = client.health().await.unwrap();
470        assert_eq!(health.status, "healthy");
471        assert_eq!(health.version, "0.1.0");
472        
473        health_mock.assert_async().await;
474    }
475}