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;