macro_rules! endpoint {
(
$(@gen $recv:ident,)?
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])?
) => {
endpoint! {
@full $(@gen $recv,)?
$(#[$meta])*
$method($($pn : $pt),*): $verb $path => $resp $([$key])? { params {} body {} }
}
};
(
$(@gen $recv:ident,)?
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])? {
params { $($qp:ident : $qt:ty),* $(,)? }
}
) => {
endpoint! {
@full $(@gen $recv,)?
$(#[$meta])*
$method($($pn : $pt),*): $verb $path => $resp $([$key])? {
params { $($qp : $qt),* }
body {}
}
}
};
(
$(@gen $recv:ident,)?
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])? {
body { $($bn:ident : $bt:ty),* $(,)? }
}
) => {
endpoint! {
@full $(@gen $recv,)?
$(#[$meta])*
$method($($pn : $pt),*): $verb $path => $resp $([$key])? {
params {}
body { $($bn : $bt),* }
}
}
};
(
$(@gen $recv:ident,)?
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])? {
params { $($qp:ident : $qt:ty),* $(,)? }
body { $($bn:ident : $bt:ty),* $(,)? }
}
) => {
endpoint! {
@full $(@gen $recv,)?
$(#[$meta])*
$method($($pn : $pt),*): $verb $path => $resp $([$key])? {
params { $($qp : $qt),* }
body { $($bn : $bt),* }
}
}
};
(
@full $(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])? {
params { $($qp:ident : $qt:ty),* $(,)? }
body { $($bn:ident : $bt:ty),* $(,)? }
}
) => {
endpoint!(@full @gen Client, $(#[$meta])* $method($($pn : $pt),*): $verb $path => $resp $([$key])? {
params { $($qp : $qt),* }
body { $($bn : $bt),* }
});
};
(
@full @gen $recv:ident, $(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): $verb:ident $path:literal => $resp:ty $([$key:ident])? {
params { $($qp:ident : $qt:ty),* $(,)? }
body { $($bn:ident : $bt:ty),* $(,)? }
}
) => {
::paste::paste! {
#[doc = concat!("builder for `", stringify!($verb), " ", $path, "`")]
#[derive(Clone)]
pub struct [<$method:camel Request>] {
client: $crate::$recv,
path: ::std::string::String,
pairs: ::std::vec::Vec<(&'static str, ::std::string::String)>,
_body: ::serde_json::Value,
}
impl $crate::$recv {
$(#[$meta])*
pub fn $method(&self, $($pn: $pt,)* $($bn: $bt),*) -> [<$method:camel Request>] {
#[allow(unused_mut)]
let mut pairs = ::std::vec::Vec::new();
let path = endpoint!(@args $path, pairs, $($pn),*);
let _body = ::serde_json::json!({ $(stringify!($bn): $bn),* });
[<$method:camel Request>] { client: self.clone(), path, pairs, _body }
}
}
impl [<$method:camel Request>] {
$(
pub fn $qp(mut self, value: $qt) -> Self {
self.pairs.push((stringify!($qp), $crate::ToParam::to_param(&value)));
self
}
)*
pub async fn send(self) -> $crate::Result<$resp> {
endpoint!(@send $verb self $resp $([$key])?)
}
}
}
endpoint!(@stream $verb $method $resp);
};
(@args $path:expr, $q:ident, $($pn:ident),* $(,)?) => {{
#[allow(unused_mut)]
let mut path = $path.to_string();
$(
let value = $crate::ToParam::to_param(&$pn);
let hole = concat!("{", stringify!($pn), "}");
if path.contains(hole) {
path = path.replace(hole, &value);
} else {
$q.push((stringify!($pn), value));
}
)*
path
}};
(@send GET $self:ident $resp:ty [$key:ident]) => {{
#[derive(::serde::Deserialize)]
struct Envelope { $key: $resp }
let envelope: Envelope = $self.client.get(&$self.path, &$self.pairs).await?;
Ok(envelope.$key)
}};
(@send GET $self:ident $resp:ty) => {
$self.client.get(&$self.path, &$self.pairs).await
};
(@send $verb:ident $self:ident $resp:ty) => {
::paste::paste! { $self.client.[<$verb:lower>](&$self.path, &$self.pairs, &$self._body).await }
};
(@stream GET $method:ident $resp:ty) => {
::paste::paste! {
impl [<$method:camel Request>] {
#[cfg(feature = "stream")]
pub fn into_stream(self) -> $crate::stream::PageStream<$resp> {
$crate::stream::page_stream(move |page: u32| {
let mut request = self.clone();
request.pairs.retain(|(key, _)| *key != "page");
request.pairs.push(("page", page.to_string()));
request.send()
})
}
}
}
};
(@stream $verb:ident $method:ident $resp:ty) => {};
(
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): GET $path:literal => $resp:ident {
base { $($(#[$bm:meta])* pub $bf:ident : $bt:ty),* $(,)? }
appends { $($(#[$am:meta])* $an:ident : $at:ty $(as $key:literal)?),* $(,)? }
}
) => {
endpoint! {
$(#[$meta])*
$method($($pn : $pt),*): GET $path => $resp $([$key])? {
params {}
base { $($(#[$bm])* pub $bf : $bt),* }
appends { $($(#[$am])* $an : $at $(as $key)?),* }
}
}
};
(
$(#[$meta:meta])*
$method:ident ($($pn:ident : $pt:ty),* $(,)?): GET $path:literal => $resp:ident {
params { $($qp:ident : $qt:ty),* $(,)? }
base { $($(#[$bm:meta])* pub $bf:ident : $bt:ty),* $(,)? }
appends { $($(#[$am:meta])* $an:ident : $at:ty $(as $key:literal)?),* $(,)? }
}
) => {
::paste::paste! {
$(#[$meta])*
#[derive(Debug, Clone)]
pub struct $resp<$([<A $an:camel>] = ()),*> {
$(
$(#[cfg_attr(any(), $bm)])*
pub $bf: $bt,
)*
$(
pub $an: [<A $an:camel>],
)*
}
#[doc = concat!("builder for `GET ", $path, "`; each `with_*` fills one append slot")]
pub struct [<$resp Request>]<$([<A $an:camel>] = ()),*> {
client: $crate::Client,
path: ::std::string::String,
pairs: ::std::vec::Vec<(&'static str, ::std::string::String)>,
_appends: ::std::marker::PhantomData<fn() -> ($([<A $an:camel>]),*)>,
}
impl $crate::Client {
$(#[$meta])*
pub fn $method(&self, $($pn: $pt),*) -> [<$resp Request>] {
#[allow(unused_mut)]
let mut pairs = ::std::vec::Vec::new();
let path = endpoint!(@args $path, pairs, $($pn),*);
[<$resp Request>] {
client: self.clone(),
path,
pairs,
_appends: ::std::marker::PhantomData,
}
}
}
impl<$([<A $an:camel>]),*> [<$resp Request>]<$([<A $an:camel>]),*> {
$(
pub fn $qp(mut self, value: $qt) -> Self {
self.pairs.push((stringify!($qp), $crate::ToParam::to_param(&value)));
self
}
)*
}
endpoint!(@withs [<$resp Request>] [$($pn,)*] [] [$($(#[$am])* $an : $at $(as $key)?,)*]);
impl<$([<A $an:camel>]: $crate::Append),*> [<$resp Request>]<$([<A $an:camel>]),*> {
pub async fn send(self) -> $crate::Result<$resp<$([<A $an:camel>]),*>> {
let mut pairs = self.pairs;
let mut append = ::std::string::String::new();
$(
if ![<A $an:camel>]::ABSENT {
append.push_str(endpoint!(@key $an $(as $key)?));
append.push(',');
}
)*
if !append.is_empty() {
append.pop();
pairs.push(("append_to_response", append));
}
$resp::<$([<A $an:camel>]),*>::fetch(&self.client, &self.path, &pairs).await
}
}
const _: () = {
#[derive(::serde::Deserialize)]
struct Shadow {
$($(#[$bm])* $bf: $bt,)*
$($an: ::std::option::Option<::serde_json::Value>,)*
}
impl<$([<A $an:camel>]: $crate::Append),*> $resp<$([<A $an:camel>]),*> {
pub(crate) async fn fetch(
client: &$crate::Client,
path: &str,
pairs: &[(&'static str, ::std::string::String)],
) -> $crate::Result<Self> {
let shadow: Shadow = client.get(path, pairs).await?;
Ok(Self {
$($bf: shadow.$bf,)*
$($an: [<A $an:camel>]::from_json(endpoint!(@key $an $(as $key)?), shadow.$an)?,)*
})
}
}
impl<'de> ::serde::Deserialize<'de> for $resp {
fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> ::std::result::Result<Self, D::Error> {
let shadow = Shadow::deserialize(d)?;
Ok(Self {
$($bf: shadow.$bf,)*
$($an: <() as $crate::Append>::from_json(endpoint!(@key $an $(as $key)?), shadow.$an)
.map_err(::serde::de::Error::custom)?,)*
})
}
}
};
}
};
(@key $an:ident) => { stringify!($an) };
(@key $an:ident as $key:literal) => { $key };
(@withs $req:ident [$($pn:ident,)*] [$($bn:ident : $bt:ty,)*] []) => {};
(@withs $req:ident [$($pn:ident,)*] [$($bn:ident : $bt:ty,)*] [$(#[$am:meta])* $an:ident : $at:ty $(as $key:literal)?, $($(#[$rm:meta])* $rest:ident : $rt:ty $(as $rkey:literal)?,)*]) => {
::paste::paste! {
impl<$([<A $bn:camel>],)* $([<A $rest:camel>],)*> $req<$([<A $bn:camel>],)* (), $([<A $rest:camel>],)*> {
$(#[$am])*
#[doc = concat!("append `", endpoint!(@key $an $(as $key)?), "` to the response")]
pub fn [<with_ $an>](self) -> $req<$([<A $bn:camel>],)* $at, $([<A $rest:camel>],)*> {
$req {
client: self.client,
path: self.path,
pairs: self.pairs,
_appends: ::std::marker::PhantomData,
}
}
}
}
endpoint!(@withs $req [$($pn,)*] [$($bn : $bt,)* $an : $at,] [$($(#[$rm])* $rest : $rt $(as $rkey)?,)*]);
};
}