use crate::api;
use api::Api;
use log::debug;
use reqwest::{RequestBuilder, Response, StatusCode};
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::fmt::Formatter;
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| {
debug!("Error sending request: {:?}", e);
ApiError::RequestError
})?;
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| {
debug!("Error sending request: {:?}", e);
ApiError::RequestError
})?;
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| {
debug!("Error reading response: {:?}", e);
ApiError::ResponseError
})?;
Self::make_api_response::<T>(json.as_str())
}
status if status == StatusCode::UNAUTHORIZED || status == StatusCode::FORBIDDEN => {
Err(ApiError::Unauthorized)
}
status if status.is_client_error() => Err(ApiError::HttpClientError(
status.as_u16(),
response.text().await.unwrap_or_default(),
)),
status if status.is_server_error() => Err(ApiError::HttpServerError(
status.as_u16(),
response.text().await.unwrap_or_default(),
)),
_ => Err(ApiError::UnexpectedResponse(
response.status().as_u16(),
format!(
"Unexpected 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| ApiError::DeserializationError(e.to_string()))?;
Ok(Some(data))
}
}
pub type ApiResponse<T> = Result<Option<T>, ApiError>;
pub trait ApiRequest {
type Output: DeserializeOwned;
fn send(&self) -> impl Future<Output = ApiResponse<Self::Output>> + Send;
}
#[derive(Debug)]
pub enum ApiError {
RequestError,
ResponseError,
Unauthorized,
HttpClientError(u16, String),
HttpServerError(u16, String),
UnexpectedResponse(u16, String),
DeserializationError(String),
}
impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ApiError::RequestError => {
write!(f, "Error building the request")
}
ApiError::ResponseError => {
write!(f, "Error getting the response")
}
ApiError::Unauthorized => {
write!(f, "Authentication error")
}
ApiError::HttpClientError(status, message) => {
write!(f, "HTTP Client error ({}): {}", status, message)
}
ApiError::HttpServerError(status, message) => {
write!(f, "HTTP Server error ({}): {}", status, message)
}
ApiError::UnexpectedResponse(status, message) => {
write!(f, "Unexpected Response [{}]: {}", status, message)
}
ApiError::DeserializationError(message) => {
write!(f, "Error deserializing: {}", message)
}
}
}
}
impl std::error::Error for ApiError {}