1use std::marker::PhantomData;
13
14use http::Method;
15use indexmap::IndexMap;
16
17use crate::request::RequestBuilder;
18use crate::url_build::QueryValue;
19
20#[cfg(feature = "json")]
21use serde::de::DeserializeOwned;
22
23#[derive(Debug, Clone, Copy, Default)]
25pub struct NeedsParams;
26
27#[derive(Debug, Clone, Copy, Default)]
29pub struct Ready;
30
31pub trait Endpoint {
70 const METHOD: Method;
72 const PATH: &'static str;
74
75 #[cfg(feature = "json")]
76 type Response: DeserializeOwned;
78
79 #[cfg(not(feature = "json"))]
80 type Response;
82
83 type Params: EndpointParams + Default;
85 type Query: EndpointQuery + Default;
87}
88
89pub type ParamsBuilderState<P> = <P as EndpointParams>::BuilderState;
91
92pub trait EndpointParamsInitial<E: Endpoint>: EndpointParams {
94 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::BuilderState>;
95}
96
97impl<E: Endpoint> EndpointParamsInitial<E> for () {
98 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Ready> {
99 EndpointRequestBuilder::new_ready(client.request(E::METHOD, E::PATH))
100 }
101}
102
103impl<E: Endpoint, P: EndpointParams<BuilderState = NeedsParams>> EndpointParamsInitial<E> for P {
104 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, NeedsParams> {
105 EndpointRequestBuilder::new_needs_params(client.request(E::METHOD, E::PATH))
106 }
107}
108
109pub trait EndpointParams: Default + Sized {
111 type BuilderState;
113 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
115}
116
117impl EndpointParams for () {
118 type BuilderState = Ready;
119
120 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
121 builder
122 }
123}
124
125impl EndpointParams for std::collections::HashMap<String, String> {
126 type BuilderState = NeedsParams;
127
128 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
129 builder.params(self)
130 }
131}
132
133impl EndpointParams for Vec<(String, String)> {
134 type BuilderState = NeedsParams;
135
136 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
137 builder.params_iter(self)
138 }
139}
140
141pub trait EndpointQuery {
143 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
145}
146
147impl EndpointQuery for () {
148 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
149 builder
150 }
151}
152
153impl EndpointQuery for IndexMap<String, QueryValue> {
154 fn apply_query(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
155 builder.queries(self)
156 }
157}
158
159#[cfg(feature = "json")]
161pub fn apply_serialized_query<T: serde::Serialize>(
162 query: T,
163 builder: RequestBuilder<'_>,
164) -> RequestBuilder<'_> {
165 match crate::url_build::serialize_to_query_map(&query) {
166 Ok(map) => builder.queries(map),
167 Err(_) => builder,
168 }
169}
170
171pub struct EndpointRequestBuilder<'a, E: Endpoint, S> {
176 pub(crate) inner: RequestBuilder<'a>,
177 _marker: PhantomData<(E, S)>,
178}
179
180impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, NeedsParams> {
181 pub(crate) fn new_needs_params(inner: RequestBuilder<'a>) -> Self {
182 Self {
183 inner,
184 _marker: PhantomData,
185 }
186 }
187
188 pub fn params(self, params: E::Params) -> EndpointRequestBuilder<'a, E, Ready> {
190 EndpointRequestBuilder {
191 inner: params.apply_params(self.inner),
192 _marker: PhantomData,
193 }
194 }
195}
196
197impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, Ready> {
198 pub(crate) fn new_ready(inner: RequestBuilder<'a>) -> Self {
199 Self {
200 inner,
201 _marker: PhantomData,
202 }
203 }
204
205 pub fn query(self, query: E::Query) -> Self {
207 Self {
208 inner: query.apply_query(self.inner),
209 _marker: PhantomData,
210 }
211 }
212
213 pub fn header(self, key: impl AsRef<str>, value: impl AsRef<str>) -> crate::Result<Self> {
215 Ok(Self {
216 inner: self.inner.header(key, value)?,
217 _marker: PhantomData,
218 })
219 }
220
221 pub fn bearer_token(self, token: impl Into<String>) -> Self {
223 Self {
224 inner: self.inner.bearer_token(token),
225 _marker: PhantomData,
226 }
227 }
228
229 pub fn cancellation_token(self, token: crate::CancellationToken) -> Self {
231 Self {
232 inner: self.inner.cancellation_token(token),
233 _marker: PhantomData,
234 }
235 }
236
237 pub fn throw_on_error(self, throw: bool) -> Self {
239 Self {
240 inner: self.inner.throw_on_error(throw),
241 _marker: PhantomData,
242 }
243 }
244
245 pub async fn send(self) -> crate::Result<crate::Response> {
247 self.inner.send().await
248 }
249
250 #[cfg(feature = "json")]
252 pub async fn send_json(self) -> crate::Result<E::Response> {
253 self.inner.send().await?.json::<E::Response>().await
254 }
255
256 pub fn into_inner(self) -> RequestBuilder<'a> {
258 self.inner
259 }
260}
261
262#[macro_export]
278macro_rules! define_params {
279 (
280 $name:ident for $path:literal {
281 $( $field:ident : $ty:ty ),* $(,)?
282 }
283 ) => {
284 #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
285 pub struct $name {
286 $( pub $field: $ty, )*
287 }
288
289 impl $crate::EndpointParams for $name {
290 type BuilderState = $crate::NeedsParams;
291
292 fn apply_params(self, builder: $crate::RequestBuilder<'_>) -> $crate::RequestBuilder<'_> {
293 let builder = builder;
294 $(
295 let builder = builder.param(stringify!($field), self.$field);
296 )*
297 builder
298 }
299 }
300 };
301}
302
303#[cfg(feature = "json")]
305#[macro_export]
306macro_rules! impl_serde_endpoint_query {
307 ($ty:ty) => {
308 impl $crate::EndpointQuery for $ty {
309 fn apply_query(
310 self,
311 builder: $crate::RequestBuilder<'_>,
312 ) -> $crate::RequestBuilder<'_> {
313 $crate::endpoint::apply_serialized_query(self, builder)
314 }
315 }
316 };
317}
318
319#[macro_export]
338macro_rules! endpoint {
339 (
340 $name:ident,
341 $method:ident,
342 $path:literal,
343 Response = $response:ty
344 ) => {
345 $crate::endpoint!(
346 $name,
347 $method,
348 $path,
349 Response = $response,
350 Params = (),
351 Query = ()
352 );
353 };
354 (
355 $name:ident,
356 $method:ident,
357 $path:literal,
358 Response = $response:ty,
359 Params = $params:ty
360 ) => {
361 $crate::endpoint!(
362 $name,
363 $method,
364 $path,
365 Response = $response,
366 Params = $params,
367 Query = ()
368 );
369 };
370 (
371 $name:ident,
372 $method:ident,
373 $path:literal,
374 Response = $response:ty,
375 Query = $query:ty
376 ) => {
377 $crate::endpoint!(
378 $name,
379 $method,
380 $path,
381 Response = $response,
382 Params = (),
383 Query = $query
384 );
385 };
386 (
387 $name:ident,
388 $method:ident,
389 $path:literal,
390 Response = $response:ty,
391 Params = $params:ty,
392 Query = $query:ty
393 ) => {
394 pub struct $name;
395 impl $crate::Endpoint for $name {
396 const METHOD: http::Method = http::Method::$method;
397 const PATH: &'static str = $path;
398 type Response = $response;
399 type Params = $params;
400 type Query = $query;
401 }
402 };
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 define_params!(TestParams for "/items/:id" { id: u64 });
410
411 #[test]
412 fn params_builder_state_is_needs_params() {
413 fn assert_needs<T: EndpointParams<BuilderState = NeedsParams>>() {}
414 assert_needs::<TestParams>();
415 }
416
417 #[test]
418 fn unit_params_builder_state_is_ready() {
419 fn assert_ready<T: EndpointParams<BuilderState = Ready>>() {}
420 assert_ready::<()>();
421 }
422}