1pub 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#[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
64pub type Result<T> = std::result::Result<T, TavoError>;
66
67pub 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 pub fn new(api_key: impl Into<String>) -> Result<Self> {
92 Self::with_api_key(api_key)
93 }
94
95 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 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 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 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 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 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 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 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 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 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 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 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 pub fn auth(&self) -> AuthOperations<'_> {
322 AuthOperations::new(self)
323 }
324
325 pub fn users(&self) -> UserOperations<'_> {
327 UserOperations::new(self)
328 }
329
330 pub fn organizations(&self) -> OrganizationOperations<'_> {
332 OrganizationOperations::new(self)
333 }
334
335 pub fn scans(&self) -> ScanOperations<'_> {
337 ScanOperations::new(self)
338 }
339
340 pub fn jobs(&self) -> JobOperations<'_> {
342 JobOperations::new(self)
343 }
344
345 pub fn webhooks(&self) -> WebhookOperations<'_> {
347 WebhookOperations::new(self)
348 }
349
350 pub fn ai_analysis(&self) -> AIAnalysisOperations<'_> {
352 AIAnalysisOperations::new(self)
353 }
354
355 pub fn billing(&self) -> BillingOperations<'_> {
357 BillingOperations::new(self)
358 }
359
360 pub fn reports(&self) -> ReportOperations<'_> {
362 ReportOperations::new(self)
363 }
364
365 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#[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#[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#[derive(Deserialize, Debug, Clone)]
405pub struct Location {
406 pub file: String,
407 pub line: u32,
408 pub column: u32,
409}
410
411#[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#[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}