Skip to main content

agentlink_core/http/
mod.rs

1//! HTTP Client Abstraction
2//!
3//! This module defines the HTTP client trait that must be implemented
4//! by platform-specific backends (native reqwest, wasm fetch, etc.)
5
6use async_trait::async_trait;
7use serde::{de::DeserializeOwned, Serialize};
8
9use crate::error::SdkResult;
10
11/// HTTP method enum
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum HttpMethod {
14    Get,
15    Post,
16    Put,
17    Delete,
18    Patch,
19}
20
21/// HTTP request builder
22#[derive(Debug, Clone)]
23pub struct HttpRequest {
24    pub method: HttpMethod,
25    pub url: String,
26    pub headers: Vec<(String, String)>,
27    pub body: Option<String>,
28}
29
30impl HttpRequest {
31    pub fn new(method: HttpMethod, url: impl Into<String>) -> Self {
32        Self {
33            method,
34            url: url.into(),
35            headers: Vec::new(),
36            body: None,
37        }
38    }
39
40    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
41        self.headers.push((key.into(), value.into()));
42        self
43    }
44
45    pub fn json_body(mut self, body: impl Serialize) -> SdkResult<Self> {
46        self.body = Some(serde_json::to_string(&body)?);
47        self.headers.push(("Content-Type".to_string(), "application/json".to_string()));
48        Ok(self)
49    }
50}
51
52/// HTTP response
53#[derive(Debug, Clone)]
54pub struct HttpResponse {
55    pub status: u16,
56    pub headers: Vec<(String, String)>,
57    pub body: String,
58}
59
60impl HttpResponse {
61    pub fn is_success(&self) -> bool {
62        self.status >= 200 && self.status < 300
63    }
64
65    pub fn json<T: DeserializeOwned>(&self) -> SdkResult<T> {
66        Ok(serde_json::from_str(&self.body)?)
67    }
68}
69
70/// HTTP client trait
71///
72/// Platform-specific implementations must implement this trait.
73#[async_trait]
74#[cfg(not(target_arch = "wasm32"))]
75pub trait HttpClient: Send + Sync {
76    /// Send an HTTP request
77    async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;
78
79    /// Set default authorization token
80    fn set_auth_token(&mut self, token: String);
81
82    /// Get current auth token
83    fn auth_token(&self) -> Option<String>;
84}
85
86/// HTTP client trait (WASM version)
87///
88/// Platform-specific implementations must implement this trait.
89#[async_trait(?Send)]
90#[cfg(target_arch = "wasm32")]
91pub trait HttpClient {
92    /// Send an HTTP request
93    async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;
94
95    /// Set default authorization token
96    fn set_auth_token(&mut self, token: String);
97
98    /// Get current auth token
99    fn auth_token(&self) -> Option<String>;
100}
101
102/// HTTP client factory trait
103///
104/// Used to create HTTP clients with platform-specific implementations.
105#[cfg(not(target_arch = "wasm32"))]
106pub trait HttpClientFactory: Send + Sync {
107    fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
108}
109
110/// HTTP client factory trait (WASM version)
111#[cfg(target_arch = "wasm32")]
112pub trait HttpClientFactory {
113    fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
114}
115
116/// Helper methods for HTTP clients
117#[async_trait]
118#[cfg(not(target_arch = "wasm32"))]
119pub trait HttpClientExt: HttpClient {
120    async fn get<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
121        let request = HttpRequest::new(HttpMethod::Get, path.to_string());
122        let response = self.request(request).await?;
123        if response.is_success() {
124            response.json()
125        } else {
126            Err(crate::error::SdkError::Http(format!(
127                "HTTP {}: {}",
128                response.status, response.body
129            )))
130        }
131    }
132
133    async fn post<T: DeserializeOwned, B: Serialize + Send + Sync>(
134        &self,
135        path: &str,
136        body: &B,
137    ) -> SdkResult<T> {
138        let request = HttpRequest::new(HttpMethod::Post, path.to_string())
139            .json_body(body)?;
140        let response = self.request(request).await?;
141        if response.is_success() {
142            response.json()
143        } else {
144            Err(crate::error::SdkError::Http(format!(
145                "HTTP {}: {}",
146                response.status, response.body
147            )))
148        }
149    }
150
151    async fn put<T: DeserializeOwned, B: Serialize + Send + Sync>(
152        &self,
153        path: &str,
154        body: &B,
155    ) -> SdkResult<T> {
156        let request = HttpRequest::new(HttpMethod::Put, path.to_string())
157            .json_body(body)?;
158        let response = self.request(request).await?;
159        if response.is_success() {
160            response.json()
161        } else {
162            Err(crate::error::SdkError::Http(format!(
163                "HTTP {}: {}",
164                response.status, response.body
165            )))
166        }
167    }
168
169    async fn delete<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
170        let request = HttpRequest::new(HttpMethod::Delete, path.to_string());
171        let response = self.request(request).await?;
172        if response.is_success() {
173            response.json()
174        } else {
175            Err(crate::error::SdkError::Http(format!(
176                "HTTP {}: {}",
177                response.status, response.body
178            )))
179        }
180    }
181}
182
183#[async_trait]
184#[cfg(not(target_arch = "wasm32"))]
185impl<T: HttpClient> HttpClientExt for T {}
186
187/// Helper methods for HTTP clients (WASM version)
188#[async_trait(?Send)]
189#[cfg(target_arch = "wasm32")]
190pub trait HttpClientExt: HttpClient {
191    async fn get<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
192        let request = HttpRequest::new(HttpMethod::Get, path.to_string());
193        let response = self.request(request).await?;
194        if response.is_success() {
195            response.json()
196        } else {
197            Err(crate::error::SdkError::Http(format!(
198                "HTTP {}: {}",
199                response.status, response.body
200            )))
201        }
202    }
203
204    async fn post<T: DeserializeOwned, B: Serialize>(
205        &self,
206        path: &str,
207        body: &B,
208    ) -> SdkResult<T> {
209        let request = HttpRequest::new(HttpMethod::Post, path.to_string())
210            .json_body(body)?;
211        let response = self.request(request).await?;
212        if response.is_success() {
213            response.json()
214        } else {
215            Err(crate::error::SdkError::Http(format!(
216                "HTTP {}: {}",
217                response.status, response.body
218            )))
219        }
220    }
221
222    async fn put<T: DeserializeOwned, B: Serialize>(
223        &self,
224        path: &str,
225        body: &B,
226    ) -> SdkResult<T> {
227        let request = HttpRequest::new(HttpMethod::Put, path.to_string())
228            .json_body(body)?;
229        let response = self.request(request).await?;
230        if response.is_success() {
231            response.json()
232        } else {
233            Err(crate::error::SdkError::Http(format!(
234                "HTTP {}: {}",
235                response.status, response.body
236            )))
237        }
238    }
239
240    async fn delete<T: DeserializeOwned>(&self, path: &str) -> SdkResult<T> {
241        let request = HttpRequest::new(HttpMethod::Delete, path.to_string());
242        let response = self.request(request).await?;
243        if response.is_success() {
244            response.json()
245        } else {
246            Err(crate::error::SdkError::Http(format!(
247                "HTTP {}: {}",
248                response.status, response.body
249            )))
250        }
251    }
252}
253
254#[async_trait(?Send)]
255#[cfg(target_arch = "wasm32")]
256impl<T: HttpClient> HttpClientExt for T {}