1use std::marker::PhantomData;
13use std::ops::Deref;
14
15use http::Method;
16use indexmap::IndexMap;
17
18use crate::error::Error;
19use crate::request::RequestBuilder;
20use crate::url_build::QueryValue;
21
22#[cfg(feature = "json")]
23use serde::de::DeserializeOwned;
24
25#[derive(Debug, Clone, Copy, Default)]
27pub struct NeedsParams;
28
29#[derive(Debug, Clone, Copy, Default)]
31pub struct NeedsBody;
32
33#[derive(Debug, Clone, Copy, Default)]
35pub struct Ready;
36
37pub trait Endpoint {
78 const METHOD: Method;
80 const PATH: &'static str;
82
83 #[cfg(feature = "json")]
84 type Response: DeserializeOwned;
86
87 #[cfg(not(feature = "json"))]
88 type Response;
90
91 type Params: EndpointParams + Default;
93 type Query: EndpointQuery + Default;
95
96 type Body: EndpointBody + Default;
98
99 type Headers: EndpointHeaders + Default;
101}
102
103pub trait EndpointBody: Default + Sized {
105 type ParamsNext: Default;
107 type CallInitial: Default;
109
110 fn apply_body(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
112}
113
114impl EndpointBody for () {
115 type ParamsNext = Ready;
116 type CallInitial = Ready;
117
118 fn apply_body(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
119 Ok(builder)
120 }
121}
122
123pub trait DefaultParamsInitial<E: Endpoint> {
125 fn initial(
126 client: &crate::Client,
127 ) -> EndpointRequestBuilder<'_, E, <E::Body as EndpointBody>::CallInitial>;
128}
129
130impl<E: Endpoint> DefaultParamsInitial<E> for ()
131where
132 E::Params: EndpointParams<BuilderState = Ready>,
133 E::Body: EndpointBody<CallInitial = Ready>,
134{
135 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Ready> {
136 EndpointRequestBuilder::new_ready(client.request(E::METHOD, E::PATH))
137 }
138}
139
140pub trait EndpointHeaders: Default + Sized {
142 fn apply_headers(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
144}
145
146impl EndpointHeaders for () {
147 fn apply_headers(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
148 Ok(builder)
149 }
150}
151
152pub type ParamsBuilderState<P> = <P as EndpointParams>::BuilderState;
154
155pub trait EndpointParamsInitial<E: Endpoint>: EndpointParams {
157 type State;
159 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::State>;
160}
161
162impl<E: Endpoint> EndpointParamsInitial<E> for ()
163where
164 (): DefaultParamsInitial<E>,
165{
166 type State = <E::Body as EndpointBody>::CallInitial;
167
168 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, Self::State> {
169 <() as DefaultParamsInitial<E>>::initial(client)
170 }
171}
172
173impl<E: Endpoint, P: EndpointParams<BuilderState = NeedsParams>> EndpointParamsInitial<E> for P {
174 type State = NeedsParams;
175
176 fn initial(client: &crate::Client) -> EndpointRequestBuilder<'_, E, NeedsParams> {
177 EndpointRequestBuilder::new_needs_params(client.request(E::METHOD, E::PATH))
178 }
179}
180
181pub trait EndpointParams: Default + Sized {
183 type BuilderState;
185 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_>;
187}
188
189impl EndpointParams for () {
190 type BuilderState = Ready;
191
192 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
193 builder
194 }
195}
196
197impl EndpointParams for std::collections::HashMap<String, String> {
198 type BuilderState = NeedsParams;
199
200 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
201 builder.params(self)
202 }
203}
204
205impl EndpointParams for Vec<(String, String)> {
206 type BuilderState = NeedsParams;
207
208 fn apply_params(self, builder: RequestBuilder<'_>) -> RequestBuilder<'_> {
209 builder.params_iter(self)
210 }
211}
212
213pub trait EndpointQuery {
215 fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>>;
217}
218
219impl EndpointQuery for () {
220 fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
221 Ok(builder)
222 }
223}
224
225impl EndpointQuery for IndexMap<String, QueryValue> {
226 fn apply_query(self, builder: RequestBuilder<'_>) -> crate::Result<RequestBuilder<'_>> {
227 Ok(builder.queries(self))
228 }
229}
230
231#[cfg(feature = "json")]
233pub fn apply_serialized_query<T: serde::Serialize>(
234 query: T,
235 builder: RequestBuilder<'_>,
236) -> crate::Result<RequestBuilder<'_>> {
237 let map = crate::url_build::serialize_to_query_map(&query).map_err(|e| match e {
238 Error::Other(msg) => Error::query_serialize(msg),
239 other => other,
240 })?;
241 Ok(builder.queries(map))
242}
243
244#[cfg(all(feature = "json", feature = "validate"))]
246pub fn apply_serialized_query_validated<T>(
247 query: T,
248 builder: RequestBuilder<'_>,
249) -> crate::Result<RequestBuilder<'_>>
250where
251 T: serde::Serialize + garde::Validate,
252 T::Context: Default,
253{
254 garde::Validate::validate(&query).map_err(|report: garde::Report| {
255 Error::RequestValidation {
256 message: report.to_string(),
257 }
258 })?;
259 apply_serialized_query(query, builder)
260}
261
262#[must_use = "endpoint builders do nothing until you call `.send().await`, `.send_json().await`, or similar"]
270pub struct EndpointRequestBuilder<'a, E: Endpoint, S> {
271 pub(crate) inner: RequestBuilder<'a>,
272 _marker: PhantomData<(E, S)>,
273}
274
275impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, NeedsParams> {
276 pub(crate) fn new_needs_params(inner: RequestBuilder<'a>) -> Self {
277 Self {
278 inner,
279 _marker: PhantomData,
280 }
281 }
282
283 pub fn params(
285 self,
286 params: E::Params,
287 ) -> EndpointRequestBuilder<'a, E, ParamsBuilderStateAfter<E>>
288 where
289 E::Body: EndpointBody,
290 {
291 EndpointRequestBuilder {
292 inner: params.apply_params(self.inner),
293 _marker: PhantomData,
294 }
295 }
296}
297
298pub type ParamsBuilderStateAfter<E> = <<E as Endpoint>::Body as EndpointBody>::ParamsNext;
300
301impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, NeedsBody> {
302 pub fn new_needs_body(inner: RequestBuilder<'a>) -> Self {
303 Self {
304 inner,
305 _marker: PhantomData,
306 }
307 }
308
309 #[cfg(feature = "json")]
311 pub fn json<T: serde::Serialize>(
312 self,
313 body: &T,
314 ) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>> {
315 Ok(EndpointRequestBuilder {
316 inner: self.inner.json(body)?,
317 _marker: PhantomData,
318 })
319 }
320
321 #[cfg(feature = "validate")]
323 pub fn json_validated<T>(self, body: &T) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>>
324 where
325 T: serde::Serialize + garde::Validate,
326 T::Context: Default,
327 {
328 Ok(EndpointRequestBuilder {
329 inner: self.inner.json_validated(body)?,
330 _marker: PhantomData,
331 })
332 }
333
334 pub fn with_body(self, body: E::Body) -> crate::Result<EndpointRequestBuilder<'a, E, Ready>> {
336 Ok(EndpointRequestBuilder {
337 inner: body.apply_body(self.inner)?,
338 _marker: PhantomData,
339 })
340 }
341
342 pub fn body(self, body: impl Into<bytes::Bytes>) -> EndpointRequestBuilder<'a, E, Ready> {
344 EndpointRequestBuilder {
345 inner: self.inner.body(body),
346 _marker: PhantomData,
347 }
348 }
349}
350
351impl<'a, E: Endpoint> EndpointRequestBuilder<'a, E, Ready> {
352 pub(crate) fn new_ready(inner: RequestBuilder<'a>) -> Self {
353 Self {
354 inner,
355 _marker: PhantomData,
356 }
357 }
358
359 pub fn query(self, query: E::Query) -> crate::Result<Self> {
400 Ok(Self {
401 inner: query.apply_query(self.inner)?,
402 _marker: PhantomData,
403 })
404 }
405
406 #[cfg(all(feature = "json", feature = "validate"))]
410 pub fn query_validated(self, query: E::Query) -> crate::Result<Self>
411 where
412 E::Query: serde::Serialize + garde::Validate,
413 <E::Query as garde::Validate>::Context: Default,
414 {
415 Ok(Self {
416 inner: apply_serialized_query_validated(query, self.inner)?,
417 _marker: PhantomData,
418 })
419 }
420
421 pub fn header(self, key: impl AsRef<str>, value: impl AsRef<str>) -> crate::Result<Self> {
423 Ok(Self {
424 inner: self.inner.header(key, value)?,
425 _marker: PhantomData,
426 })
427 }
428
429 pub fn bearer_token(self, token: impl Into<String>) -> Self {
431 Self {
432 inner: self.inner.bearer_token(token),
433 _marker: PhantomData,
434 }
435 }
436
437 pub fn cancellation_token(self, token: crate::CancellationToken) -> Self {
439 Self {
440 inner: self.inner.cancellation_token(token),
441 _marker: PhantomData,
442 }
443 }
444
445 pub fn throw_on_error(self, throw: bool) -> Self {
447 Self {
448 inner: self.inner.throw_on_error(throw),
449 _marker: PhantomData,
450 }
451 }
452
453 pub fn base_url(self, base_url: impl AsRef<str>) -> crate::Result<Self> {
455 Ok(Self {
456 inner: self.inner.base_url(base_url)?,
457 _marker: PhantomData,
458 })
459 }
460
461 pub fn retry(self, policy: crate::RetryPolicy) -> Self {
463 Self {
464 inner: self.inner.retry(policy),
465 _marker: PhantomData,
466 }
467 }
468
469 pub fn timeout(self, timeout: std::time::Duration) -> Self {
471 Self {
472 inner: self.inner.timeout(timeout),
473 _marker: PhantomData,
474 }
475 }
476
477 pub async fn send_stream(self) -> crate::Result<crate::StreamingResponse> {
479 self.inner.send_stream().await
480 }
481
482 pub fn max_response_bytes(self, limit: u64) -> Self {
484 Self {
485 inner: self.inner.max_response_bytes(limit),
486 _marker: PhantomData,
487 }
488 }
489
490 #[cfg(feature = "json")]
492 pub fn json<T: serde::Serialize>(self, body: &T) -> crate::Result<Self> {
493 Ok(Self {
494 inner: self.inner.json(body)?,
495 _marker: PhantomData,
496 })
497 }
498
499 #[cfg(feature = "validate")]
501 pub fn json_validated<T>(self, body: &T) -> crate::Result<Self>
502 where
503 T: serde::Serialize + garde::Validate,
504 T::Context: Default,
505 {
506 Ok(Self {
507 inner: self.inner.json_validated(body)?,
508 _marker: PhantomData,
509 })
510 }
511
512 pub fn body(self, body: impl Into<bytes::Bytes>) -> Self {
514 Self {
515 inner: self.inner.body(body),
516 _marker: PhantomData,
517 }
518 }
519
520 pub async fn send(self) -> crate::Result<crate::Response> {
522 self.inner.send().await
523 }
524
525 pub fn with_body(self, body: E::Body) -> crate::Result<Self> {
527 Ok(Self {
528 inner: body.apply_body(self.inner)?,
529 _marker: PhantomData,
530 })
531 }
532
533 pub fn with_headers(self, headers: E::Headers) -> crate::Result<Self> {
535 Ok(Self {
536 inner: headers.apply_headers(self.inner)?,
537 _marker: PhantomData,
538 })
539 }
540
541 #[cfg(feature = "validate")]
543 pub fn with_headers_validated(self, headers: E::Headers) -> crate::Result<Self>
544 where
545 E::Headers: garde::Validate,
546 <E::Headers as garde::Validate>::Context: Default,
547 {
548 garde::Validate::validate(&headers).map_err(|report: garde::Report| {
549 Error::RequestValidation {
550 message: report.to_string(),
551 }
552 })?;
553 self.with_headers(headers)
554 }
555
556 #[cfg(feature = "json")]
558 pub async fn send_json(self) -> crate::Result<E::Response> {
559 self.inner.send().await?.json::<E::Response>().await
560 }
561
562 #[cfg(feature = "json")]
564 pub async fn send_api<T, ErrBody>(self) -> crate::Result<std::result::Result<T, ErrBody>>
565 where
566 T: serde::de::DeserializeOwned,
567 ErrBody: serde::de::DeserializeOwned,
568 {
569 crate::api_response::into_api_result(self.inner.send().await?)
570 }
571}
572
573impl<'a, E: Endpoint> Deref for EndpointRequestBuilder<'a, E, Ready> {
574 type Target = RequestBuilder<'a>;
575
576 fn deref(&self) -> &Self::Target {
577 &self.inner
578 }
579}
580
581#[macro_export]
597macro_rules! define_params {
598 (
599 $name:ident for $path:literal {
600 $( $field:ident : $ty:ty ),* $(,)?
601 }
602 ) => {
603 #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
604 pub struct $name {
605 $( pub $field: $ty, )*
606 }
607
608 impl $crate::EndpointParams for $name {
609 type BuilderState = $crate::NeedsParams;
610
611 fn apply_params(self, builder: $crate::RequestBuilder<'_>) -> $crate::RequestBuilder<'_> {
612 let builder = builder;
613 $(
614 let builder = builder.param(stringify!($field), self.$field);
615 )*
616 builder
617 }
618 }
619 };
620}
621
622#[cfg(feature = "json")]
624#[macro_export]
625macro_rules! impl_serde_endpoint_query {
626 ($ty:ty) => {
627 impl $crate::EndpointQuery for $ty {
628 fn apply_query(
629 self,
630 builder: $crate::RequestBuilder<'_>,
631 ) -> $crate::Result<$crate::RequestBuilder<'_>> {
632 $crate::endpoint::apply_serialized_query(self, builder)
633 }
634 }
635 };
636}
637
638#[macro_export]
657macro_rules! endpoint {
658 (
659 $name:ident,
660 $method:ident,
661 $path:literal,
662 Response = $response:ty
663 ) => {
664 $crate::endpoint!(
665 $name,
666 $method,
667 $path,
668 Response = $response,
669 Params = (),
670 Query = ()
671 );
672 };
673 (
674 $name:ident,
675 $method:ident,
676 $path:literal,
677 Response = $response:ty,
678 Params = $params:ty
679 ) => {
680 $crate::endpoint!(
681 $name,
682 $method,
683 $path,
684 Response = $response,
685 Params = $params,
686 Query = ()
687 );
688 };
689 (
690 $name:ident,
691 $method:ident,
692 $path:literal,
693 Response = $response:ty,
694 Query = $query:ty
695 ) => {
696 $crate::endpoint!(
697 $name,
698 $method,
699 $path,
700 Response = $response,
701 Params = (),
702 Query = $query
703 );
704 };
705 (
706 $name:ident,
707 $method:ident,
708 $path:literal,
709 Response = $response:ty,
710 Params = $params:ty,
711 Query = $query:ty
712 ) => {
713 pub struct $name;
714 impl $crate::Endpoint for $name {
715 const METHOD: http::Method = http::Method::$method;
716 const PATH: &'static str = $path;
717 type Response = $response;
718 type Params = $params;
719 type Query = $query;
720 type Body = ();
721 type Headers = ();
722 }
723 };
724}
725
726#[cfg(test)]
727mod tests {
728 use super::*;
729
730 define_params!(TestParams for "/items/:id" { id: u64 });
731
732 #[test]
733 fn params_builder_state_is_needs_params() {
734 fn assert_needs<T: EndpointParams<BuilderState = NeedsParams>>() {}
735 assert_needs::<TestParams>();
736 }
737
738 #[test]
739 fn unit_params_builder_state_is_ready() {
740 fn assert_ready<T: EndpointParams<BuilderState = Ready>>() {}
741 assert_ready::<()>();
742 }
743}