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}