use reqwest::{Client, RequestBuilder, Response};
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::{FromApiState, Queryable, RequestMethod};
#[derive(Debug, Error)]
pub enum ApiError {
#[error("An error happened with serialization: {0}")]
Serde(serde_json::Error),
#[error("An error happened with the request itself: {0}")]
Reqwest(reqwest::Error),
}
impl From<serde_json::Error> for ApiError {
fn from(err: serde_json::Error) -> Self { Self::Serde(err) }
}
impl From<reqwest::Error> for ApiError {
fn from(err: reqwest::Error) -> Self { Self::Reqwest(err) }
}
#[async_trait::async_trait]
pub trait ApiClient<State> {
fn state(&self) -> &State;
fn client(&self) -> &Client;
async fn before_request(
&self, req: RequestBuilder,
) -> Result<RequestBuilder, ApiError> {
Ok(req)
}
async fn handle_response<ReturnType, FromState, QueryableType>(
&self, queryable: QueryableType, response: Response,
) -> Result<ReturnType, ApiError>
where
ReturnType: DeserializeOwned,
FromState: FromApiState<State>,
QueryableType: Queryable<FromState, ReturnType> + Send + Sync,
{
let response = response.error_for_status()?;
let val = response.bytes().await?;
Ok(queryable.deserialize(&val)?)
}
async fn query<ReturnType, FromState, QueryableType>(
&self, queryable: QueryableType,
) -> Result<ReturnType, ApiError>
where
ReturnType: DeserializeOwned,
FromState: FromApiState<State>,
QueryableType: Queryable<FromState, ReturnType> + Send + Sync,
{
let request = Self::build_request(
self.client(),
FromState::from_state(self.state()),
&queryable,
)?;
let request = self.before_request(request).await?;
let response = request.send().await?;
self.handle_response(queryable, response).await
}
fn build_request<ReturnType, FromState, QueryableType>(
http: &Client, api_state: &FromState, queryable: &QueryableType,
) -> Result<RequestBuilder, ApiError>
where
ReturnType: DeserializeOwned,
FromState: FromApiState<State>,
QueryableType: Queryable<FromState, ReturnType> + Send + Sync,
{
let mut request = http.request(
match queryable.method(api_state) {
RequestMethod::Get => reqwest::Method::GET,
RequestMethod::Head => reqwest::Method::HEAD,
RequestMethod::Patch => reqwest::Method::PATCH,
RequestMethod::Post => reqwest::Method::POST,
RequestMethod::Put => reqwest::Method::PUT,
RequestMethod::Delete => reqwest::Method::DELETE,
},
queryable.url(api_state),
);
if let Some(body) = queryable.body(api_state) {
request = request.body(body?).header("Content-Type", "application/json");
}
Ok(request)
}
}