use std::borrow::Cow;
use async_trait::async_trait;
use bytes::Bytes;
use http::{self, header, request::Builder, Method, Request, Response};
use serde::de::DeserializeOwned;
use crate::{
auth::Scope, ApiError, AsyncClient, AsyncCustomQuery, BodyError, Client, CustomQuery,
RestClient,
};
pub trait Endpoint {
type AccessControl: Scope;
fn method(&self) -> Method;
fn endpoint(&self) -> Cow<'static, str>;
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
Ok(None)
}
}
impl<E, T, C> CustomQuery<T, C> for E
where
E: Endpoint,
T: DeserializeOwned,
C: Client,
E::AccessControl: From<C::AccessLevel>,
{
fn query_custom(&self, client: &C) -> Result<T, ApiError<C::Error>> {
let (req, data) = build_request_with_body(self, client)?;
let rsp = client.rest(req, data)?;
process_response(&rsp, serde_json::from_value)
}
}
#[async_trait]
impl<E, T, C> AsyncCustomQuery<T, C> for E
where
E: Endpoint + Sync,
T: DeserializeOwned + 'static,
C: AsyncClient + Sync,
E::AccessControl: From<C::AccessLevel>,
{
async fn query_custom_async(&self, client: &C) -> Result<T, ApiError<C::Error>> {
let (req, data) = build_request_with_body(self, client)?;
let rsp = client.rest_async(req, data).await?;
process_response(&rsp, serde_json::from_value)
}
}
pub fn process_response<T, E, F>(r: &Response<Bytes>, mapper: F) -> Result<T, ApiError<E>>
where
T: DeserializeOwned,
E: std::error::Error + Send + Sync + 'static,
F: FnOnce(serde_json::Value) -> Result<T, serde_json::Error>,
{
let fill = Bytes::from("null");
let body = if r.body().is_empty() { &fill } else { r.body() };
let result_v = serde_json::from_slice(body);
if r.status().is_success() {
mapper(result_v?).map_err(ApiError::data_type::<T>)
} else {
let v = result_v.map_err(|_| ApiError::server_error(r.status(), r.body()))?;
Err(ApiError::from_traduora(v))
}
}
pub fn build_request_with_body<E, C>(
endpoint: &E,
client: &C,
) -> Result<(Builder, Vec<u8>), ApiError<C::Error>>
where
E: Endpoint,
C: RestClient,
{
let uri = client
.rest_endpoint(&endpoint.endpoint())?
.as_str()
.parse::<http::Uri>()
.expect("failed to parse a url::Url as an http::Uri");
let req = Request::builder().method(endpoint.method()).uri(uri);
Ok(match endpoint.body()? {
Some((mime, body)) => (req.header(header::CONTENT_TYPE, mime), body),
None => (req, Vec::new()),
})
}