use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::time::Duration;
use bytes::Bytes;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::{Method, StatusCode};
use serde::de::DeserializeOwned;
use super::Client;
use super::builder::validate_timeout;
use super::transport::Resolved;
use crate::error::{Error, Result};
use crate::retry::RetryPolicy;
pub(crate) struct DecodeFailure {
pub(crate) message: String,
pub(crate) source: Option<serde_json::Error>,
}
pub(crate) type Parser<T> = fn(&[u8]) -> std::result::Result<T, DecodeFailure>;
pub(crate) fn parse_json<T: DeserializeOwned>(
body: &[u8],
) -> std::result::Result<T, DecodeFailure> {
serde_json::from_slice(body).map_err(|err| DecodeFailure {
message: format!("Could not parse the response body: {err}"),
source: Some(err),
})
}
#[must_use = "a call does nothing until it is awaited"]
pub struct Call<T> {
client: Client,
method: Method,
path: String,
body: Result<Option<Bytes>>,
timeout: Option<Duration>,
total_timeout: Option<Option<Duration>>,
retry: Option<RetryPolicy>,
max_retries: Option<u32>,
headers: HeaderMap,
parse: Parser<T>,
}
#[derive(Debug, Clone)]
pub struct WithResponse<T> {
pub data: T,
pub status: StatusCode,
pub headers: HeaderMap,
pub request_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RawResponse {
pub status: StatusCode,
pub headers: HeaderMap,
pub body: Bytes,
pub request_id: Option<String>,
}
impl<T> Call<T> {
pub(crate) fn new(
client: Client,
method: Method,
path: impl Into<String>,
body: Result<Option<Vec<u8>>>,
parse: Parser<T>,
) -> Self {
Self {
client,
method,
path: path.into(),
body: body.map(|body| body.map(Bytes::from)),
timeout: None,
total_timeout: None,
retry: None,
max_retries: None,
headers: HeaderMap::new(),
parse,
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn total_timeout(mut self, total_timeout: impl Into<Option<Duration>>) -> Self {
self.total_timeout = Some(total_timeout.into());
self
}
pub fn retry(mut self, retry: RetryPolicy) -> Self {
self.retry = Some(retry);
self
}
pub fn max_retries(mut self, max_retries: u32) -> Self {
self.max_retries = Some(max_retries);
self
}
pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self {
self.headers.insert(name, value);
self
}
pub fn headers(mut self, headers: HeaderMap) -> Self {
self.headers.extend(headers);
self
}
pub async fn raw(self) -> Result<RawResponse> {
let client = self.client.clone();
let resolved = self.resolve()?;
client.execute(resolved).await
}
pub async fn with_response(self) -> Result<WithResponse<T>> {
let parse = self.parse;
let raw = self.raw().await?;
match parse(&raw.body) {
Ok(data) => Ok(WithResponse {
data,
status: raw.status,
headers: raw.headers,
request_id: raw.request_id,
}),
Err(failure) => Err(Error::Decode {
message: failure.message,
request_id: raw.request_id,
source: failure.source,
}),
}
}
fn resolve(self) -> Result<Resolved> {
let body = self.body?;
let timeout = validate_timeout(self.timeout.unwrap_or(self.client.timeout()))?;
let total_timeout = match self.total_timeout.unwrap_or(self.client.total_timeout()) {
Some(total) => Some(validate_timeout(total).map_err(|_| {
Error::Config("`total_timeout` must be a positive duration, got 0.".into())
})?),
None => None,
};
let mut retry = self.retry.unwrap_or_else(|| self.client.retry().clone());
if let Some(max_retries) = self.max_retries {
retry.max_retries = max_retries;
}
retry.validate()?;
let mut headers = self.client.default_headers().clone();
headers.extend(self.headers);
Ok(Resolved {
method: self.method,
path: self.path,
body,
headers,
timeout,
retry,
total_timeout,
})
}
}
impl<T: Send + 'static> IntoFuture for Call<T> {
type Output = Result<T>;
type IntoFuture = Pin<Box<dyn Future<Output = Result<T>> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.with_response().await.map(|response| response.data) })
}
}