use crate::{request::ApiRequest, Backend};
use serde::{Serialize, Serializer};
use std::time::Duration;
#[cfg_attr(feature = "with-builder", derive(TypedBuilder))]
#[derive(Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub struct GlobalOptions {
#[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
pub offline: Option<bool>,
#[cfg_attr(feature = "with-builder", builder(default, setter(strip_option)))]
#[serde(serialize_with = "duration_as_secs_ns")]
pub timeout: Option<Duration>,
}
fn duration_as_secs_ns<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match duration {
Some(duration) => serializer.serialize_str(&format!(
"{}s{}ns",
duration.as_secs(),
duration.subsec_nanos(),
)),
None => serializer.serialize_none(),
}
}
pub struct BackendWithGlobalOptions<Back: Backend> {
pub(crate) backend: Back,
pub(crate) options: GlobalOptions,
}
#[derive(Serialize)]
struct OptCombiner<'a, Req> {
#[serde(flatten)]
global: &'a GlobalOptions,
#[serde(flatten)]
request: &'a Req,
}
impl<Back: Backend> BackendWithGlobalOptions<Back> {
pub fn into_inner(self) -> Back {
self.backend
}
pub fn new(backend: Back, options: GlobalOptions) -> Self {
Self { backend, options }
}
fn combine<'a, Req: ApiRequest>(&'a self, req: &'a Req) -> OptCombiner<'a, Req> {
OptCombiner {
global: &self.options,
request: &req,
}
}
}
impl<'a, Req: ApiRequest> ApiRequest for OptCombiner<'a, Req> {
const PATH: &'static str = <Req as ApiRequest>::PATH;
const METHOD: http::Method = http::Method::POST;
}
#[async_trait::async_trait(?Send)]
impl<Back: Backend> Backend for BackendWithGlobalOptions<Back> {
type HttpRequest = Back::HttpRequest;
type HttpResponse = Back::HttpResponse;
type Error = Back::Error;
fn build_base_request<Req>(
&self,
req: &Req,
form: Option<common_multipart_rfc7578::client::multipart::Form<'static>>,
) -> Result<Self::HttpRequest, Self::Error>
where
Req: ApiRequest,
{
self.backend.build_base_request(&self.combine(req), form)
}
fn get_header(
res: &Self::HttpResponse,
key: http::header::HeaderName,
) -> Option<&http::HeaderValue> {
Back::get_header(res, key)
}
async fn request_raw<Req>(
&self,
req: Req,
form: Option<common_multipart_rfc7578::client::multipart::Form<'static>>,
) -> Result<(http::StatusCode, bytes::Bytes), Self::Error>
where
Req: ApiRequest + Serialize,
{
self.backend.request_raw(self.combine(&req), form).await
}
fn response_to_byte_stream(
res: Self::HttpResponse,
) -> Box<dyn futures::Stream<Item = Result<bytes::Bytes, Self::Error>> + Unpin> {
Back::response_to_byte_stream(res)
}
fn request_stream<Res, F, OutStream>(
&self,
req: Self::HttpRequest,
process: F,
) -> Box<dyn futures::Stream<Item = Result<Res, Self::Error>> + Unpin>
where
OutStream: futures::Stream<Item = Result<Res, Self::Error>> + Unpin,
F: 'static + Fn(Self::HttpResponse) -> OutStream,
{
self.backend.request_stream(req, process)
}
}