mod activation;
mod errors;
mod service_client;
mod webhooks;
pub mod access;
pub mod ai;
pub mod audit;
pub mod auth;
pub mod billing;
pub mod crypto_inventory;
pub mod kms;
pub mod search;
pub mod storage;
pub mod tenant;
pub mod vault;
#[cfg(feature = "crypto")]
pub mod crypto;
pub use activation::{Activation, ActivationResult};
pub use errors::{ApiError, AuthError, Error, NetworkError, WebhookError};
pub use webhooks::{parse_webhook, verify_webhook_signature, WebhookEvent, MAX_WEBHOOK_SKEW};
use std::sync::Arc;
const DEFAULT_BASE_URL: &str = "https://api.qnsp.cuilabs.io";
pub(crate) const SDK_ID: &str = "qnsp-rust";
pub(crate) const SDK_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone)]
pub struct ClientOptions {
pub api_key: String,
pub base_url: String,
pub timeout_seconds: u64,
}
impl ClientOptions {
pub fn with_api_key(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
base_url: DEFAULT_BASE_URL.to_string(),
timeout_seconds: 15,
}
}
}
#[derive(Clone, Debug)]
pub struct Client {
activation: Arc<Activation>,
http: reqwest::Client,
}
impl Client {
pub fn new(opts: ClientOptions) -> Result<Self, Error> {
if opts.api_key.trim().is_empty() {
return Err(Error::Auth(AuthError {
code: None,
message: "api_key is required (sign up at https://cloud.qnsp.cuilabs.io/auth)".into(),
}));
}
let http = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(opts.timeout_seconds))
.build()
.map_err(|e| Error::Network(NetworkError {
op: "build".into(),
url: opts.base_url.clone(),
cause: e.to_string(),
}))?;
let activation = Activation::new(opts.api_key, opts.base_url, http.clone());
Ok(Self {
activation: Arc::new(activation),
http,
})
}
pub fn vault(&self) -> vault::Client {
vault::Client::new(self.activation.clone(), self.http.clone())
}
pub fn kms(&self) -> kms::Client {
kms::Client::new(self.activation.clone(), self.http.clone())
}
pub fn audit(&self) -> audit::Client {
audit::Client::new(self.activation.clone(), self.http.clone())
}
pub fn auth(&self) -> auth::Client {
auth::Client::new(self.activation.clone(), self.http.clone())
}
pub fn tenant(&self) -> tenant::Client {
tenant::Client::new(self.activation.clone(), self.http.clone())
}
pub fn access(&self) -> access::Client {
access::Client::new(self.activation.clone(), self.http.clone())
}
pub fn billing(&self) -> billing::Client {
billing::Client::new(self.activation.clone(), self.http.clone())
}
pub fn crypto_inventory(&self) -> crypto_inventory::Client {
crypto_inventory::Client::new(self.activation.clone(), self.http.clone())
}
pub fn storage(&self) -> storage::Client {
storage::Client::new(self.activation.clone(), self.http.clone())
}
pub fn search(&self) -> search::Client {
search::Client::new(self.activation.clone(), self.http.clone())
}
pub fn ai(&self) -> ai::Client {
ai::Client::new(self.activation.clone(), self.http.clone())
}
pub async fn ensure_activated(&self) -> Result<ActivationResult, Error> {
self.activation.get().await
}
pub async fn tenant_id(&self) -> Result<String, Error> {
Ok(self.activation.get().await?.tenant_id)
}
pub async fn tier(&self) -> Result<String, Error> {
Ok(self.activation.get().await?.tier)
}
pub async fn limits(&self) -> Result<serde_json::Map<String, serde_json::Value>, Error> {
Ok(self.activation.get().await?.limits)
}
pub async fn has_feature(&self, feature: &str) -> Result<bool, Error> {
let limits = self.limits().await?;
Ok(limits
.get(feature)
.and_then(|v| v.as_bool())
.unwrap_or(false))
}
}