ltfi-wsap 2.0.2

LTFI-WSAP (Layered Transformer Framework Intelligence - Web System Alignment Protocol) Rust SDK
Documentation
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::*;

/// LTFI-WSAP API client configuration
#[derive(Debug, Clone)]
pub struct Config {
    /// API key for authentication
    pub api_key: String,
    /// Base URL for the API (defaults to https://api.ltfi.ai)
    pub base_url: String,
    /// Request timeout duration
    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),
        }
    }
}

/// LTFI-WSAP API client
pub struct Client {
    config: Config,
    http: ReqwestClient,
}

impl Client {
    /// Create a new client with the given configuration
    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 })
    }

    /// Create a new client with default configuration (uses LTFI_WSAP_API_KEY env var)
    pub fn from_env() -> Result<Self> {
        Self::new(Config::default())
    }

    /// Helper to build authenticated requests
    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"))
    }

    /// List entities with optional filters
    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()))
    }

    /// Get a specific entity by ID
    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()))
    }

    /// Create a new entity
    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()))
    }

    /// Update an existing entity
    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()))
    }

    /// Delete an entity
    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(())
    }

    /// Initiate domain verification
    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()))
    }

    /// Check if a domain is verified
    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)
    }

    /// Generate WSAP data for an entity
    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()))
    }

    /// Fetch public WSAP data for a domain
    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()))
    }

    /// Get current authenticated user
    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()))
    }

    /// Check API health status
    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()))
    }
}