#![doc = include_str!("../README.md")]
#![forbid(missing_docs)]
use std::collections::HashMap;
use reqwest::{
Client,
RequestBuilder,
};
use serde_json::Value;
use serde::de::DeserializeOwned;
use async_trait::async_trait;
use thiserror::Error as ErrorMacro;
pub use reqwest;
pub use serde_json;
pub use serde;
pub use ::async_trait;
pub trait RequestInfo {
const BASE_URL : &'static str;
fn client(&self) -> &Client;
}
pub trait RequestModifiers: RequestInfo {
fn create_endpoint(endpoint : &str) -> String {
format!("{}/{}",Self::BASE_URL,endpoint)
}
fn add_header_if(request_builder: RequestBuilder,key: &str, value: &str,closure : impl FnOnce() -> bool) -> RequestBuilder {
match closure() {
true => request_builder.header(key, value),
false => request_builder
}
}
}
pub trait RequestDefaults: RequestModifiers {
fn default_headers(&self,request_builder : reqwest::RequestBuilder) -> reqwest::RequestBuilder {
request_builder
}
fn default_parameters(&self,request_builder : reqwest::RequestBuilder) -> reqwest::RequestBuilder {
request_builder
}
fn default_post_requestor(&self,endpoint : &str, json : String) -> reqwest::RequestBuilder {
self.default_parameters(self.default_headers(self.client().post(Self::create_endpoint(endpoint)))).body(json)
}
fn default_get_requestor<'a>(&self,endpoint : &str,parameters : &HashMap<&'a str,Value>) -> reqwest::RequestBuilder {
self.default_parameters(self.default_headers(self.client().get(Self::create_endpoint(endpoint)))).query(¶meters)
}
}
#[async_trait]
pub trait RequestHandler<T : DeserializeOwned,O : DeserializeOwned,E : DeserializeOwned> : RequestDefaults {
async fn request_map(request: reqwest::RequestBuilder,map : impl FnOnce(T) -> O + Send + Sync) -> Result<O,RequestError<E>> {
let response = request.send().await?;
let status = response.status();
let body = response.bytes().await?;
match status.is_success() {
true => {
let json = serde_json::from_slice(&body)?;
Ok(map(json))
}
false => {
let json = serde_json::from_slice(&body)?;
Err(RequestError::ErrorPayload(json))
}
}
}
fn resolve_error(&self,response : Result<O,RequestError<E>>,error_handler : impl Fn(RequestError<E>) + Sync) -> Option<O> {
match response {
Ok(value) => Some(value),
Err(error) => {
error_handler(error);
None
}
}
}
async fn get_request_handler<'a>(&self,endpoint : &str,parameters : &HashMap<&'a str,Value>,map : impl FnOnce(T) -> O + Send + Sync,error_handler : impl Fn(RequestError<E>) + Sync + Send) -> Option<O> {
let request = self.default_get_requestor(endpoint,parameters);
let response = Self::request_map(request,map).await;
self.resolve_error(response,error_handler)
}
async fn post_request_handler(&self,endpoint : &str,json : String,map : impl FnOnce(T) -> O + Send + Sync,error_handler : impl Fn(RequestError<E>) + Sync + Send) -> Option<O> {
let request = self.default_post_requestor(endpoint,json);
let response = Self::request_map(request,map).await;
self.resolve_error(response,error_handler)
}
}
#[derive(ErrorMacro)]
pub enum RequestError<E> {
#[error(
r#"Failed operation relating to request to ({}) with status code of {}.
Is request: {},
Is connect: {},
Is body: {}"#,
.0.url().map(|x|x.to_string()).unwrap_or(String::from("Not Found")),
.0.status().map(|x|x.to_string()).unwrap_or(String::from("Not Found")),
.0.is_request(),
.0.is_connect(),
.0.is_body()
)]
RequestError(#[from] reqwest::Error),
#[error("Failed to parse json due to {}",.0)]
InvalidJsonBody(#[from] serde_json::Error),
#[error("Request error playload : {0}")]
ErrorPayload(#[source] E),
}