use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use crate::error::SdkResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
#[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)
}
}
#[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)?)
}
}
#[async_trait]
#[cfg(not(target_arch = "wasm32"))]
pub trait HttpClient: Send + Sync {
async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;
fn set_auth_token(&mut self, token: String);
fn auth_token(&self) -> Option<String>;
}
#[async_trait(?Send)]
#[cfg(target_arch = "wasm32")]
pub trait HttpClient {
async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse>;
fn set_auth_token(&mut self, token: String);
fn auth_token(&self) -> Option<String>;
}
#[cfg(not(target_arch = "wasm32"))]
pub trait HttpClientFactory: Send + Sync {
fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
}
#[cfg(target_arch = "wasm32")]
pub trait HttpClientFactory {
fn create_client(&self, base_url: &str) -> Box<dyn HttpClient>;
}
#[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 {}
#[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 {}