use std::time::Duration;
use reqwest::Client;
use serde::de::DeserializeOwned;
use crate::errors::VerifexError;
use crate::types::*;
const DEFAULT_BASE_URL: &str = "https://api.verifex.dev";
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const USER_AGENT: &str = "verifex-rust/0.2.0";
pub struct Verifex {
api_key: String,
base_url: String,
client: Client,
}
impl Verifex {
pub fn new(api_key: &str) -> Self {
Self {
api_key: api_key.to_string(),
base_url: DEFAULT_BASE_URL.to_string(),
client: Client::builder()
.timeout(DEFAULT_TIMEOUT)
.user_agent(USER_AGENT)
.build()
.expect("failed to build HTTP client"),
}
}
pub fn with_base_url(mut self, url: &str) -> Self {
self.base_url = url.trim_end_matches('/').to_string();
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.client = Client::builder()
.timeout(timeout)
.user_agent(USER_AGENT)
.build()
.expect("failed to build HTTP client");
self
}
pub async fn screen(&self, req: ScreenRequest) -> Result<ScreenResult, VerifexError> {
self.post("/v1/screen", &req).await
}
pub async fn batch_screen(
&self,
entities: Vec<ScreenRequest>,
) -> Result<BatchScreenResult, VerifexError> {
#[derive(serde::Serialize)]
struct Body {
entities: Vec<ScreenRequest>,
}
self.post("/v1/screen/batch", &Body { entities }).await
}
pub async fn usage(&self) -> Result<UsageStats, VerifexError> {
self.get("/v1/usage").await
}
pub async fn list_keys(&self) -> Result<Vec<ApiKeyInfo>, VerifexError> {
self.get("/v1/keys").await
}
pub async fn create_key(&self, name: &str) -> Result<ApiKeyCreated, VerifexError> {
#[derive(serde::Serialize)]
struct Body<'a> {
name: &'a str,
}
self.post("/v1/keys", &Body { name }).await
}
pub async fn revoke_key(&self, key_id: &str) -> Result<(), VerifexError> {
let url = format!("{}/v1/keys/{}", self.base_url, key_id);
let resp = self
.client
.delete(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await?;
if resp.status().is_success() {
Ok(())
} else {
Err(parse_error(resp).await)
}
}
pub async fn health(&self) -> Result<HealthResponse, VerifexError> {
let url = format!("{}/v1/health", self.base_url);
let resp = self.client.get(&url).send().await?;
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(parse_error(resp).await)
}
}
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, VerifexError> {
let url = format!("{}{}", self.base_url, path);
let resp = self
.client
.get(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await?;
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(parse_error(resp).await)
}
}
async fn post<T: DeserializeOwned, B: serde::Serialize>(
&self,
path: &str,
body: &B,
) -> Result<T, VerifexError> {
let url = format!("{}{}", self.base_url, path);
let resp = self
.client
.post(&url)
.header("Authorization", format!("Bearer {}", self.api_key))
.json(body)
.send()
.await?;
if resp.status().is_success() {
Ok(resp.json().await?)
} else {
Err(parse_error(resp).await)
}
}
}
async fn parse_error(resp: reqwest::Response) -> VerifexError {
let status = resp.status().as_u16();
let body: serde_json::Value = resp.json().await.unwrap_or_default();
let message = body["error"]
.as_str()
.unwrap_or("Unknown error")
.to_string();
let code = body["code"].as_str().unwrap_or("UNKNOWN").to_string();
let request_id = body["request_id"].as_str().unwrap_or("").to_string();
match status {
401 => VerifexError::Auth {
message,
request_id,
},
402 => VerifexError::QuotaExceeded {
message,
request_id,
},
429 => VerifexError::RateLimit {
message,
retry_after: 60,
request_id,
},
_ => VerifexError::Api {
message,
code,
status,
request_id,
},
}
}