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#[derive(Debug, Clone)]
12pub struct Config {
13 pub api_key: String,
15 pub base_url: String,
17 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
31pub struct Client {
33 config: Config,
34 http: ReqwestClient,
35}
36
37impl Client {
38 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 pub fn from_env() -> Result<Self> {
55 Self::new(Config::default())
56 }
57
58 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 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 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 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 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 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 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 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 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 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 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 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}