use std::sync::Arc;
use crate::config::{resolve_config, ClientConfig};
use crate::error::Error;
use crate::http::HttpTransport;
use crate::models::events::ListEventsParams;
use crate::resources::{
AttestationsResource, AuditResource, DocumentsResource, EventsResource, TracesResource,
};
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub valid: bool,
pub reason: Option<String>,
pub base_url: String,
}
#[derive(Clone)]
pub struct InvoanceClient {
transport: Arc<HttpTransport>,
base_url: String,
}
impl InvoanceClient {
pub fn new() -> Result<Self, Error> {
Self::from_config(ClientConfig::default())
}
pub fn from_config(config: ClientConfig) -> Result<Self, Error> {
let resolved = resolve_config(config)?;
let base_url = resolved.base_url.clone();
let transport = Arc::new(HttpTransport::new(resolved)?);
Ok(Self {
transport,
base_url,
})
}
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
pub fn events(&self) -> EventsResource {
EventsResource::new(self.transport.clone())
}
pub fn documents(&self) -> DocumentsResource {
DocumentsResource::new(self.transport.clone())
}
pub fn attestations(&self) -> AttestationsResource {
AttestationsResource::new(self.transport.clone())
}
pub fn traces(&self) -> TracesResource {
TracesResource::new(self.transport.clone())
}
pub fn audit(&self) -> AuditResource {
AuditResource::new(self.transport.clone())
}
pub async fn validate(&self) -> ValidationResult {
let base_url = self.base_url.clone();
let params = ListEventsParams {
limit: Some(1),
..Default::default()
};
match self.events().list(params).await {
Ok(_) => ValidationResult {
valid: true,
reason: None,
base_url,
},
Err(e) if e.is_authentication() => ValidationResult {
valid: false,
reason: Some("Authentication failed — check INVOANCE_API_KEY".into()),
base_url,
},
Err(e) if e.is_forbidden() => ValidationResult {
valid: true,
reason: Some("API key authenticated but lacks permission to list events".into()),
base_url,
},
Err(e) if e.is_quota_exceeded() => ValidationResult {
valid: true,
reason: Some("API key authenticated but currently rate limited".into()),
base_url,
},
Err(e) if e.is_network() || e.is_timeout() => ValidationResult {
valid: false,
reason: Some(format!("Server unreachable: {e}")),
base_url,
},
Err(e) => ValidationResult {
valid: false,
reason: Some(e.to_string()),
base_url,
},
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ClientBuilder {
config: ClientConfig,
}
impl ClientBuilder {
pub fn api_key(mut self, key: impl Into<String>) -> Self {
self.config.api_key = Some(key.into());
self
}
pub fn base_url(mut self, url: impl Into<String>) -> Self {
self.config.base_url = Some(url.into());
self
}
pub fn api_version(mut self, v: impl Into<String>) -> Self {
self.config.api_version = Some(v.into());
self
}
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.config.timeout = Some(timeout);
self
}
pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
self.config.idempotency_key = Some(key.into());
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.config.extra_headers.insert(name.into(), value.into());
self
}
pub fn build(self) -> Result<InvoanceClient, Error> {
InvoanceClient::from_config(self.config)
}
}