1use std::marker::PhantomData;
2
3use http::Method;
4use indexmap::IndexMap;
5
6use crate::request::RequestBuilder;
7use crate::url_build::QueryValue;
8
9#[cfg(feature = "json")]
10use serde::de::DeserializeOwned;
11
12pub trait Endpoint {
14 const METHOD: Method;
15 const PATH: &'static str;
16
17 #[cfg(feature = "json")]
18 type Response: DeserializeOwned;
19
20 #[cfg(not(feature = "json"))]
21 type Response;
22
23 type Params: EndpointParams + Default;
24 type Query: EndpointQuery + Default;
25}
26
27pub trait EndpointParams {
29 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
30}
31
32impl EndpointParams for () {
33 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
34 builder
35 }
36}
37
38impl EndpointParams for std::collections::HashMap<String, String> {
39 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
40 builder.params(self)
41 }
42}
43
44impl EndpointParams for Vec<(String, String)> {
45 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
46 builder.params_iter(self)
47 }
48}
49
50pub trait EndpointQuery {
52 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
53}
54
55impl EndpointQuery for () {
56 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
57 builder
58 }
59}
60
61impl EndpointQuery for IndexMap<String, QueryValue> {
62 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
63 builder.queries(self)
64 }
65}
66
67pub struct EndpointRequestBuilder<'a, E: Endpoint> {
69 pub(crate) inner: RequestBuilder<'a>,
70 _marker: PhantomData<E>,
71}
72
73impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E> {
74 pub(crate) fn new(inner: RequestBuilder<'a>) -> Self {
75 Self {
76 inner,
77 _marker: PhantomData,
78 }
79 }
80
81 pub fn params(self, params: E::Params) -> Self {
82 Self {
83 inner: params.apply_params(self.inner),
84 _marker: PhantomData,
85 }
86 }
87
88 pub fn query(self, query: E::Query) -> Self {
89 Self {
90 inner: query.apply_query(self.inner),
91 _marker: PhantomData,
92 }
93 }
94
95 pub fn param(self, key: impl Into<String>, value: impl ToString) -> Self {
96 Self {
97 inner: self.inner.param(key, value),
98 _marker: PhantomData,
99 }
100 }
101
102 pub fn query_pair(self, key: impl Into<String>, value: impl ToString) -> Self {
103 Self {
104 inner: self.inner.query(key, value),
105 _marker: PhantomData,
106 }
107 }
108
109 pub fn header(self, key: impl AsRef<str>, value: impl AsRef<str>) -> crate::Result<Self> {
110 Ok(Self {
111 inner: self.inner.header(key, value)?,
112 _marker: PhantomData,
113 })
114 }
115
116 pub fn bearer_token(self, token: impl Into<String>) -> Self {
117 Self {
118 inner: self.inner.bearer_token(token),
119 _marker: PhantomData,
120 }
121 }
122
123 pub fn cancellation_token(self, token: crate::CancellationToken) -> Self {
124 Self {
125 inner: self.inner.cancellation_token(token),
126 _marker: PhantomData,
127 }
128 }
129
130 pub fn throw_on_error(self, throw: bool) -> Self {
131 Self {
132 inner: self.inner.throw_on_error(throw),
133 _marker: PhantomData,
134 }
135 }
136
137 pub async fn send(self) -> crate::Result<crate::Response> {
138 self.inner.send().await
139 }
140
141 #[cfg(feature = "json")]
142 pub async fn send_json(self) -> crate::Result<E::Response> {
143 self.inner.send().await?.json::<E::Response>().await
144 }
145
146 pub fn into_inner(self) -> RequestBuilder<'a> {
147 self.inner
148 }
149}
150
151#[macro_export]
153macro_rules! endpoint {
154 (
155 $name:ident,
156 $method:ident,
157 $path:literal,
158 Response = $response:ty
159 ) => {
160 pub struct $name;
161 impl $crate::Endpoint for $name {
162 const METHOD: http::Method = http::Method::$method;
163 const PATH: &'static str = $path;
164 type Response = $response;
165 type Params = ();
166 type Query = ();
167 }
168 };
169}