use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::pin::Pin;
use reqwest::header::HeaderMap;
use reqwest::Method;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::Value;
use crate::client::{content_type, Call, Client, RequestOptions};
use crate::error::Error;
use crate::query::Query;
use crate::Record;
macro_rules! request_settings {
() => {
pub fn query(mut self, query: impl $crate::query::IntoQuery) -> Self {
match query.into_query() {
Ok(map) => self.query.merge(map),
Err(e) => {
self.error.get_or_insert(e);
}
}
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.opts.headers.push((name.into(), value.into()));
self
}
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.opts.timeout = Some(timeout);
self
}
pub fn max_retries(mut self, n: u32) -> Self {
self.opts.max_retries = Some(n);
self
}
pub fn no_retry(mut self) -> Self {
self.opts.no_retry = true;
self
}
pub fn retry_status_codes(mut self, codes: impl IntoIterator<Item = u16>) -> Self {
self.opts.retry_status_codes.extend(codes);
self
}
pub fn on_retry(mut self, hook: impl Fn(u32, Option<u16>) + Send + Sync + 'static) -> Self {
self.opts.on_retry = Some(std::sync::Arc::new(hook));
self
}
pub fn on_rate_limited(mut self, hook: impl Fn(u64, u32) + Send + Sync + 'static) -> Self {
self.opts.on_rate_limited = Some(std::sync::Arc::new(hook));
self
}
pub fn options(mut self, options: $crate::RequestOptions) -> Self {
self.opts = options;
self
}
};
}
pub(crate) use request_settings;
#[must_use = "a request does nothing until it is awaited"]
pub struct Request<T = Record> {
client: Client,
method: Method,
path: String,
query: Query,
body: Option<Value>,
opts: RequestOptions,
error: Option<Error>,
_marker: PhantomData<fn() -> T>,
}
impl<T> std::fmt::Debug for Request<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("method", &self.method)
.field("path", &self.path)
.field("query", &self.query)
.field("body", &self.body)
.field("options", &self.opts)
.finish()
}
}
impl<T> Request<T> {
pub(crate) fn new(client: Client, method: Method, path: String) -> Self {
Self {
client,
method,
path,
query: Query::new(),
body: None,
opts: RequestOptions::default(),
error: None,
_marker: PhantomData,
}
}
request_settings!();
pub fn body(mut self, body: impl Serialize) -> Self {
match serde_json::to_value(body) {
Ok(Value::Null) => self.body = None,
Ok(v) => self.body = Some(v),
Err(e) => {
self.error
.get_or_insert(Error::InvalidRequest(format!("cannot serialise body: {e}")));
}
}
self
}
pub fn typed<U>(self) -> Request<U> {
Request {
client: self.client,
method: self.method,
path: self.path,
query: self.query,
body: self.body,
opts: self.opts,
error: self.error,
_marker: PhantomData,
}
}
pub(crate) fn query_ref(&mut self) -> &mut Query {
&mut self.query
}
async fn execute(self) -> Result<crate::client::Response, Error> {
if let Some(e) = self.error {
return Err(e);
}
let call = Call::new(self.method, self.path, &self.query, self.body);
self.client.execute(&call, &self.opts).await
}
pub async fn raw(self) -> Result<RawResponse, Error> {
let resp = self.execute().await?;
Ok(RawResponse {
status: resp.status.as_u16(),
headers: resp.headers,
body: resp.body,
})
}
}
impl<T: DeserializeOwned> Request<T> {
pub async fn send(self) -> Result<T, Error> {
self.execute().await?.decode()
}
}
impl<T: DeserializeOwned + Send + 'static> IntoFuture for Request<T> {
type Output = Result<T, Error>;
type IntoFuture = Pin<Box<dyn Future<Output = Result<T, Error>> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.send())
}
}
#[derive(Debug, Clone)]
pub struct RawResponse {
pub status: u16,
pub headers: HeaderMap,
pub body: Vec<u8>,
}
impl RawResponse {
pub fn content_type(&self) -> Option<&str> {
content_type(&self.headers)
}
pub fn text(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
pub fn json<U: DeserializeOwned>(&self) -> Result<U, Error> {
serde_json::from_slice(&self.body).map_err(|e| Error::decode(e.to_string(), Some(e)))
}
}