use std::env;
use std::time::Duration;
use reqwest::{Client as ReqwestClient, Method, RequestBuilder};
use serde::{Serialize, Deserialize};
use url::Url;
use crate::error::{Error, Result};
use crate::types::*;
#[derive(Debug, Clone)]
pub struct Config {
pub api_key: String,
pub base_url: String,
pub timeout: Duration,
}
impl Default for Config {
fn default() -> Self {
Config {
api_key: env::var("LTFI_WSAP_API_KEY").unwrap_or_default(),
base_url: "https://api.ltfi.ai".to_string(),
timeout: Duration::from_secs(30),
}
}
}
pub struct Client {
config: Config,
http: ReqwestClient,
}
impl Client {
pub fn new(config: Config) -> Result<Self> {
if config.api_key.is_empty() {
return Err(Error::Authentication("API key required: set LTFI_WSAP_API_KEY or provide in config".to_string()));
}
let http = ReqwestClient::builder()
.timeout(config.timeout)
.user_agent("LTFI-WSAP-Rust/2.0.0")
.build()
.map_err(|e| Error::Network(e.to_string()))?;
Ok(Client { config, http })
}
pub fn from_env() -> Result<Self> {
Self::new(Config::default())
}
fn request(&self, method: Method, path: &str) -> Result<RequestBuilder> {
let url = format!("{}{}", self.config.base_url, path);
Ok(self.http
.request(method, url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json"))
}
pub async fn list_entities(&self, params: Option<ListParams>) -> Result<PaginatedResponse<Entity>> {
let mut req = self.request(Method::GET, "/api/entities/")?;
if let Some(p) = params {
req = req.query(&p);
}
let resp = req.send().await.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn get_entity(&self, id: &str) -> Result<Entity> {
let path = format!("/api/entities/{}/", id);
let resp = self.request(Method::GET, &path)?
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn create_entity(&self, request: &CreateEntityRequest) -> Result<Entity> {
let resp = self.request(Method::POST, "/api/entities/")?
.json(request)
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn update_entity(&self, id: &str, request: &UpdateEntityRequest) -> Result<Entity> {
let path = format!("/api/entities/{}/", id);
let resp = self.request(Method::PUT, &path)?
.json(request)
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn delete_entity(&self, id: &str) -> Result<()> {
let path = format!("/api/entities/{}/", id);
let resp = self.request(Method::DELETE, &path)?
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
Ok(())
}
pub async fn initiate_verification(&self, domain: &str) -> Result<Verification> {
#[derive(Serialize)]
struct Request<'a> {
domain: &'a str,
method: &'a str,
}
let resp = self.request(Method::POST, "/api/verification/initiate/")?
.json(&Request { domain, method: "dns_txt" })
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn verify_domain(&self, domain: &str) -> Result<bool> {
#[derive(Serialize)]
struct Request<'a> {
domain: &'a str,
}
#[derive(Deserialize)]
struct Response {
verified: bool,
}
let resp = self.request(Method::POST, "/api/verification/verify/")?
.json(&Request { domain })
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Ok(false);
}
let result: Response = resp.json().await.map_err(|e| Error::Parse(e.to_string()))?;
Ok(result.verified)
}
pub async fn generate_wsap(&self, entity_id: &str, level: DisclosureLevel) -> Result<WSAPData> {
#[derive(Serialize)]
struct Request<'a> {
entity_id: &'a str,
disclosure_level: DisclosureLevel,
}
let resp = self.request(Method::POST, "/api/wsap/generate/")?
.json(&Request { entity_id, disclosure_level: level })
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn fetch_wsap(&self, domain: &str) -> Result<WSAPData> {
let path = format!("/api/wsap/public/{}/", domain);
let resp = self.request(Method::GET, &path)?
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn get_current_user(&self) -> Result<User> {
let resp = self.request(Method::GET, "/api/auth/me/")?
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
pub async fn health_check(&self) -> Result<HealthResponse> {
let resp = self.request(Method::GET, "/api/health/")?
.send()
.await
.map_err(|e| Error::Network(e.to_string()))?;
if !resp.status().is_success() {
return Err(Error::Api(resp.status().as_u16(), resp.text().await.unwrap_or_default()));
}
resp.json().await.map_err(|e| Error::Parse(e.to_string()))
}
}