1use anyhow::{Context, Result};
2use reqwest::{
3 Client, Method, Response,
4 header::{HeaderMap, HeaderName, HeaderValue},
5};
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8use std::time::Duration;
9use url::Url;
10
11use crate::rt;
12
13#[derive(Clone, Debug, Default)]
16pub struct HttpBuilder {
17 timeout: Option<Duration>,
18 danger_accept_invalid_certs: bool,
19 danger_accept_invalid_hostnames: bool,
20 proxy: Option<Url>,
21 default_headers: Option<HeaderMap>,
22}
23
24impl HttpBuilder {
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn timeout(mut self, duration: Duration) -> Self {
30 self.timeout = Some(duration);
31 self
32 }
33
34 pub fn danger_accept_invalid_certs(mut self, on: bool) -> Self {
35 self.danger_accept_invalid_certs = on;
36 self
37 }
38
39 pub fn danger_accept_invalid_hostnames(mut self, on: bool) -> Self {
40 self.danger_accept_invalid_hostnames = on;
41 self
42 }
43
44 pub fn insecure_tls(mut self, on: bool) -> Self {
46 self.danger_accept_invalid_certs = on;
47 self.danger_accept_invalid_hostnames = on;
48 self
49 }
50
51 pub fn proxy(mut self, url: Option<Url>) -> Self {
52 self.proxy = url;
53 self
54 }
55
56 pub fn default_headers(mut self, headers: HeaderMap) -> Self {
57 self.default_headers = Some(headers);
58 self
59 }
60
61 pub fn build(self) -> Result<Http> {
62 let mut builder = Client::builder().use_rustls_tls();
63 if let Some(timeout) = self.timeout {
64 builder = builder.timeout(timeout);
65 }
66 builder = builder
67 .danger_accept_invalid_certs(self.danger_accept_invalid_certs)
68 .danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames);
69 if let Some(proxy_url) = self.proxy {
70 let proxy = reqwest::Proxy::all(proxy_url.as_str())
71 .with_context(|| format!("invalid proxy url: {proxy_url}"))?;
72 builder = builder.proxy(proxy);
73 }
74 if let Some(headers) = self.default_headers {
75 builder = builder.default_headers(headers);
76 }
77 Http::from_builder(builder)
78 }
79}
80
81#[derive(Clone)]
83pub struct Http {
84 client: Client,
85}
86
87impl Http {
88 pub fn new(timeout: Duration) -> Result<Self> {
90 Self::builder().timeout(timeout).build()
91 }
92
93 pub fn from_builder(builder: reqwest::ClientBuilder) -> Result<Self> {
95 Ok(Self {
96 client: builder.build().context("failed to build HTTP client")?,
97 })
98 }
99
100 pub fn builder() -> HttpBuilder {
102 HttpBuilder::new()
103 }
104
105 pub fn request(&self, method: Method, url: impl AsRef<str>) -> HttpRequest {
107 let url = url.as_ref();
108 let builder = self.client.request(method, url);
109 HttpRequest { builder }
110 }
111
112 pub fn get(&self, url: impl AsRef<str>) -> HttpRequest {
113 self.request(Method::GET, url)
114 }
115
116 pub fn post(&self, url: impl AsRef<str>) -> HttpRequest {
117 self.request(Method::POST, url)
118 }
119
120 pub fn put(&self, url: impl AsRef<str>) -> HttpRequest {
121 self.request(Method::PUT, url)
122 }
123
124 pub fn delete(&self, url: impl AsRef<str>) -> HttpRequest {
125 self.request(Method::DELETE, url)
126 }
127
128 pub fn client(&self) -> &Client {
129 &self.client
130 }
131}
132
133pub struct HttpRequest {
134 builder: reqwest::RequestBuilder,
135}
136
137impl HttpRequest {
138 pub fn bearer_auth(mut self, token: impl AsRef<str>) -> Self {
139 self.builder = self.builder.bearer_auth(token.as_ref());
140 self
141 }
142
143 pub fn header(mut self, name: HeaderName, value: HeaderValue) -> Self {
144 self.builder = self.builder.header(name, value);
145 self
146 }
147
148 pub fn headers(mut self, headers: HeaderMap) -> Self {
149 self.builder = self.builder.headers(headers);
150 self
151 }
152
153 pub fn json(mut self, value: &impl Serialize) -> Self {
154 self.builder = self.builder.json(value);
155 self
156 }
157
158 pub fn body(mut self, value: impl Into<reqwest::Body>) -> Self {
159 self.builder = self.builder.body(value);
160 self
161 }
162
163 pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> Self {
164 self.builder = self.builder.query(query);
165 self
166 }
167
168 pub fn form(mut self, value: &impl Serialize) -> Self {
169 self.builder = self.builder.form(value);
170 self
171 }
172
173 pub fn send(self) -> Result<HttpResponse> {
174 rt::sync_await(async {
175 let response = self.builder.send().await?;
176 Ok(HttpResponse { inner: response })
177 })
178 }
179
180 pub fn send_json<T: DeserializeOwned>(self) -> Result<T> {
181 let response = self.send()?;
182 response.json()
183 }
184
185 pub fn send_text(self) -> Result<String> {
186 let response = self.send()?;
187 response.text()
188 }
189}
190
191pub struct HttpResponse {
192 inner: Response,
193}
194
195impl HttpResponse {
196 pub fn status(&self) -> reqwest::StatusCode {
197 self.inner.status()
198 }
199
200 pub fn headers(&self) -> &HeaderMap {
201 self.inner.headers()
202 }
203
204 pub fn into_inner(self) -> Response {
205 self.inner
206 }
207
208 pub fn json<T: DeserializeOwned>(self) -> Result<T> {
209 rt::sync_await(async {
210 self.inner
211 .json::<T>()
212 .await
213 .context("failed to decode JSON")
214 })
215 }
216
217 pub fn text(self) -> Result<String> {
218 rt::sync_await(async {
219 self.inner
220 .text()
221 .await
222 .context("failed to read body as text")
223 })
224 }
225}