tavo_ai/
lib.rs

1//! # Tavo AI Rust SDK
2//!
3//! The official Tavo AI SDK for Rust applications, providing comprehensive
4//! security scanning and AI model analysis capabilities.
5//!
6//! ## Example
7//!
8//! ```rust,no_run
9//! use tavo_ai::TavoClient;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
13//!     let client = TavoClient::new("your-api-key")?;
14//!
15//!     // Scan code for vulnerabilities
16//!     let result = client.scans().start_scan("fn main() {}", "rust").await?;
17//!
18//!     println!("Found {} issues", result.total_issues);
19//!     Ok(())
20//! }
21//! ```
22
23pub mod ai_analysis;
24pub mod auth;
25pub mod billing;
26pub mod jobs;
27pub mod organizations;
28pub mod reports;
29pub mod scans;
30pub mod users;
31pub mod webhooks;
32
33use reqwest::Client;
34use serde::Deserialize;
35use std::collections::HashMap;
36use thiserror::Error;
37
38pub use ai_analysis::*;
39pub use auth::*;
40pub use billing::*;
41pub use jobs::*;
42pub use organizations::*;
43pub use reports::*;
44pub use scans::*;
45pub use users::*;
46pub use webhooks::*;
47
48/// Errors that can occur when using the Tavo AI SDK
49#[derive(Error, Debug)]
50pub enum TavoError {
51    #[error("HTTP request failed: {0}")]
52    Http(#[from] reqwest::Error),
53
54    #[error("JSON serialization failed: {0}")]
55    Json(#[from] serde_json::Error),
56
57    #[error("Invalid API key")]
58    InvalidApiKey,
59
60    #[error("API error: {message}")]
61    Api { message: String },
62}
63
64/// Result type for Tavo AI operations
65pub type Result<T> = std::result::Result<T, TavoError>;
66
67/// Main client for interacting with the Tavo AI API
68pub struct TavoClient {
69    client: Client,
70    api_key: Option<String>,
71    jwt_token: Option<String>,
72    session_token: Option<String>,
73    base_url: String,
74}
75
76impl TavoClient {
77    /// Create a new TavoClient with the given API key
78    ///
79    /// # Arguments
80    ///
81    /// * `api_key` - Your Tavo AI API key
82    ///
83    /// # Example
84    ///
85    /// ```rust,no_run
86    /// use tavo_ai::TavoClient;
87    ///
88    /// let client = TavoClient::new("your-api-key")?;
89    /// # Ok::<(), Box<dyn std::error::Error>>(())
90    /// ```
91    pub fn new(api_key: impl Into<String>) -> Result<Self> {
92        Self::with_api_key(api_key)
93    }
94
95    /// Create a new TavoClient with API key authentication
96    pub fn with_api_key(api_key: impl Into<String>) -> Result<Self> {
97        let api_key = api_key.into();
98        if api_key.is_empty() {
99            return Err(TavoError::InvalidApiKey);
100        }
101
102        Ok(Self {
103            client: Client::new(),
104            api_key: Some(api_key),
105            jwt_token: None,
106            session_token: None,
107            base_url: "https://api.tavoai.net".to_string(),
108        })
109    }
110
111    /// Create a new TavoClient with JWT token authentication
112    pub fn with_jwt_token(jwt_token: impl Into<String>) -> Result<Self> {
113        let jwt_token = jwt_token.into();
114        if jwt_token.is_empty() {
115            return Err(TavoError::InvalidApiKey);
116        }
117
118        Ok(Self {
119            client: Client::new(),
120            api_key: None,
121            jwt_token: Some(jwt_token),
122            session_token: None,
123            base_url: "https://api.tavoai.net".to_string(),
124        })
125    }
126
127    /// Create a new TavoClient with session token authentication
128    pub fn with_session_token(session_token: impl Into<String>) -> Result<Self> {
129        let session_token = session_token.into();
130        if session_token.is_empty() {
131            return Err(TavoError::InvalidApiKey);
132        }
133
134        Ok(Self {
135            client: Client::new(),
136            api_key: None,
137            jwt_token: None,
138            session_token: Some(session_token),
139            base_url: "https://api.tavoai.net".to_string(),
140        })
141    }
142
143    /// Create a new TavoClient with custom base URL
144    pub fn with_base_url(api_key: impl Into<String>, base_url: impl Into<String>) -> Result<Self> {
145        let api_key = api_key.into();
146        if api_key.is_empty() {
147            return Err(TavoError::InvalidApiKey);
148        }
149
150        Ok(Self {
151            client: Client::new(),
152            api_key: Some(api_key),
153            jwt_token: None,
154            session_token: None,
155            base_url: base_url.into(),
156        })
157    }
158
159    /// Create a request builder with appropriate authentication headers
160    fn authenticated_request(&self, method: reqwest::Method, url: &str) -> reqwest::RequestBuilder {
161        let mut request = self.client.request(method, url);
162
163        if let Some(jwt_token) = &self.jwt_token {
164            request = request.header("Authorization", format!("Bearer {}", jwt_token));
165        } else if let Some(session_token) = &self.session_token {
166            request = request.header("X-Session-Token", session_token);
167        } else if let Some(api_key) = &self.api_key {
168            request = request.header("X-API-Key", api_key);
169        }
170
171        request
172    }
173
174    // Helper methods for making HTTP requests
175
176    /// Make a GET request and deserialize JSON response
177    async fn get<T: for<'de> Deserialize<'de>>(&self, endpoint: &str) -> Result<T> {
178        let url = format!("{}/api/v1{}", self.base_url, endpoint);
179        let response = self
180            .authenticated_request(reqwest::Method::GET, &url)
181            .send()
182            .await?;
183
184        if !response.status().is_success() {
185            let error_msg = response.text().await.unwrap_or_default();
186            return Err(TavoError::Api { message: error_msg });
187        }
188
189        let result = response.json().await?;
190        Ok(result)
191    }
192
193    /// Make a GET request with query parameters
194    async fn get_with_params<T: for<'de> Deserialize<'de>>(
195        &self,
196        endpoint: &str,
197        params: &HashMap<String, serde_json::Value>,
198    ) -> Result<T> {
199        let mut url = format!("{}/api/v1{}", self.base_url, endpoint);
200        if !params.is_empty() {
201            url.push('?');
202            for (key, value) in params {
203                if !url.ends_with('?') {
204                    url.push('&');
205                }
206                url.push_str(&format!("{}={}", key, value));
207            }
208        }
209
210        let response = self
211            .authenticated_request(reqwest::Method::GET, &url)
212            .send()
213            .await?;
214
215        if !response.status().is_success() {
216            let error_msg = response.text().await.unwrap_or_default();
217            return Err(TavoError::Api { message: error_msg });
218        }
219
220        let result = response.json().await?;
221        Ok(result)
222    }
223
224    /// Make a POST request
225    async fn post<T: for<'de> Deserialize<'de>>(
226        &self,
227        endpoint: &str,
228        data: &HashMap<String, serde_json::Value>,
229    ) -> Result<T> {
230        let url = format!("{}/api/v1{}", self.base_url, endpoint);
231        let response = self
232            .authenticated_request(reqwest::Method::POST, &url)
233            .json(data)
234            .send()
235            .await?;
236
237        if !response.status().is_success() {
238            let error_msg = response.text().await.unwrap_or_default();
239            return Err(TavoError::Api { message: error_msg });
240        }
241
242        let result = response.json().await?;
243        Ok(result)
244    }
245
246    /// Make a PUT request
247    async fn put<T: for<'de> Deserialize<'de>>(
248        &self,
249        endpoint: &str,
250        data: &HashMap<String, serde_json::Value>,
251    ) -> Result<T> {
252        let url = format!("{}/api/v1{}", self.base_url, endpoint);
253        let response = self
254            .authenticated_request(reqwest::Method::PUT, &url)
255            .json(data)
256            .send()
257            .await?;
258
259        if !response.status().is_success() {
260            let error_msg = response.text().await.unwrap_or_default();
261            return Err(TavoError::Api { message: error_msg });
262        }
263
264        let result = response.json().await?;
265        Ok(result)
266    }
267
268    /// Make a DELETE request
269    async fn delete(&self, endpoint: &str) -> Result<()> {
270        let url = format!("{}/api/v1{}", self.base_url, endpoint);
271        let response = self
272            .authenticated_request(reqwest::Method::DELETE, &url)
273            .send()
274            .await?;
275
276        if !response.status().is_success() {
277            let error_msg = response.text().await.unwrap_or_default();
278            return Err(TavoError::Api { message: error_msg });
279        }
280
281        Ok(())
282    }
283
284    /// Get raw text response
285    async fn get_text(&self, endpoint: &str) -> Result<String> {
286        let url = format!("{}/api/v1{}", self.base_url, endpoint);
287        let response = self
288            .authenticated_request(reqwest::Method::GET, &url)
289            .send()
290            .await?;
291
292        if !response.status().is_success() {
293            let error_msg = response.text().await.unwrap_or_default();
294            return Err(TavoError::Api { message: error_msg });
295        }
296
297        let result = response.text().await?;
298        Ok(result)
299    }
300
301    /// Get raw bytes response
302    async fn get_bytes(&self, endpoint: &str) -> Result<Vec<u8>> {
303        let url = format!("{}/api/v1{}", self.base_url, endpoint);
304        let response = self
305            .authenticated_request(reqwest::Method::GET, &url)
306            .send()
307            .await?;
308
309        if !response.status().is_success() {
310            let error_msg = response.text().await.unwrap_or_default();
311            return Err(TavoError::Api { message: error_msg });
312        }
313
314        let result = response.bytes().await?;
315        Ok(result.to_vec())
316    }
317
318    // Operation accessors
319
320    /// Get authentication operations
321    pub fn auth(&self) -> AuthOperations<'_> {
322        AuthOperations::new(self)
323    }
324
325    /// Get user operations
326    pub fn users(&self) -> UserOperations<'_> {
327        UserOperations::new(self)
328    }
329
330    /// Get organization operations
331    pub fn organizations(&self) -> OrganizationOperations<'_> {
332        OrganizationOperations::new(self)
333    }
334
335    /// Get scan operations
336    pub fn scans(&self) -> ScanOperations<'_> {
337        ScanOperations::new(self)
338    }
339
340    /// Get job operations
341    pub fn jobs(&self) -> JobOperations<'_> {
342        JobOperations::new(self)
343    }
344
345    /// Get webhook operations
346    pub fn webhooks(&self) -> WebhookOperations<'_> {
347        WebhookOperations::new(self)
348    }
349
350    /// Get AI analysis operations
351    pub fn ai_analysis(&self) -> AIAnalysisOperations<'_> {
352        AIAnalysisOperations::new(self)
353    }
354
355    /// Get billing operations
356    pub fn billing(&self) -> BillingOperations<'_> {
357        BillingOperations::new(self)
358    }
359
360    /// Get report operations
361    pub fn reports(&self) -> ReportOperations<'_> {
362        ReportOperations::new(self)
363    }
364
365    /// Health check - verify API connectivity
366    pub async fn health_check(&self) -> Result<HealthResponse> {
367        let url = format!("{}/", self.base_url);
368        let response = self
369            .authenticated_request(reqwest::Method::GET, &url)
370            .send()
371            .await?;
372
373        if !response.status().is_success() {
374            let error_msg = response.text().await.unwrap_or_default();
375            return Err(TavoError::Api { message: error_msg });
376        }
377
378        let result = response.json().await?;
379        Ok(result)
380    }
381}
382
383/// Result of a security scan
384#[derive(Deserialize, Debug, Clone)]
385pub struct ScanResult {
386    pub success: bool,
387    pub vulnerabilities: Vec<Vulnerability>,
388    pub total_issues: u32,
389    pub scan_id: String,
390}
391
392/// Security vulnerability information
393#[derive(Deserialize, Debug, Clone)]
394pub struct Vulnerability {
395    pub id: String,
396    pub title: String,
397    pub description: String,
398    pub severity: String,
399    pub category: String,
400    pub location: Location,
401}
402
403/// Location of a vulnerability in code
404#[derive(Deserialize, Debug, Clone)]
405pub struct Location {
406    pub file: String,
407    pub line: u32,
408    pub column: u32,
409}
410
411/// Result of AI model analysis
412#[derive(Deserialize, Debug, Clone)]
413pub struct ModelAnalysisResult {
414    pub safe: bool,
415    pub risks: Vec<String>,
416    pub recommendations: HashMap<String, serde_json::Value>,
417}
418
419/// Health check response
420#[derive(Deserialize, Debug, Clone)]
421pub struct HealthResponse {
422    pub message: String,
423    pub version: String,
424    pub status: String,
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    #[test]
432    fn test_client_creation() {
433        let client = TavoClient::new("test-key");
434        assert!(client.is_ok());
435    }
436
437    #[test]
438    fn test_invalid_api_key() {
439        let client = TavoClient::new("");
440        assert!(matches!(client, Err(TavoError::InvalidApiKey)));
441    }
442}