use std::marker::PhantomData;
use std::ops::Deref;
use http::Method;
use indexmap::IndexMap;
use crate::error::Error;
use crate::request::RequestBuilder;
use crate::url_build::QueryValue;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
#[derive(Debug, Clone, Copy, Default)]
pub struct NeedsParams;
#[derive(Debug, Clone, Copy, Default)]
pub struct NeedsBody;
#[derive(Debug, Clone, Copy, Default)]
pub struct Ready;
pub trait Endpoint {
const METHOD: Method;
const PATH: &'static str;
#[cfg(feature = "json")]
type Response: DeserializeOwned;
#[cfg(not(feature = "json"))]
type Response;
type Params: EndpointParams + Default;
type Query: EndpointQuery + Default;
type Body: EndpointBody + Default;
type Headers: EndpointHeaders + Default;
}
pub trait EndpointBody: Default + Sized {
type ParamsNext: Default;
type CallInitial: Default;
fn apply_body(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
}
impl EndpointBody for () {
type ParamsNext = Ready;
type CallInitial = Ready;
fn apply_body(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
Ok(builder)
}
}
pub trait DefaultParamsInitial<E: Endpoint> {
fn initial(
client: &crate::Client,
) -> EndpointRequestBuilder<'_, E, <E::Body as EndpointBody>::CallInitial>;
}
impl<E: Endpoint> DefaultParamsInitial<E> for ()
where
E::Params: EndpointParams<BuilderState = Ready>,
E::Body: EndpointBody<CallInitial = Ready>,
{
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Ready> {
EndpointRequestBuilder::new_ready(client.request(E::METHOD, E::PATH))
}
}
pub trait EndpointHeaders: Default + Sized {
fn apply_headers(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
}
impl EndpointHeaders for () {
fn apply_headers(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
Ok(builder)
}
}
pub type ParamsBuilderState<P> = <P as EndpointParams>::BuilderState;
pub trait EndpointParamsInitial<E: Endpoint>: EndpointParams {
type State;
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::State>;
}
impl<E: Endpoint> EndpointParamsInitial<E> for ()
where
(): DefaultParamsInitial<E>,
{
type State = <E::Body as EndpointBody>::CallInitial;
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::State> {
<() as DefaultParamsInitial<E>>::initial(client)
}
}
impl<E: Endpoint, P: EndpointParams<BuilderState = NeedsParams>> EndpointParamsInitial<E> for P {
type State = NeedsParams;
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, NeedsParams> {
EndpointRequestBuilder::new_needs_params(client.request(E::METHOD, E::PATH))
}
}
pub trait EndpointParams: Default + Sized {
type BuilderState;
fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
}
impl EndpointParams for () {
type BuilderState = Ready;
fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
builder
}
}
impl EndpointParams for std::collections::HashMap<String, String> {
type BuilderState = NeedsParams;
fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
builder.params(self)
}
}
impl EndpointParams for Vec<(String, String)> {
type BuilderState = NeedsParams;
fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
builder.params_iter(self)
}
}
pub trait EndpointQuery {
fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
}
impl EndpointQuery for () {
fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
Ok(builder)
}
}
impl EndpointQuery for IndexMap<String, QueryValue> {
fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
Ok(builder.queries(self))
}
}
#[cfg(feature = "json")]
pub fn apply_serialized_query<T: serde::Serialize>(
query: T,
builder: RequestBuilder<'_>,
) -> crate::Result<RequestBuilder<'_>> {
let map = crate::url_build::serialize_to_query_map(&query).map_err(|e| match e {
Error::Other(msg) => Error::query_serialize(msg),
other => other,
})?;
Ok(builder.queries(map))
}
#[cfg(all(feature = "json", feature = "validate"))]
pub fn apply_serialized_query_validated<T>(
query: T,
builder: RequestBuilder<'_>,
) -> crate::Result<RequestBuilder<'_>>
where
T: serde::Serialize + garde::Validate,
T::Context: Default,
{
garde::Validate::validate(&query).map_err(|report: garde::Report| {
Error::RequestValidation {
message: report.to_string(),
}
})?;
apply_serialized_query(query, builder)
}
#[must_use = "endpoint builders do nothing until you call `.send().await`, `.send_json().await`, or similar"]
pub struct EndpointRequestBuilder<'a, E: Endpoint, S> {
pub(crate) inner: RequestBuilder<'a>,
_marker: PhantomData<(E, S)>,
}
impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, NeedsParams> {
pub(crate) fn new_needs_params(inner: RequestBuilder<'a>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
pub fn params(
self,
params: E::Params,
) -> EndpointRequestBuilder<'a, E, ParamsBuilderStateAfter<E>>
where
E::Body: EndpointBody,
{
EndpointRequestBuilder {
inner: params.apply_params(self.inner),
_marker: PhantomData,
}
}
}
pub type ParamsBuilderStateAfter<E> = <<E as Endpoint>::Body as EndpointBody>::ParamsNext;
impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, NeedsBody> {
pub fn new_needs_body(inner: RequestBuilder<'a>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
#[cfg(feature = "json")]
pub fn json<T: serde::Serialize>(
self,
body: &T,
) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>> {
Ok(EndpointRequestBuilder {
inner: self.inner.json(body)?,
_marker: PhantomData,
})
}
#[cfg(feature = "validate")]
pub fn json_validated<T>(self, body: &T) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>>
where
T: serde::Serialize + garde::Validate,
T::Context: Default,
{
Ok(EndpointRequestBuilder {
inner: self.inner.json_validated(body)?,
_marker: PhantomData,
})
}
pub fn with_body(self, body: E::Body) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>> {
Ok(EndpointRequestBuilder {
inner: body.apply_body(self.inner)?,
_marker: PhantomData,
})
}
pub fn body(self, body: impl Into<bytes::Bytes>) -> EndpointRequestBuilder<'a, E, Ready> {
EndpointRequestBuilder {
inner: self.inner.body(body),
_marker: PhantomData,
}
}
}
impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, Ready> {
pub(crate) fn new_ready(inner: RequestBuilder<'a>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
pub fn query(self, query: E::Query) -> crate::Result<Self> {
Ok(Self {
inner: query.apply_query(self.inner)?,
_marker: PhantomData,
})
}
#[cfg(all(feature = "json", feature = "validate"))]
pub fn query_validated(self, query: E::Query) -> crate::Result<Self>
where
E::Query: serde::Serialize + garde::Validate,
<E::Query as garde::Validate>::Context: Default,
{
Ok(Self {
inner: apply_serialized_query_validated(query, self.inner)?,
_marker: PhantomData,
})
}
pub fn header(self, key: impl AsRef<str>, value: impl AsRef<str>) -> crate::Result<Self> {
Ok(Self {
inner: self.inner.header(key, value)?,
_marker: PhantomData,
})
}
pub fn bearer_token(self, token: impl Into<String>) -> Self {
Self {
inner: self.inner.bearer_token(token),
_marker: PhantomData,
}
}
pub fn cancellation_token(self, token: crate::CancellationToken) -> Self {
Self {
inner: self.inner.cancellation_token(token),
_marker: PhantomData,
}
}
pub fn throw_on_error(self, throw: bool) -> Self {
Self {
inner: self.inner.throw_on_error(throw),
_marker: PhantomData,
}
}
pub fn base_url(self, base_url: impl AsRef<str>) -> crate::Result<Self> {
Ok(Self {
inner: self.inner.base_url(base_url)?,
_marker: PhantomData,
})
}
pub fn retry(self, policy: crate::RetryPolicy) -> Self {
Self {
inner: self.inner.retry(policy),
_marker: PhantomData,
}
}
pub fn timeout(self, timeout: std::time::Duration) -> Self {
Self {
inner: self.inner.timeout(timeout),
_marker: PhantomData,
}
}
pub async fn send_stream(self) -> crate::Result<crate::StreamingResponse> {
self.inner.send_stream().await
}
pub fn max_response_bytes(self, limit: u64) -> Self {
Self {
inner: self.inner.max_response_bytes(limit),
_marker: PhantomData,
}
}
#[cfg(feature = "json")]
pub fn json<T: serde::Serialize>(self, body: &T) -> crate::Result<Self> {
Ok(Self {
inner: self.inner.json(body)?,
_marker: PhantomData,
})
}
#[cfg(feature = "validate")]
pub fn json_validated<T>(self, body: &T) -> crate::Result<Self>
where
T: serde::Serialize + garde::Validate,
T::Context: Default,
{
Ok(Self {
inner: self.inner.json_validated(body)?,
_marker: PhantomData,
})
}
pub fn body(self, body: impl Into<bytes::Bytes>) -> Self {
Self {
inner: self.inner.body(body),
_marker: PhantomData,
}
}
pub async fn send(self) -> crate::Result<crate::Response> {
self.inner.send().await
}
pub fn with_body(self, body: E::Body) -> crate::Result<Self> {
Ok(Self {
inner: body.apply_body(self.inner)?,
_marker: PhantomData,
})
}
pub fn with_headers(self, headers: E::Headers) -> crate::Result<Self> {
Ok(Self {
inner: headers.apply_headers(self.inner)?,
_marker: PhantomData,
})
}
#[cfg(feature = "validate")]
pub fn with_headers_validated(self, headers: E::Headers) -> crate::Result<Self>
where
E::Headers: garde::Validate,
<E::Headers as garde::Validate>::Context: Default,
{
garde::Validate::validate(&headers).map_err(|report: garde::Report| {
Error::RequestValidation {
message: report.to_string(),
}
})?;
self.with_headers(headers)
}
#[cfg(feature = "json")]
pub async fn send_json(self) -> crate::Result<E::Response> {
self.inner.send().await?.json::<E::Response>().await
}
#[cfg(feature = "json")]
pub async fn send_api<T, ErrBody>(self) -> crate::Result<std::result::Result<T, ErrBody>>
where
T: serde::de::DeserializeOwned,
ErrBody: serde::de::DeserializeOwned,
{
crate::api_response::into_api_result(self.inner.send().await?)
}
}
impl<'a, E: Endpoint> Deref for EndpointRequestBuilder<'a, E, Ready> {
type Target = RequestBuilder<'a>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[macro_export]
macro_rules! define_params {
(
$name:ident for $path:literal {
$( $field:ident : $ty:ty ),* $(,)?
}
) => {
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct $name {
$( pub $field: $ty, )*
}
impl $crate::EndpointParams for $name {
type BuilderState = $crate::NeedsParams;
fn apply_params(self, builder: $crate::RequestBuilder<'_>) -> $crate::RequestBuilder<'_> {
let builder = builder;
$(
let builder = builder.param(stringify!($field), self.$field);
)*
builder
}
}
};
}
#[cfg(feature = "json")]
#[macro_export]
macro_rules! impl_serde_endpoint_query {
($ty:ty) => {
impl $crate::EndpointQuery for $ty {
fn apply_query(
self,
builder: $crate::RequestBuilder<'_>,
) -> $crate::Result<$crate::RequestBuilder<'_>> {
$crate::endpoint::apply_serialized_query(self, builder)
}
}
};
}
#[macro_export]
macro_rules! endpoint {
(
$name:ident,
$method:ident,
$path:literal,
Response = $response:ty
) => {
$crate::endpoint!(
$name,
$method,
$path,
Response = $response,
Params = (),
Query = ()
);
};
(
$name:ident,
$method:ident,
$path:literal,
Response = $response:ty,
Params = $params:ty
) => {
$crate::endpoint!(
$name,
$method,
$path,
Response = $response,
Params = $params,
Query = ()
);
};
(
$name:ident,
$method:ident,
$path:literal,
Response = $response:ty,
Query = $query:ty
) => {
$crate::endpoint!(
$name,
$method,
$path,
Response = $response,
Params = (),
Query = $query
);
};
(
$name:ident,
$method:ident,
$path:literal,
Response = $response:ty,
Params = $params:ty,
Query = $query:ty
) => {
pub struct $name;
impl $crate::Endpoint for $name {
const METHOD: http::Method = http::Method::$method;
const PATH: &'static str = $path;
type Response = $response;
type Params = $params;
type Query = $query;
type Body = ();
type Headers = ();
}
};
}
#[cfg(test)]
mod tests {
use super::*;
define_params!(TestParams for "/items/:id" { id: u64 });
#[test]
fn params_builder_state_is_needs_params() {
fn assert_needs<T: EndpointParams<BuilderState = NeedsParams>>() {}
assert_needs::<TestParams>();
}
#[test]
fn unit_params_builder_state_is_ready() {
fn assert_ready<T: EndpointParams<BuilderState = Ready>>() {}
assert_ready::<()>();
}
}