1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
//! HTTP client supporting custom request and response types. Pass any type into
//! a `send` function or method, and it will return a result of your desired
//! response type. `send` handles request serialization, http messaging, and
//! response deserialization.
//!
//! To keep this crate simple, it is is oriented towards a specific but very
//! common pattern. If your use case meets the following conditions, this crate
//! will work for you:
//! 1. request-response communication
//! 2. async rust functions
//! 3. communicate over http (uses reqwest under the hood)
//! 4. http body is serialized as json
//! 5. status codes outside the 200 range are considered errors
//! 6. request and response types must be serializable and deserializable using
//!    serde
//! 7. the path and HTTP method can be determined from the concrete rust type
//!    used for the request
//!
//! ## Usage
//!
//! ### Typical
//!
//! To use this library, your request and response types must implement
//! serde::Serialize and serde::Deserialize, respectively.
//!
//! To take full advantage of all library features, you can implement `Request`
//! for each of your request types, instantiate a `Client`, and then you can
//! simply invoke `Client::send` to send requests.
//!
//! ```rust
//! let client = Client::new("http://example.com");
//! let response = client.send(MyRequest::new()).await?;
//! ```
//!
//! ### Basic
//!
//! If you don't want to implement Request or create a Client, the most manual
//! and basic way to use this library is by using `send_custom`.
//!
//! ```rust
//! let my_response: MyResponse = send_custom(
//!     "http://example.com/path/to/my/request/",
//!     HttpMethod::Get,
//!     MyRequest::new()
//! )
//! .await?;
//! ```
//!
//! ### Client
//!
//! One downside of the `send_custom` (and `send`) *function* is that it
//! instantiates a client for every request, which is expensive. To improve
//! performance, you can use the `Client::send_custom` (and `Client::send`)
//! *method* instead to re-use an existing client for every request.
//!
//! ```rust
//! let client = Client::default();
//! let my_response: MyResponse = client.send_custom(
//!     "http://example.com/path/to/my/request/",
//!     HttpMethod::Get,
//!     MyRequest::new()
//! )
//! .await?;
//! ```
//!
//! ### Request
//!
//! You may also prefer not to specify metadata about the request every time you
//! send a request, since these things will likely be the same for every request
//! of this type. Describe the request metadata in the type system by
//! implementing the Request trait.
//!
//! ```rust
//! pub trait Request {
//!     type Response;
//!     fn method(&self) -> HttpMethod;
//!     fn path(&self) -> String;
//! }
//! ```
//!
//! This increases design flexibility and reduces boilerplate. See the [API
//! Client Design](#api-client-design) section below for an explanation.
//!
//! If you do not control the crate with the request and response structs, you
//! can implement any traits for them using the newtype pattern, or with a
//! reusable generic wrapper struct.
//!
//! After implementing this trait, you can use the send function and method,
//! which requires the base url to be included, instead of the full url. All
//! other information about how to send the request and response is implied by
//! the type of the input. This still creates a client on every request, so the
//! performance is not optimal if you are sending multiple requests.
//!
//! ```rust
//! let my_response = send("http://example.com", MyRequest::new()).await?;
//! // The type of my_response is determined by the trait's associated type.
//! // It does not need to be inferrable from the calling context.
//! return my_response.some_field
//! ```
//!
//! If you want to send multiple requests, or if you don't want to include the
//! base url when calling `send`, instantiate a Client:
//!
//! ```rust
//! let client = Client::new("http://example.com");
//! let my_response = client.send(MyRequest::new()).await?;
//! ```
//!
//! ### Request groups
//!
//! You can also define request groups. This defines a client type that is
//! explicit about exactly which requests it can handle. The code will not
//! compile if you try to send a request with the wrong client.
//!
//! ```rust
//! request_group!(MyApi { MyRequest1, MyRequest2 });
//! ```
//! ```rust
//! let my_client = Client::<MyApi>::new("http://example.com");
//! let my_response1 = my_client.send(MyRequest1::new()).await?; // works
//! let other_response = my_client.send(OtherRequest::new()).await?; // does not compile
//! ```
//!
//! ### send_to
//!
//! If you want to restrict the request group, but still want to include the url
//! for every call to `send`, `MyClient` has a `send_to` method that can be used
//! with the default client to specify the url at the call-site.
//! ```rust
//! let my_client = Client::<MyApi>::default();
//! let my_response2 = my_client.send_to("http://example.com", MyRequest2::new()).await?; // works
//! let other_response = my_client.send_to("http://example.com", OtherRequest::new()).await?; // does not compile
//! ```
//!
//! The send_to method can also be used to insert a string after the base_url
//! and before the Request path.
//!
//! ```rust
//! let my_client = Client::new("http://example.com");
//! let my_response = my_client.send_to("/api/v2", MyRequest::new()).await?;
//! ```
//!
//! ## Cargo Features
//!
//! Typically, the default features should be fine:
//!
//! ```toml
//! http-typed = "0.3"
//! ```
//!
//! The default features include the full Client implementation, and depend on
//! system tls libraries.
//!
//! All features:
//!
//! - **default** = ["client", "native-tls"]
//! - **client**: Includes the Client implementation described above and depends
//!   on reqwest.
//! - **native-tls**: Depend on dynamically linked system tls libraries.
//! - **rustls-tls**: Statically link all tls dependencies with webpki, no tls
//!   is required in the system.
//!
//!
//! ### No system tls? Use rustls
//!
//! To statically link the tls dependencies, use this:
//!
//! ```toml
//! http-typed = { version = "0.3", default-features = false, features = ["client", "rustls-tls"] }
//! ```
//!
//! ### No Client
//!
//! If you'd like to exclude the `Client` implementation and all of its
//! dependencies on reqwest and tls libraries, use this:
//!
//! ```toml
//! http-typed = { version = "0.3", default-features = false }
//! ```
//!
//! This allows you, as a server developer, to exclude unnecessary dependencies
//! from your server. For example, you may have an API crate with all the
//! request and response structs, which you both import in the server and also
//! make available to clients. You can feature gate the client in your API
//! library:
//!
//! ```toml
//! # api library's Cargo.toml
//!
//! [features]
//! default = ["client"]
//! client = ["http-typed/client"]
//! ```
//!
//! ...and then you can disable it in the server's Cargo.toml. Something like
//! this:
//!
//! ```toml
//! # server binary's Cargo.toml
//!
//! [dependencies]
//! my-api-client = { path = "../client", default-features = false }
//! ```
//!
//! A similar pattern can be used to give clients the option between native-tls
//! and rustls-tls.
//!
//! ## API Client Design
//! Normally, a you might implement a custom client struct to connect to an API,
//! including a custom method for every request. In doing so, you've forced all
//! dependents of the API to make a choice between two options:
//! 1. use the specific custom client struct that was already implemented,
//!    accepting any issues with it.
//! 2. implement a custom client from scratch, re-writing and maintaining all
//!    the details about each request, including what http method to use, what
//!    path to use, how to serialize/deserialized the message, etc.
//!
//! Instead, you can describe the metadata through trait definitions for
//! ultimate flexibility, without locking dependents into a client
//! implementation or needing to implement any custom clients structs.
//! Dependents of the API now have better options:
//! 1. use the Client struct provided by http-typed, accepting any issues with
//!    it. This is easier for you to support because you don't need to worry
//!    about implementation details of sending requests in general. You can just
//!    export or alias the `Client` struct.
//! 2. implement a custom client that can generically handle types implementing
//!    Request by using the data returned by their methods. This is easier for
//!    dependents because they don't need to write any request-specific code.
//!    The Request trait exposes that information without locking them into a
//!    client implementation. Only a single generic request handler is
//!    sufficient.
//!
//! ## Other use cases
//! If your use case does not meet some of the conditions 2-7 described in the
//! introduction, you'll find my other crate useful, which individually
//! generalizes each of those, allowing any of them to be individually
//! customized with minimal boilerplate. It is currently a work in progress, but
//! almost complete. This crate and that crate will be source-code-compatible,
//! meaning the other crate can be used as a drop-in replacement of this one
//! without changing any code, just with more customization available.

#[cfg(feature = "client")]
mod client;

use std::convert::Infallible;

#[cfg(feature = "client")]
pub use client::*;

pub trait Request: Sized {
    // TODO: use when stable: https://github.com/rust-lang/rust/issues/29661
    /// Specify a pre-defined approach to serialize a request body. For example:
    /// - SerdeJson
    /// - NoBody
    type Serializer: SerializeBody<Self>;

    /// Type to deserialize from the http response body
    type Response;

    /// HTTP method that the request will be sent with
    fn method(&self) -> HttpMethod;

    /// String to appended to the end of url when sending this request.
    fn path(&self) -> String;
}

pub struct SerdeJson;
pub struct NoBody;

pub trait SerializeBody<T> {
    type Error;
    fn serialize_body(request: &T) -> Result<Vec<u8>, Self::Error>;
}

impl<T> SerializeBody<T> for SerdeJson
where
    T: serde::Serialize,
{
    type Error = serde_json::error::Error;

    fn serialize_body(request: &T) -> Result<Vec<u8>, Self::Error> {
        Ok(serde_json::to_string(&request)?.into_bytes())
    }
}

impl<T> SerializeBody<T> for NoBody {
    type Error = Infallible;

    fn serialize_body(_: &T) -> Result<Vec<u8>, Self::Error> {
        Ok(vec![])
    }
}

/// Define a request group to constrain which requests can be used with a client.
/// ```ignore
/// request_group!(MyApi { MyRequest1, MyRequest2 });
/// ```
#[macro_export]
macro_rules! request_group {
    ($viz:vis $Name:ident { $($Request:ident),*$(,)? }) => {
        $viz struct $Name;
        $(impl $crate::InRequestGroup<$Name> for $Request {})*
    };
}

/// Indicates that a request is part of a request group. If you use the
/// request_group macro to define the group, it will handle the implementation
/// of this trait automatically.
pub trait InRequestGroup<Group> {}

/// The default group. All requests are in this group.
pub struct All;
impl<T> InRequestGroup<All> for T {}

#[derive(Debug, Clone, Copy)]
pub enum HttpMethod {
    Options,
    Get,
    Post,
    Put,
    Delete,
    Head,
    Trace,
    Connect,
    Patch,
}