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
//! The plain `reqwest` transport of generated REST clients, selected with
//! `transport_type_path =` [`REQWEST_TRANSPORT_TYPE_PATH`]: a `reqwest::Client` with an optional
//! bearer token and `User-Agent`. No credential providers, no retries; builds for `wasm32` too.
use quote::quote;
use super::format_tokens_static;
/// Transport type path that selects the generated `reqwest` transport.
pub const REQWEST_TRANSPORT_TYPE_PATH: &str = "crate::codegen::transport::HttpClient";
/// The `transport.rs` file. With `user_agent`, the default transport sends
/// `<user_agent>/<client crate version>`, except in the browser.
pub(crate) fn generate(user_agent: Option<&str>) -> String {
let (derive, default) = match user_agent {
None => (quote! { #[derive(Clone, Default)] }, quote! {}),
Some(product) => {
let prefix = format!("{product}/");
let doc =
format!(" own, its `User-Agent` is `{product}/` and the version of this crate.");
let default = quote! {
impl Default for HttpClient {
/// A transport with a default `reqwest::Client`. Outside the browser, which sends its
#[doc = #doc]
fn default() -> Self {
let client = Self::new(reqwest::Client::default());
#[cfg(not(target_arch = "wasm32"))]
let client =
client.with_user_agent(concat!(#prefix, env!("CARGO_PKG_VERSION")));
client
}
}
};
(quote! { #[derive(Clone)] }, default)
}
};
let tokens = quote! {
use reqwest::{Method, RequestBuilder};
use url::Url;
/// The HTTP transport of the generated clients: a `reqwest::Client`, plus a bearer token
/// and a `User-Agent` that go in every request when set.
#derive
pub struct HttpClient {
client: reqwest::Client,
token: Option<String>,
user_agent: Option<String>,
}
#default
// Hand-written to redact the token.
impl ::std::fmt::Debug for HttpClient {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("HttpClient")
.field("token", &self.token.as_ref().map(|_| "<redacted>"))
.field("user_agent", &self.user_agent)
.finish_non_exhaustive()
}
}
impl HttpClient {
/// A transport over `client`, without a token or `User-Agent`.
pub fn new(client: reqwest::Client) -> Self {
Self { client, token: None, user_agent: None }
}
/// The same transport, sending `token` as the bearer token of every request.
pub fn with_token(mut self, token: impl Into<String>) -> Self {
self.token = Some(token.into());
self
}
/// The same transport, sending `user_agent` as the `User-Agent` of every request.
pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = Some(user_agent.into());
self
}
/// Start a `GET` request.
pub fn get(&self, url: Url) -> RequestBuilder {
self.request(Method::GET, url)
}
/// Start a `POST` request.
pub fn post(&self, url: Url) -> RequestBuilder {
self.request(Method::POST, url)
}
/// Start a `PUT` request.
pub fn put(&self, url: Url) -> RequestBuilder {
self.request(Method::PUT, url)
}
/// Start a `PATCH` request.
pub fn patch(&self, url: Url) -> RequestBuilder {
self.request(Method::PATCH, url)
}
/// Start a `DELETE` request.
pub fn delete(&self, url: Url) -> RequestBuilder {
self.request(Method::DELETE, url)
}
/// Start a `HEAD` request.
pub fn head(&self, url: Url) -> RequestBuilder {
self.request(Method::HEAD, url)
}
/// Start a request with any method.
pub fn request(&self, method: Method, url: Url) -> RequestBuilder {
let mut builder = self.client.request(method, url);
if let Some(user_agent) = &self.user_agent {
builder = builder.header(reqwest::header::USER_AGENT, user_agent);
}
match &self.token {
Some(token) => builder.bearer_auth(token),
None => builder,
}
}
}
};
format_tokens_static(tokens)
}