use std::marker::PhantomData;
use http::Method;
use indexmap::IndexMap;
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 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;
}
pub type ParamsBuilderState<P> = <P as EndpointParams>::BuilderState;
pub trait EndpointParamsInitial<E: Endpoint>: EndpointParams {
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::BuilderState>;
}
impl<E: Endpoint> EndpointParamsInitial<E> for () {
fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Ready> {
EndpointRequestBuilder::new_ready(client.request(E::METHOD, E::PATH))
}
}
impl<E: Endpoint, P: EndpointParams<BuilderState = NeedsParams>> EndpointParamsInitial<E> for P {
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<'_>) -> RequestBuilder<'_>;
}
impl EndpointQuery for () {
fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
builder
}
}
impl EndpointQuery for IndexMap<String, QueryValue> {
fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
builder.queries(self)
}
}
#[cfg(feature = "json")]
pub fn apply_serialized_query<T: serde::Serialize>(
query: T,
builder: RequestBuilder<'_>,
) -> RequestBuilder<'_> {
match crate::url_build::serialize_to_query_map(&query) {
Ok(map) => builder.queries(map),
Err(_) => builder,
}
}
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, Ready> {
EndpointRequestBuilder {
inner: params.apply_params(self.inner),
_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) -> Self {
Self {
inner: query.apply_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 async fn send(self) -> crate::Result<crate::Response> {
self.inner.send().await
}
#[cfg(feature = "json")]
pub async fn send_json(self) -> crate::Result<E::Response> {
self.inner.send().await?.json::<E::Response>().await
}
pub fn into_inner(self) -> RequestBuilder<'a> {
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::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;
}
};
}
#[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::<()>();
}
}