use crate::api;
use crate::Error;
use api::Api;
use reqwest::{RequestBuilder, Response, StatusCode};
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::future::Future;
#[derive(Clone, Default, Debug)]
pub struct Client {
pub base_path: String,
pub http_client: reqwest::Client,
pub api_token: String,
}
impl Client {
pub fn api(self) -> Api {
Api { client: self }
}
}
pub fn new(base_path: &str, api_token: &str) -> Client {
Client {
base_path: base_path.to_string(),
http_client: reqwest::Client::new(),
api_token: api_token.to_string(),
}
}
impl Client {
pub async fn builder(&self, req: RequestBuilder) -> RequestBuilder {
req.header("Authorization", format!("Bearer {}", self.api_token))
.header("Content-Type", "application/json")
}
pub fn with_http_client(&mut self, http_client: reqwest::Client) {
self.http_client = http_client;
}
pub async fn get<T: ApiRequest>(
&self,
uri: &str,
params: Option<HashMap<String, String>>,
) -> ApiResponse<T::Output> {
let uri = format!("{}/{}", self.base_path, uri);
let get = self.http_client.get(uri).query(¶ms);
let req = self
.builder(get)
.await
.build()
.expect("Failed to build request");
let response = self.http_client.execute(req).await.map_err(|e| {
Error::RequestError(format!("Error sending request: {:?}", e))
})?;
Self::process_response::<T>(response).await
}
pub async fn post<T: ApiRequest>(
&self,
uri: &str,
body: &str,
) -> ApiResponse<<T as ApiRequest>::Output> {
let uri = format!("{}/{}", self.base_path, uri);
let post = self.http_client.post(uri).body(body.to_string());
let req = self
.builder(post)
.await
.build()
.expect("Failed to build request");
let response = self.http_client.execute(req).await.map_err(|e| {
Error::RequestError(format!("Error sending request: {:?}", e))
})?;
Self::process_response::<T>(response).await
}
async fn process_response<T: ApiRequest>(
response: Response,
) -> ApiResponse<<T as ApiRequest>::Output> {
match response.status() {
status if status.is_success() => {
let json = response.text().await.map_err(|e| {
Error::ResponseError(format!("Error reading response: {e:#?}"))
})?;
Self::make_api_response::<T>(json.as_str())
}
status if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN => {
Err(Error::Unauthorized)
}
status if status.is_client_error() => Err(Error::ResponseError(format!(
"HTTP Client error [{}]: {}",
status.as_u16(),
response.text().await.unwrap_or_default(),
))),
status if status.is_server_error() => Err(Error::ResponseError(format!(
"HTTP Server error [{}]: {}",
status.as_u16(),
response.text().await.unwrap_or_default(),
))),
_ => Err(Error::Unexpected(format!(
"Unexpected HTTP Response [{}]: {}",
response.status(),
response.text().await.unwrap_or_default()
))),
}
}
fn make_api_response<T: ApiRequest>(json: &str) -> ApiResponse<<T as ApiRequest>::Output> {
if json.len() == 0 {
return Ok(None);
}
let data = serde_json::from_str::<T::Output>(json)
.map_err(|e| Error::ResponseError(format!("Error deserializing: {e:#?}")))?;
Ok(Some(data))
}
}
pub type ApiResponse<T> = Result<Option<T>, Error>;
pub trait ApiRequest {
type Output: DeserializeOwned;
fn send(&self) -> impl Future<Output = ApiResponse<Self::Output>> + Send;
}