agentlink-core 0.1.0

AgentLink SDK Core - Platform agnostic core library
Documentation
//! HTTP Client Abstraction
//!
//! This module defines the HTTP client trait that must be implemented
//! by platform-specific backends (native reqwest, wasm fetch, etc.)

use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};

use crate::error::SdkResult;

/// HTTP method enum
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
}

/// HTTP request builder
#[derive(Debug, Clone)]
pub struct HttpRequest {
    pub method: HttpMethod,
    pub url: String,
    pub headers: Vec<(String, String)>,
    pub body: Option<String>,
}

impl HttpRequest {
    pub fn new(method: HttpMethod, url: impl Into<String>) -> Self {
        Self {
            method,
            url: url.into(),
            headers: Vec::new(),
            body: None,
        }
    }

    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.headers.push((key.into(), value.into()));
        self
    }

    pub fn json_body(mut self, body: impl Serialize) -> SdkResult<Self> {
        self.body = Some(serde_json::to_string(&body)?);
        self.headers.push(("Content-Type".to_string(), "application/json".to_string()));
        Ok(self)
    }
}

/// HTTP response
#[derive(Debug, Clone)]
pub struct HttpResponse {
    pub status: u16,
    pub headers: Vec<(String, String)>,
    pub body: String,
}

impl HttpResponse {
    pub fn is_success(&self) -> bool {
        self.status >= 200 && self.status < 300
    }

    pub fn json<T: DeserializeOwned>(&self) -> SdkResult<T> {
        Ok(serde_json::from_str(&self.body)?)
    }
}

/// HTTP client trait
///
/// Platform-specific implementations must implement this trait.
#[async_trait]
#[cfg(not(target_arch = "wasm32"))]
pub trait HttpClient: Send + Sync {
    /// Send an HTTP request
    async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;

    /// Set default authorization token
    fn set_auth_token(&mut self, token: String);

    /// Get current auth token
    fn auth_token(&self) -> Option<String>;
}

/// HTTP client trait (WASM version)
///
/// Platform-specific implementations must implement this trait.
#[async_trait(?Send)]
#[cfg(target_arch = "wasm32")]
pub trait HttpClient {
    /// Send an HTTP request
    async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;

    /// Set default authorization token
    fn set_auth_token(&mut self, token: String);

    /// Get current auth token
    fn auth_token(&self) -> Option<String>;
}

/// HTTP client factory trait
///
/// Used to create HTTP clients with platform-specific implementations.
#[cfg(not(target_arch = "wasm32"))]
pub trait HttpClientFactory: Send + Sync {
    fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
}

/// HTTP client factory trait (WASM version)
#[cfg(target_arch = "wasm32")]
pub trait HttpClientFactory {
    fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
}

/// Helper methods for HTTP clients
#[async_trait]
#[cfg(not(target_arch = "wasm32"))]
pub trait HttpClientExt: HttpClient {
    async fn get<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Get, path.to_string());
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn post<T: DeserializeOwned, B: Serialize + Send + Sync>(
        &self,
        path: &str,
        body: &B,
    ) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Post, path.to_string())
            .json_body(body)?;
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn put<T: DeserializeOwned, B: Serialize + Send + Sync>(
        &self,
        path: &str,
        body: &B,
    ) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Put, path.to_string())
            .json_body(body)?;
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn delete<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Delete, path.to_string());
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }
}

#[async_trait]
#[cfg(not(target_arch = "wasm32"))]
impl<T: HttpClient> HttpClientExt for T {}

/// Helper methods for HTTP clients (WASM version)
#[async_trait(?Send)]
#[cfg(target_arch = "wasm32")]
pub trait HttpClientExt: HttpClient {
    async fn get<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Get, path.to_string());
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn post<T: DeserializeOwned, B: Serialize>(
        &self,
        path: &str,
        body: &B,
    ) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Post, path.to_string())
            .json_body(body)?;
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn put<T: DeserializeOwned, B: Serialize>(
        &self,
        path: &str,
        body: &B,
    ) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Put, path.to_string())
            .json_body(body)?;
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }

    async fn delete<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
        let request = HttpRequest::new(HttpMethod::Delete, path.to_string());
        let response = self.request(request).await?;
        if response.is_success() {
            response.json()
        } else {
            Err(crate::error::SdkError::Http(format!(
                "HTTP {}: {}",
                response.status, response.body
            )))
        }
    }
}

#[async_trait(?Send)]
#[cfg(target_arch = "wasm32")]
impl<T: HttpClient> HttpClientExt for T {}