Skip to main content

ltfi_wsap/
client.rs

1use std::env;
2use std::time::Duration;
3use reqwest::{Client as ReqwestClient, Method, RequestBuilder};
4use serde::{Serialize, Deserialize};
5use url::Url;
6
7use crate::error::{Error, Result};
8use crate::types::*;
9
10/// LTFI-WSAP API client configuration
11#[derive(Debug, Clone)]
12pub struct Config {
13    /// API key for authentication
14    pub api_key: String,
15    /// Base URL for the API (defaults to https://api.ltfi.ai)
16    pub base_url: String,
17    /// Request timeout duration
18    pub timeout: Duration,
19}
20
21impl Default for Config {
22    fn default() -> Self {
23        Config {
24            api_key: env::var("LTFI_WSAP_API_KEY").unwrap_or_default(),
25            base_url: "https://api.ltfi.ai".to_string(),
26            timeout: Duration::from_secs(30),
27        }
28    }
29}
30
31/// LTFI-WSAP API client
32pub struct Client {
33    config: Config,
34    http: ReqwestClient,
35}
36
37impl Client {
38    /// Create a new client with the given configuration
39    pub fn new(config: Config) -> Result<Self> {
40        if config.api_key.is_empty() {
41            return Err(Error::Authentication("API key required: set LTFI_WSAP_API_KEY or provide in config".to_string()));
42        }
43
44        let http = ReqwestClient::builder()
45            .timeout(config.timeout)
46            .user_agent("LTFI-WSAP-Rust/2.0.0")
47            .build()
48            .map_err(|e| Error::Network(e.to_string()))?;
49
50        Ok(Client { config, http })
51    }
52
53    /// Create a new client with default configuration (uses LTFI_WSAP_API_KEY env var)
54    pub fn from_env() -> Result<Self> {
55        Self::new(Config::default())
56    }
57
58    /// Helper to build authenticated requests
59    fn request(&self, method: Method, path: &str) -> Result<RequestBuilder> {
60        let url = format!("{}{}", self.config.base_url, path);
61        Ok(self.http
62            .request(method, url)
63            .header("Authorization", format!("Bearer {}", self.config.api_key))
64            .header("Content-Type", "application/json"))
65    }
66
67    /// List entities with optional filters
68    pub async fn list_entities(&self, params: Option<ListParams>) -> Result<PaginatedResponse<Entity>> {
69        let mut req = self.request(Method::GET, "/api/entities/")?;
70        
71        if let Some(p) = params {
72            req = req.query(&p);
73        }
74
75        let resp = req.send().await.map_err(|e| Error::Network(e.to_string()))?;
76        
77        if !resp.status().is_success() {
78            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
79        }
80
81        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
82    }
83
84    /// Get a specific entity by ID
85    pub async fn get_entity(&self, id: &str) -> Result<Entity> {
86        let path = format!("/api/entities/{}/", id);
87        let resp = self.request(Method::GET, &path)?
88            .send()
89            .await
90            .map_err(|e| Error::Network(e.to_string()))?;
91
92        if !resp.status().is_success() {
93            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
94        }
95
96        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
97    }
98
99    /// Create a new entity
100    pub async fn create_entity(&self, request: &CreateEntityRequest) -> Result<Entity> {
101        let resp = self.request(Method::POST, "/api/entities/")?
102            .json(request)
103            .send()
104            .await
105            .map_err(|e| Error::Network(e.to_string()))?;
106
107        if !resp.status().is_success() {
108            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
109        }
110
111        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
112    }
113
114    /// Update an existing entity
115    pub async fn update_entity(&self, id: &str, request: &UpdateEntityRequest) -> Result<Entity> {
116        let path = format!("/api/entities/{}/", id);
117        let resp = self.request(Method::PUT, &path)?
118            .json(request)
119            .send()
120            .await
121            .map_err(|e| Error::Network(e.to_string()))?;
122
123        if !resp.status().is_success() {
124            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
125        }
126
127        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
128    }
129
130    /// Delete an entity
131    pub async fn delete_entity(&self, id: &str) -> Result<()> {
132        let path = format!("/api/entities/{}/", id);
133        let resp = self.request(Method::DELETE, &path)?
134            .send()
135            .await
136            .map_err(|e| Error::Network(e.to_string()))?;
137
138        if !resp.status().is_success() {
139            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
140        }
141
142        Ok(())
143    }
144
145    /// Initiate domain verification
146    pub async fn initiate_verification(&self, domain: &str) -> Result<Verification> {
147        #[derive(Serialize)]
148        struct Request<'a> {
149            domain: &'a str,
150            method: &'a str,
151        }
152
153        let resp = self.request(Method::POST, "/api/verification/initiate/")?
154            .json(&Request { domain, method: "dns_txt" })
155            .send()
156            .await
157            .map_err(|e| Error::Network(e.to_string()))?;
158
159        if !resp.status().is_success() {
160            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
161        }
162
163        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
164    }
165
166    /// Check if a domain is verified
167    pub async fn verify_domain(&self, domain: &str) -> Result<bool> {
168        #[derive(Serialize)]
169        struct Request<'a> {
170            domain: &'a str,
171        }
172
173        #[derive(Deserialize)]
174        struct Response {
175            verified: bool,
176        }
177
178        let resp = self.request(Method::POST, "/api/verification/verify/")?
179            .json(&Request { domain })
180            .send()
181            .await
182            .map_err(|e| Error::Network(e.to_string()))?;
183
184        if !resp.status().is_success() {
185            return Ok(false);
186        }
187
188        let result: Response = resp.json().await.map_err(|e| Error::Parse(e.to_string()))?;
189        Ok(result.verified)
190    }
191
192    /// Generate WSAP data for an entity
193    pub async fn generate_wsap(&self, entity_id: &str, level: DisclosureLevel) -> Result<WSAPData> {
194        #[derive(Serialize)]
195        struct Request<'a> {
196            entity_id: &'a str,
197            disclosure_level: DisclosureLevel,
198        }
199
200        let resp = self.request(Method::POST, "/api/wsap/generate/")?
201            .json(&Request { entity_id, disclosure_level: level })
202            .send()
203            .await
204            .map_err(|e| Error::Network(e.to_string()))?;
205
206        if !resp.status().is_success() {
207            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
208        }
209
210        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
211    }
212
213    /// Fetch public WSAP data for a domain
214    pub async fn fetch_wsap(&self, domain: &str) -> Result<WSAPData> {
215        let path = format!("/api/wsap/public/{}/", domain);
216        let resp = self.request(Method::GET, &path)?
217            .send()
218            .await
219            .map_err(|e| Error::Network(e.to_string()))?;
220
221        if !resp.status().is_success() {
222            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
223        }
224
225        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
226    }
227
228    /// Get current authenticated user
229    pub async fn get_current_user(&self) -> Result<User> {
230        let resp = self.request(Method::GET, "/api/auth/me/")?
231            .send()
232            .await
233            .map_err(|e| Error::Network(e.to_string()))?;
234
235        if !resp.status().is_success() {
236            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
237        }
238
239        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
240    }
241
242    /// Check API health status
243    pub async fn health_check(&self) -> Result<HealthResponse> {
244        let resp = self.request(Method::GET, "/api/health/")?
245            .send()
246            .await
247            .map_err(|e| Error::Network(e.to_string()))?;
248
249        if !resp.status().is_success() {
250            return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
251        }
252
253        resp.json().await.map_err(|e| Error::Parse(e.to_string()))
254    }
255}