hapic/
lib.rs

1//! A Rust crate for quickly creating nice-to-use client libraries for HTTP APIs, in particular,
2//! there's lots of tooling around HTTP JSON APIs.
3//!
4//! **This is still a work in progress.**
5//!
6//! ## Example: defining a JSON API client
7//!
8//! ### Super Simple
9//!
10//! We'll start with a simple dummy API, it has the following endpoints:
11//!
12//! ```text
13//! >> POST /add
14//! >> { "a": 2, "b": 3 }
15//! << { "c": 5 }
16//!
17//! >> POST /sub
18//! >> { "a": 6, "b": 3 }
19//! << { "c": 3 }
20//!
21//! >> POST /factorial
22//! >> { "a": 4 }
23//! << { "c": 24 }
24//! ```
25//!
26//! We can define a client for this API as such:
27//!
28//! ```
29//! use hapic::json_api;
30//! use serde::{Deserialize, Serialize};
31//!
32//! #[derive(Serialize)]
33//! struct Add {
34//!     a: u32,
35//!     b: u32,
36//! }
37//!
38//! #[derive(Serialize)]
39//! struct Sub {
40//!     a: u32,
41//!     b: u32,
42//! }
43//!
44//! #[derive(Serialize)]
45//! struct Factorial {
46//!     a: u8,
47//! }
48//!
49//! #[derive(Deserialize, PartialEq, Eq, Debug)]
50//! struct Output {
51//!     c: u64,
52//! }
53//!
54//! json_api!(
55//!     struct TestClient<B, T: Transport<B>>;
56//!     trait TestApiCall;
57//!
58//!     simple {
59//!         "/add": Add => Output;
60//!         "/sub": Sub => Output;
61//!         "/factorial": Factorial => Output;
62//!     }
63//! );
64//! ```
65//!
66//! Now, the call types (`Add`, `Sub` and `Factorial`) all implement `TestApiCall`.
67//!
68//! We can make an API call (to `http://localhost:8080`) using:
69//!
70//! ```
71//! # use hapic::json_api;
72//! # use serde::{Deserialize, Serialize};
73//! #
74//! # #[derive(Serialize)]
75//! # struct Add {
76//! #     a: u32,
77//! #     b: u32,
78//! # }
79//! #
80//! # #[derive(Serialize)]
81//! # struct Sub {
82//! #     a: u32,
83//! #     b: u32,
84//! # }
85//! #
86//! # #[derive(Serialize)]
87//! # struct Factorial {
88//! #     a: u8,
89//! # }
90//! #
91//! # #[derive(Deserialize, PartialEq, Eq, Debug)]
92//! # struct Output {
93//! #     c: u64,
94//! # }
95//! #
96//! # json_api!(
97//! #     struct TestClient<B, T: Transport<B>>;
98//! #     trait TestApiCall;
99//! #
100//! #     simple {
101//! #         "/add": Add => Output;
102//! #         "/sub": Sub => Output;
103//! #         "/factorial": Factorial => Output;
104//! #     }
105//! # );
106//! # async fn example() {
107//! let client = TestClient::new("http://localhost:8000".into());
108//! let output = client.call(Add { a: 1, b: 2 }).await.unwrap();
109//! assert_eq!(output, Output { c: 3 });
110//! # }
111//! ```
112//!
113//! ### With Conversions
114//!
115//! Now suppose we have a single endpoint for all these operations:
116//!
117//! ```text
118//! >> POST /op
119//! >> { "a": 2, "b": 3, "operation": "add" }
120//! << { "c": 5 }
121//!
122//! >> POST /op
123//! >> { "a": 6, "b": 3, "operation": "sub" }
124//! << { "c": 3 }
125//!
126//! >> POST /op
127//! >> { "a": 4, "operation": "factorial" }
128//! << { "c": 24 }
129//! ```
130//!
131//! We *could* define the type:
132//!
133//! ```
134//! #[derive(serde::Serialize)]
135//! struct Operation {
136//!     operation: &'static str,
137//!     a: u32,
138//!     #[serde(skip_serializing_if = "Option::is_none")]
139//!     b: Option<u32>,
140//! }
141//! ```
142//!
143//! This this isn't very idiomatic! Firstly, the user can put any string as the operation, but we
144//! know our API server only accepts `add`, `sub` or `factorial`. In addition, if `b` is `Some` in a
145//! factorial operation, or is `None` in `add` or `sub`, we'll have an invalid call.
146//!
147//! We want to make this safe, so we can define two types, one for the API call, and another which
148//! will be sent as the JSON body.
149//!
150//! ```rust
151//! # use hapic::json_api;
152//! #
153//! # #[derive(serde::Deserialize, PartialEq, Eq, Debug)]
154//! # struct Output {
155//! #     c: u64,
156//! # }
157//! enum Operation {
158//!     Add((u32, u32)),
159//!     Sub((u32, u32)),
160//!     Factorial(u8),
161//! }
162//!
163//! #[derive(serde::Serialize)]
164//! struct JsonOperation {
165//!     operation: &'static str,
166//!     a: u32,
167//!     #[serde(skip_serializing_if = "Option::is_none")]
168//!     b: Option<u32>,
169//! }
170//!
171//! impl From<Operation> for JsonOperation {
172//!     fn from(op: Operation) -> JsonOperation {
173//!         match op {
174//!             Operation::Add((a, b)) => JsonOperation {
175//!                 operation: "add",
176//!                 a,
177//!                 b: Some(b),
178//!             },
179//!             Operation::Sub((a, b)) => JsonOperation {
180//!                 operation: "sub",
181//!                 a,
182//!                 b: Some(b),
183//!             },
184//!             Operation::Factorial(x) => JsonOperation {
185//!                 operation: "factorial",
186//!                 a: x as u32,
187//!                 b: None,
188//!             },
189//!         }
190//!     }
191//! }
192//!
193//! json_api!(
194//!     struct TestClient<B, T: Transport<B>>;
195//!     trait TestApiCall;
196//!
197//!     json {
198//!         "/op": Operation as JsonOperation => Output as Output;
199//!     }
200//! );
201//! ```
202//!
203//! Of course, we could have achieved the same thing using a custom serialization implementation,
204//! but in many cases (especially for deserializing the output) it's significantly faster to define
205//! two types and implement `TryInto` or `Into` between them.
206//!
207//! Finally, we make another tweak. Out `Output` type is just a single number, so why not return a
208//! number as the result.
209//!
210//! ```rust
211//! # enum Operation {
212//! #     Add((u32, u32)),
213//! #     Sub((u32, u32)),
214//! #     Factorial(u8),
215//! # }
216//! #
217//! # #[derive(serde::Serialize)]
218//! # struct JsonOperation {
219//! #     operation: &'static str,
220//! #     a: u32,
221//! #     #[serde(skip_serializing_if = "Option::is_none")]
222//! #     b: Option<u32>,
223//! # }
224//! #
225//! # impl From<Operation> for JsonOperation {
226//! #     fn from(op: Operation) -> JsonOperation {
227//! #         match op {
228//! #             Operation::Add((a, b)) => JsonOperation {
229//! #                 operation: "add",
230//! #                 a,
231//! #                 b: Some(b),
232//! #             },
233//! #             Operation::Sub((a, b)) => JsonOperation {
234//! #                 operation: "sub",
235//! #                 a,
236//! #                 b: Some(b),
237//! #             },
238//! #             Operation::Factorial(x) => JsonOperation {
239//! #                 operation: "factorial",
240//! #                 a: x as u32,
241//! #                 b: None,
242//! #             },
243//! #         }
244//! #     }
245//! # }
246//! # #[derive(serde::Deserialize, PartialEq, Eq, Debug)]
247//! # struct Output {
248//! #     c: u64,
249//! # }
250//! #
251//! # use hapic::json_api;
252//! impl From<Output> for u64 {
253//!     fn from(output: Output) -> u64 {
254//!         output.c
255//!     }
256//! }
257//!
258//! json_api!(
259//!     struct TestClient<B, T: Transport<B>>;
260//!     trait TestApiCall;
261//!
262//!     json {
263//!         "/op": Operation as JsonOperation => Output as u64;
264//!     }
265//! );
266//! ```
267//!
268//! Now we can make API calls really neatly:
269//!
270//! ```rust
271//! # enum Operation {
272//! #     Add((u32, u32)),
273//! #     Sub((u32, u32)),
274//! #     Factorial(u8),
275//! # }
276//! #
277//! # #[derive(serde::Serialize)]
278//! # struct JsonOperation {
279//! #     operation: &'static str,
280//! #     a: u32,
281//! #     #[serde(skip_serializing_if = "Option::is_none")]
282//! #     b: Option<u32>,
283//! # }
284//! #
285//! # impl From<Operation> for JsonOperation {
286//! #     fn from(op: Operation) -> JsonOperation {
287//! #         match op {
288//! #             Operation::Add((a, b)) => JsonOperation {
289//! #                 operation: "add",
290//! #                 a,
291//! #                 b: Some(b),
292//! #             },
293//! #             Operation::Sub((a, b)) => JsonOperation {
294//! #                 operation: "sub",
295//! #                 a,
296//! #                 b: Some(b),
297//! #             },
298//! #             Operation::Factorial(x) => JsonOperation {
299//! #                 operation: "factorial",
300//! #                 a: x as u32,
301//! #                 b: None,
302//! #             },
303//! #         }
304//! #     }
305//! # }
306//! #
307//! # #[derive(serde::Deserialize, PartialEq, Eq, Debug)]
308//! # struct Output {
309//! #     c: u64,
310//! # }
311//! #
312//! # use hapic::json_api;
313//! #
314//! # impl From<Output> for u64 {
315//! #     fn from(output: Output) -> u64 {
316//! #         output.c
317//! #     }
318//! # }
319//! #
320//! # json_api!(
321//! #     struct TestClient<B, T: Transport<B>>;
322//! #     trait TestApiCall;
323//! #
324//! #     json {
325//! #         "/op": Operation as JsonOperation => Output as u64;
326//! #     }
327//! # );
328//! # async fn example() {
329//! let client = TestClient::new(std::borrow::Cow::Borrowed("http://localhost:8000"));
330//! let output = client.call(Operation::Add((1, 2))).await.unwrap();
331//! assert_eq!(output, 3);
332//! # }
333//! ```
334//!
335//! ## Real-World Example
336//!
337//! For a real world example, see `cloudconvert-rs`.
338//!
339//! ## Using the macros
340//!
341//! In the simple examples above, we made use of the `json_api!` macro to generate the client and
342//! API calls.
343//!
344//! The `json_api!` macro is just a shorthand way of using the `client!` macro, then the
345//! `json_api_call!` macro, both of which have detailed documentation.
346//!
347//! ## Implementing Traits Directly
348//!
349//! You can also (and in some cases will want to) implement the traits directly. Here's a summary,
350//! from most abstract to least abstract:
351//!
352//! - [`SimpleApiCall`]: a JSON API call where the input and output types undergo no conversion
353//!   (automatically implements [`JsonApiCall`]).
354//! - [`JsonApiCall`]: a JSON api call, allowing for conversion of the request and response
355//!   (automatically implements [`ApiCall`]).
356//! - [`ApiCall`]: a generic API call (automatically implements [`RawApiCall`]).
357//! - [`RawApiCall`]: the lowest level trait for API calls, you probably want to implement one of
358//!   the others.
359
360use std::borrow::Cow;
361use std::marker::PhantomData;
362
363pub use http;
364pub use serde_json;
365
366#[cfg(feature = "hyper")]
367pub use hyper;
368#[cfg(feature = "hyper")]
369pub use hyper_tls;
370
371use http::{
372    header::{self, HeaderMap, HeaderValue},
373    Method, StatusCode,
374};
375use serde::{Deserialize, Serialize};
376
377pub mod transport;
378
379#[cfg(feature = "hyper")]
380/// Macro for generating the client and ApiCall type.
381///
382/// If you're defining JSON API calls at the same time, you can use [`json_api`] instead.
383///
384/// For example:
385/// ```
386/// hapic::client!(
387///     pub struct Client<B, T: Transport<B>>;
388///     pub trait ApiCall;
389/// );
390/// ```
391///
392/// Creates:
393/// ```
394/// use hapic::RawApiCall;
395/// use hapic::transport::Transport;
396///
397/// pub struct Client<B, T: Transport<B>> {
398///     pub client: hapic::Client<B, T>
399/// }
400///
401/// pub trait ApiCall: RawApiCall {}
402/// ```
403///
404/// The `Client` type has, by default, two impls:
405///
406/// ```
407/// use hapic::{Error, RawApiCall};
408/// use hapic::transport::HttpsTransport;
409///
410/// # use hapic::transport::Transport;
411/// # pub struct Client<B, T: Transport<B>> {
412/// #     pub client: hapic::Client<B, T>
413/// # }
414///
415/// pub trait ApiCall: RawApiCall {}
416///
417/// impl Client<hyper::Body, HttpsTransport> {
418///     /// Create a new client to the provided endpoint, using `hyper` and `HttpsTransport`.
419///     pub fn new(endpoint: std::borrow::Cow<'static, str>) -> Self {
420///         Client {
421///             client: hapic::Client::new_https_client(endpoint),
422///         }
423///     }
424/// }
425///
426/// impl<B: Send + Sync, T: Transport<B>> Client<B, T> {
427///     pub async fn call<C>(
428///         &self,
429///         api_call: C,
430///     ) -> std::result::Result<C::Output, Error>
431///     where
432///         C: ApiCall + Send,
433///         B: From<<C as RawApiCall>::RequestBody>,
434///     {
435///         RawApiCall::request(api_call, &self.client).await
436///     }
437/// }
438/// ```
439#[macro_export]
440macro_rules! client {
441    (
442        $(#[$client_meta:meta])*
443        $client_vis:vis struct $Client:ident<B, T: $($(hapic::)?transport::)?Transport<B>>;
444        $(#[$trait_meta:meta])*
445        $trait_vis:vis trait $ApiCall:ident;
446    ) => {
447        $(#[$client_meta])*
448        $client_vis struct $Client<B, T: $crate::transport::Transport<B>> {
449            pub client: $crate::Client<B, T>,
450        }
451
452        $(#[$trait_meta])*
453        $trait_vis trait $ApiCall: $crate::RawApiCall {}
454
455        impl $Client<$crate::hyper::Body, $crate::transport::HttpsTransport> {
456            /// Create a new client to the provided endpoint, using `hyper` and `HttpsTransport`.
457            pub fn new(endpoint: std::borrow::Cow<'static, str>) -> Self {
458                $Client {
459                    client: $crate::Client::new_https_client(endpoint),
460                }
461            }
462        }
463
464        impl<B: Send + Sync, T: $crate::transport::Transport<B>> $Client<B, T> {
465            /// Make an API call.
466            pub async fn call<C>(
467                &self,
468                api_call: C,
469            ) -> std::result::Result<<C as $crate::RawApiCall>::Output, $crate::Error>
470            where
471                C: $ApiCall + Send,
472                B: From<<C as $crate::RawApiCall>::RequestBody>,
473            {
474                $crate::RawApiCall::request(api_call, &self.client).await
475            }
476        }
477    };
478}
479
480#[cfg(not(feature = "hyper"))]
481/// Macro for generating the client and ApiCall type.
482///
483/// If you're defining JSON API calls at the same time, you can use [`json_api`] instead.
484///
485/// For example:
486/// ```
487/// hapic::client!(
488///     pub struct Client<B, T: Transport<B>>;
489///     pub trait ApiCall;
490/// );
491/// ```
492///
493/// Creates:
494/// ```
495/// use hapic::RawApiCall;
496/// use hapic::transport::Transport;
497///
498/// pub struct Client<B, T: Transport<B>> {
499///     pub client: hapic::Client<B, T>
500/// }
501///
502/// pub trait ApiCall: RawApiCall {}
503/// ```
504///
505/// The `Client` type has, by default, two impls:
506///
507/// ```
508/// use hapic::{Error, RawApiCall};
509/// use hapic::transport::HttpsTransport;
510///
511/// # use hapic::transport::Transport;
512/// # pub struct Client<B, T: Transport<B>> {
513/// #     pub client: hapic::Client<B, T>
514/// # }
515///
516/// pub trait ApiCall: RawApiCall {}
517///
518/// impl Client<hyper::Body, HttpsTransport> {
519///     /// Create a new client to the provided endpoint, using `hyper` and `HttpsTransport`.
520///     pub fn new(endpoint: std::borrow::Cow<'static, str>) -> Self {
521///         Client {
522///             client: hapic::Client::new_https_client(endpoint),
523///         }
524///     }
525/// }
526///
527/// impl<B: Send + Sync, T: Transport<B>> Client<B, T> {
528///     pub async fn call<C>(
529///         &self,
530///         api_call: C,
531///     ) -> std::result::Result<C::Output, Error>
532///     where
533///         C: ApiCall + Send,
534///         B: From<<C as RawApiCall>::RequestBody>,
535///     {
536///         RawApiCall::request(api_call, &self.client).await
537///     }
538/// }
539/// ```
540#[macro_export]
541macro_rules! client {
542    (
543        $(#[$client_meta:meta])*
544        $client_vis:vis struct $Client:ident<B, T: $($(hapic::)?transport::)?Transport<B>>;
545        $(#[$trait_meta:meta])*
546        $trait_vis:vis trait $ApiCall:ident;
547    ) => {
548        $(#[$client_meta])*
549        $client_vis struct $Client<B, T: $crate::transport::Transport<B>> {
550            pub client: $crate::Client<B, T>,
551        }
552
553        $(#[$trait_meta])*
554        $trait_vis trait $ApiCall: $crate::RawApiCall {}
555
556        impl<B: Send + Sync, T: $crate::transport::Transport<B>> $Client<B, T> {
557            /// Make an API call.
558            pub async fn call<C>(
559                &self,
560                api_call: C,
561            ) -> std::result::Result<<C as $crate::RawApiCall>::Output, $crate::Error>
562            where
563                C: $ApiCall + Send,
564                B: From<<C as $crate::RawApiCall>::RequestBody>,
565            {
566                $crate::RawApiCall::request(api_call, &self.client).await
567            }
568        }
569    };
570}
571
572/// Define a JSON api client and some API calls.
573///
574/// This is shorthand for the [`client`] macro, followed by the [`json_api_call`] macro.
575///
576/// See the crate level documentation for examples, and the constituent macros for more detailed
577/// documentation.
578#[macro_export]
579macro_rules! json_api {
580    (
581        $(#[$client_meta:meta])*
582        $client_vis:vis struct $Client:ident<B, T: $($(hapic::)?transport::)?Transport<B>>;
583        $(#[$trait_meta:meta])*
584        $trait_vis:vis trait $ApiCall:ident;
585        $($tt:tt)*
586    ) => {
587        $crate::client!(
588            $(#[$client_meta])*
589            $client_vis struct $Client<B, T: Transport<B>>;
590            $(#[$trait_meta])*
591            $trait_vis trait $ApiCall;
592        );
593        $crate::json_api_call!(
594            ApiCall: $ApiCall;
595            $($tt)*
596        );
597    }
598}
599
600/// A macro for quickly implementing the various JSON API call traits.
601///
602/// # SimpleApiCall
603///
604/// To implement [`SimpleApiCall`], use:
605///
606/// ```
607/// # use hapic::json_api_call;
608/// # #[derive(serde::Serialize)]
609/// # struct Foo;
610/// # #[derive(serde::Deserialize)]
611/// # struct Bar;
612///
613/// json_api_call!(simple "/foo": Foo => Bar);
614/// ```
615///
616/// This implements [``SimpleApiCall`] for `Foo`, with output type `Bar`. The API call formats the
617/// value (of type `Foo`) to a JSON string, makes a post request to `"/foo"` and then parses the
618/// results as type `Bar` (expecting a 200-299 response).
619///
620/// `Foo` must implement [`serde::Serialize`] and `Bar` must implement [`serde::Deserialize`].
621///
622/// To customise the request method used, you can write:
623///
624/// ```
625/// # use hapic::json_api_call;
626/// # #[derive(serde::Serialize)]
627/// # struct Foo;
628/// # #[derive(serde::Deserialize)]
629/// # struct Bar;
630///
631/// json_api_call!(simple PUT "/foo": Foo => Bar);
632/// ```
633///
634/// Replacing `PUT` with any HTTP method.
635///
636/// # JsonApiCall
637///
638/// If you require type conversion of the input and/or output type, you can instead implement
639/// [`JsonApiCall`] with:
640///
641/// ```
642/// # use hapic::json_api_call;
643/// # struct Foo;
644/// # #[derive(serde::Serialize)]
645/// # struct FooJson;
646/// # #[derive(serde::Deserialize)]
647/// # struct BarJson;
648/// # struct Bar;
649/// # impl From<Foo> for FooJson { fn from(_: Foo) -> FooJson { FooJson } }
650/// # impl From<BarJson> for Bar { fn from(_: BarJson) -> Bar { Bar } }
651/// json_api_call!(json "/foo": Foo as FooJson => BarJson as Bar);
652/// ```
653///
654/// For this, we require:
655/// ```ignore
656/// Foo: TryInto<FooJson>
657///     where <Foo as TryInto<FooJson>>::Error: Into<hapic::Error>
658/// FooJson: Serialize
659/// BarJson: Deserialize
660/// BarJson: TryInto<Bar>
661///     where <BarJson as TryInto<Bar>>::Error: Into<hapic::Error>
662/// ```
663///
664/// # Not using the macros
665///
666/// Of course, you can implement [`SimpleApiCall`] or [`JsonApiCall`] yourself, however there isn't
667/// much advantage, except for adding handling for non-2xx status codes.
668///
669/// If your API call is more complicated, you probably want to consider implementing [`ApiCall`]
670/// (or, if you want almost no abstraction, [`SimpleApiCall`]).
671#[macro_export]
672macro_rules! json_api_call {
673    (simple $(<$($a:lifetime),*>)? ($ApiCall:ty) $method:ident $url:literal : $Input:ty => $Output:ty) => {
674        impl$(<$($a),*>)? $ApiCall for $Input {}
675        $crate::json_api_call!(simple $(<$($a),*>)? $method $url : $Input => $Output);
676    };
677
678    (simple $(<$($a:lifetime),*>)? $method:ident $url:literal : $Input:ty => $Output:ty) => {
679        impl$(<$($a),*>)? $crate::SimpleApiCall for $Input {
680            type Output = $Output;
681
682            fn method(&self) -> $crate::http::Method {
683                $crate::http::Method::$method
684            }
685
686            fn uri(&self, endpoint: &str) -> std::string::String {
687                let uri = String::with_capacity(endpoint.len() + $url.len());
688                uri + endpoint + $url
689            }
690        }
691    };
692
693    (simple $(<$($a:lifetime),*>)? $(($ApiCall:ty))? $url:literal : $Input:ty => $Output:ty) => {
694        $crate::json_api_call!(simple$(<$($a),*>)? $(($ApiCall))? POST $url: $Input => $Output);
695    };
696
697    (json $(<$($a:lifetime),*>)? ($ApiCall:ty) $method:ident $url:literal : $Input:ty as $JsonInput:ty => $JsonOutput:ty as $Output:ty) => {
698        impl$(<$($a),*>)? $ApiCall for $Input {}
699        $crate::json_api_call!(
700            json$(<$($a),*>)? $method $url : $Input as $JsonInput => $JsonOutput as $Output
701        );
702    };
703
704    (json $(<$($a:lifetime),*>)? $method:ident $url:literal : $Input:ty as $JsonInput:ty => $JsonOutput:ty as $Output:ty) => {
705        impl$(<$($a),*>)? $crate::JsonApiCall for $Input {
706            type Output = $Output;
707            type JsonResponse = $JsonOutput;
708            type JsonRequest = $JsonInput;
709
710            fn method(&self) -> $crate::http::Method {
711                $crate::http::Method::$method
712            }
713
714            fn uri(&self, endpoint: &str) -> std::string::String {
715                let uri = String::with_capacity(endpoint.len() + $url.len());
716                uri + endpoint + $url
717            }
718
719            fn try_into_request(self) -> std::result::Result<$JsonInput, $crate::Error> {
720                Ok(self.try_into()?)
721            }
722
723            fn parse_json_response(
724                status: $crate::http::StatusCode,
725                content_type: Option<$crate::http::HeaderValue>,
726                raw_resp: Vec<u8>,
727                resp: $crate::serde_json::Result<Self::JsonResponse>,
728            ) -> std::result::Result<$Output, $crate::Error> {
729                if status.is_success() {
730                    Ok(resp?.try_into()?)
731                } else {
732                    Err($crate::Error::HttpStatusNotSuccess{
733                        status,
734                        content_type,
735                        body: raw_resp,
736                    })
737                }
738            }
739        }
740    };
741
742    (json $(<$($a:lifetime),*>)? $(($ApiCall:ty))? $url:literal : $Input:ty as $JsonInput:ty => $JsonOutput:ty as $Output:ty) => {
743        $crate::json_api_call!(
744            json $(<$($a),*>)? $(($ApiCall))? POST $url: $Input as $JsonInput => $JsonOutput as $Output
745        );
746    };
747
748    (
749        ApiCall: $ApiCall:ident;
750        $(
751            $(
752                simple {$(
753                    $(<$($a:lifetime),*>)? $($simple_method:ident)? $simple_url:literal : $SimpleInput:ty => $SimpleOutput:ty;
754                )*}
755            )?
756            $(
757                json {$(
758                    $(<$($b:lifetime),*>)? $($json_method:ident)? $json_url:literal : $Input:ty as $JsonInput:ty => $JsonOutput:ty as $Output:ty;
759                )*}
760            )?
761        );*
762    ) => {$(
763        $($(
764            $crate::json_api_call!(simple $(<$($a),*>)? ($ApiCall) $($simple_method)? $simple_url: $SimpleInput => $SimpleOutput);
765        )*)?
766        $($(
767            $crate::json_api_call!(json $(<$($b),*>)? ($ApiCall) $($json_method)? $json_url: $Input as $JsonInput => $JsonOutput as $Output);
768        )*)?
769    )*};
770}
771
772use transport::{ResponseBody, Transport};
773
774pub struct Client<B, T: Transport<B>> {
775    pub transport: T,
776    pub phantom_body: PhantomData<B>,
777    pub endpoint: Cow<'static, str>,
778    pub authorization: Option<HeaderValue>,
779    pub extra_headers: HeaderMap,
780}
781
782#[cfg(feature = "hyper")]
783impl Client<hyper::Body, transport::HttpsTransport> {
784    /// Create a new client using [`transport::HttpsTransport`] and [`hyper::Body`].
785    pub fn new_https_client(
786        endpoint: Cow<'static, str>,
787    ) -> Client<hyper::Body, transport::HttpsTransport> {
788        Client {
789            transport: hyper::Client::builder().build(hyper_tls::HttpsConnector::new()),
790            phantom_body: PhantomData,
791            endpoint,
792            authorization: None,
793            extra_headers: HeaderMap::new(),
794        }
795    }
796
797    /// Create a new client using [`transport::HttpsRetryTransport`] and [`hyper::Body`].
798    pub fn new_https_retry_client(
799        endpoint: Cow<'static, str>,
800        retry_config: transport::retry::RetryConfig,
801    ) -> Client<Vec<u8>, transport::HttpsRetryTransport> {
802        Client {
803            transport: transport::RetryTransport::new(
804                hyper::Client::builder().build(hyper_tls::HttpsConnector::new()),
805                retry_config,
806            ),
807            phantom_body: PhantomData,
808            endpoint,
809            authorization: None,
810            extra_headers: HeaderMap::new(),
811        }
812    }
813}
814
815impl<B, T: Transport<B>> Client<B, T> {
816    /// Create a new request builder based on this client.
817    ///
818    /// This should always be used as the base builder, since it includes the authorization and
819    /// extra headers, and may include more parameters in the future.
820    pub fn http_request_builder(&self) -> http::request::Builder {
821        let mut builder = http::Request::builder();
822        if let Some(authorization) = self.authorization.as_ref() {
823            builder = builder.header(header::AUTHORIZATION, authorization);
824        }
825        for (header, value) in self.extra_headers.iter() {
826            builder = builder.header(header, value);
827        }
828        builder
829    }
830}
831
832#[derive(Debug)]
833pub enum Error {
834    Json(serde_json::Error),
835    #[cfg(feature = "hyper")]
836    Hyper(hyper::Error),
837    HttpStatusNotSuccess {
838        status: http::StatusCode,
839        content_type: Option<http::HeaderValue>,
840        body: Vec<u8>,
841    },
842    HttpNoBody,
843    Other(Cow<'static, str>),
844}
845
846macro_rules! error_from {
847    ($(impl From<$type:ty> for $Error:ident :: $Variant:ident);* $(;)?) => {
848        $(
849            impl From<$type> for $Error {
850                fn from(err: $type) -> $Error {
851                    $Error::$Variant(err)
852                }
853            }
854        )*
855    };
856}
857
858error_from!(
859    impl From<serde_json::Error> for Error::Json;
860);
861
862#[cfg(feature = "hyper")]
863error_from!(
864    impl From<hyper::Error> for Error::Hyper;
865);
866
867impl From<std::convert::Infallible> for Error {
868    fn from(_: std::convert::Infallible) -> Error {
869        unreachable!()
870    }
871}
872
873/// A JSON API call where the input and output types undergo no conversion.
874///
875/// This can be implemented manually, or by using:
876/// `json_api_call!(simple "/endpoint": InputType => OutputType)`, or, to specify a custom method:
877/// `json_api_call!(simple PATCH "/endpoint": InputType => OutputType)`.
878///
879/// Implementers of `SimpleApiCall`, automatically implement [`JsonApiCall`], therefore [`ApiCall`]
880/// and therefore [`RawApiCall`].
881pub trait SimpleApiCall: Serialize + Sized + Send {
882    /// The output type of the call.
883    type Output: for<'a> Deserialize<'a>;
884
885    /// The HTTP request method.
886    fn method(&self) -> Method {
887        Method::POST
888    }
889
890    /// Generate the URI to make the HTTP POST request to.
891    fn uri(&self, endpoint: &str) -> String;
892
893    /// A hook used to modify the request before it's finalised. This is called directly before the
894    /// `body` method on the builder is called.
895    fn modify_request(&self, request_builder: http::request::Builder) -> http::request::Builder {
896        request_builder
897    }
898}
899
900impl<T: SimpleApiCall> JsonApiCall for T {
901    type Output = <Self as SimpleApiCall>::Output;
902    type JsonResponse = <Self as SimpleApiCall>::Output;
903
904    type JsonRequest = Self;
905
906    fn method(&self) -> Method {
907        SimpleApiCall::method(self)
908    }
909
910    fn uri(&self, endpoint: &str) -> String {
911        SimpleApiCall::uri(self, endpoint)
912    }
913
914    fn modify_request(&self, request_builder: http::request::Builder) -> http::request::Builder {
915        SimpleApiCall::modify_request(self, request_builder)
916    }
917
918    fn try_into_request(self) -> Result<Self, Error> {
919        Ok(self)
920    }
921
922    fn parse_json_response(
923        status: StatusCode,
924        content_type: Option<http::HeaderValue>,
925        raw_resp: Vec<u8>,
926        resp: serde_json::Result<Self::Output>,
927    ) -> Result<Self::Output, Error> {
928        if status.is_success() {
929            Ok(resp?)
930        } else {
931            Err(Error::HttpStatusNotSuccess {
932                status,
933                content_type,
934                body: raw_resp,
935            })
936        }
937    }
938}
939
940/// A JSON API call. (See also: [`SimpleApiCall`])
941///
942/// This can be implemented manually, or by using the [`json_api_call`] macro.
943///
944/// Implementers of `JsonApiCall`, automatically implement [`ApiCall`] and therefore [`RawApiCall`].
945pub trait JsonApiCall: Sized + Send {
946    /// The output type of the call.
947    type Output;
948
949    /// The response type of the call, as is returned by the server.
950    ///
951    /// This is passed through [`Self::parse_json_response`] to generate the `Output` value.
952    type JsonResponse: for<'a> Deserialize<'a>;
953
954    /// The request that is sent to the server. `self` is converted to this type, using
955    /// [`Self::try_into_request`] before making the API call.
956    type JsonRequest: Serialize;
957
958    /// The HTTP request method.
959    fn method(&self) -> Method {
960        Method::POST
961    }
962
963    /// Generate the URI to make the HTTP POST request to.
964    fn uri(&self, endpoint: &str) -> String;
965
966    /// A hook used to modify the request before it's finalised. This is called directly before the
967    /// `body` method on the builder is called.
968    fn modify_request(&self, request_builder: http::request::Builder) -> http::request::Builder {
969        request_builder
970    }
971
972    /// Convert `self` to a [`Self::JsonRequest`]. This is called before making the HTTP POST
973    /// request.
974    fn try_into_request(self) -> Result<Self::JsonRequest, Error>;
975
976    /// Convert a [`Self::JsonResponse`], as received from the server, into [`Self::Output`].
977    fn parse_json_response(
978        status: StatusCode,
979        content_type: Option<http::HeaderValue>,
980        raw_resp: Vec<u8>,
981        resp: serde_json::Result<Self::JsonResponse>,
982    ) -> Result<Self::Output, Error>;
983}
984
985#[async_trait::async_trait]
986impl<T: JsonApiCall> ApiCall for T {
987    type Output = <Self as JsonApiCall>::Output;
988    type RequestBody = Vec<u8>;
989
990    fn method(&self) -> Method {
991        JsonApiCall::method(self)
992    }
993
994    fn uri(&self, endpoint: &str) -> String {
995        JsonApiCall::uri(self, endpoint)
996    }
997
998    fn modify_request(&self, request_builder: http::request::Builder) -> http::request::Builder {
999        JsonApiCall::modify_request(
1000            self,
1001            request_builder.header(http::header::CONTENT_TYPE, "application/json"),
1002        )
1003    }
1004
1005    fn request_body(self) -> Result<Self::RequestBody, Error> {
1006        let req = self.try_into_request()?;
1007        Ok(serde_json::to_vec(&req)?)
1008    }
1009
1010    async fn response<B: ResponseBody>(resp: http::Response<B>) -> Result<Self::Output, Error> {
1011        let status = resp.status();
1012        let content_type = resp.headers().get("Content-Type").cloned();
1013        let body_bytes = resp
1014            .into_body()
1015            .read_all()
1016            .await
1017            .map_err(|err| err.into())?;
1018        let resp: serde_json::Result<<Self as JsonApiCall>::JsonResponse> =
1019            serde_json::from_slice(body_bytes.as_ref());
1020        <Self as JsonApiCall>::parse_json_response(status, content_type, body_bytes.into(), resp)
1021    }
1022}
1023
1024/// A generic API call. For a lower level interface, see [`RawApiCall`]. For JSON APIs, see
1025/// [`JsonApiCall`] and [`SimpleApiCall`].
1026#[async_trait::async_trait]
1027pub trait ApiCall: Sized + Send {
1028    /// The type of the request body.
1029    type RequestBody;
1030
1031    /// The output type of the call.
1032    type Output;
1033
1034    /// The HTTP request method.
1035    fn method(&self) -> Method {
1036        Method::POST
1037    }
1038
1039    /// Generate the URI to make the HTTP POST request to.
1040    fn uri(&self, endpoint: &str) -> String;
1041
1042    /// A hook used to modify the request before it's finalised. This is called directly before the
1043    /// `body` method on the builder is called.
1044    fn modify_request(&self, request_builder: http::request::Builder) -> http::request::Builder {
1045        request_builder
1046    }
1047
1048    /// Convert `self` to a [`Self::RequestBody`]. This is called before making the HTTP POST
1049    /// request.
1050    fn request_body(self) -> Result<Self::RequestBody, Error>;
1051
1052    /// Convert a [`http::Response`], as received from the server, into [`Self::Output`].
1053    async fn response<B: ResponseBody>(resp: http::Response<B>) -> Result<Self::Output, Error>;
1054}
1055
1056#[async_trait::async_trait]
1057impl<C: ApiCall> RawApiCall for C {
1058    type Output = <C as ApiCall>::Output;
1059
1060    type RequestBody = <C as ApiCall>::RequestBody;
1061
1062    async fn request<B: From<Self::RequestBody> + Send, T: Transport<B>>(
1063        self,
1064        client: &Client<B, T>,
1065    ) -> Result<Self::Output, Error>
1066    where
1067        B: From<Self::RequestBody> + Sync,
1068    {
1069        let method = self.method();
1070        let uri = self.uri(&client.endpoint);
1071        let request = self
1072            .modify_request(client.http_request_builder().method(method).uri(uri))
1073            .body::<B>(self.request_body()?.into())
1074            .unwrap();
1075        let resp = client
1076            .transport
1077            .request(request)
1078            .await
1079            .map_err(|err| err.into())?;
1080        Self::response(resp).await
1081    }
1082}
1083
1084/// The lowest level trait for API calls. You probably shouldn't implement it directly, instead
1085/// preferring [`ApiCall`], or, for JSON APIs, [`JsonApiCall`] and [`SimpleApiCall`].
1086///
1087/// If you do implement it directly, make sure you properly read the docs for the `request` method!
1088#[async_trait::async_trait]
1089pub trait RawApiCall: Sized {
1090    /// The type of the request body.
1091    type RequestBody;
1092
1093    /// The output type of the call.
1094    type Output;
1095
1096    /// Make a request using a client.
1097    ///
1098    /// It's super important to use the `client` value correctly. You should:
1099    /// - Use `client.endpoint` as the base of the URI.
1100    /// - Use `client.http_request_builder` as the base request builder, since this is garenteed to
1101    ///   use the other properties of the client.
1102    /// - Use `client.transport` to make the request.
1103    async fn request<B: From<Self::RequestBody> + Send, T: Transport<B>>(
1104        self,
1105        client: &Client<B, T>,
1106    ) -> Result<Self::Output, Error>
1107    where
1108        B: From<Self::RequestBody> + Sync;
1109}
1110
1111#[cfg(test)]
1112mod test;