1use crate::{AbortSignal, Error, Method, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10use std::sync::{Mutex, OnceLock};
11use web_sys::RequestMode;
12
13impl From<reqwest::Error> for Error {
14 fn from(err: reqwest::Error) -> Self {
15 Error::Transport(err.to_string())
16 }
17}
18
19fn client() -> &'static reqwest::Client {
21 static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
22 CLIENT.get_or_init(reqwest::Client::new)
23}
24
25pub struct RequestBuilder {
27 url: String,
28 method: Method,
29 headers: HashMap<String, String>,
30 body: Option<String>,
31 signal: Option<AbortSignal>,
32}
33
34impl RequestBuilder {
35 pub(crate) fn new(method: Method, url: impl Into<String>) -> Self {
36 Self {
37 url: url.into(),
38 method,
39 headers: HashMap::new(),
40 body: None,
41 signal: None,
42 }
43 }
44
45 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
47 self.headers.insert(key.into(), value.into());
48 self
49 }
50
51 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
53 self.headers.extend(headers);
54 self
55 }
56
57 pub fn mode(self, _mode: RequestMode) -> Self {
60 self
61 }
62
63 pub fn body(mut self, body: impl Into<String>) -> Self {
65 self.body = Some(body.into());
66 self
67 }
68
69 pub fn json<T: Serialize>(mut self, json: &T) -> Result<Self> {
71 let body = serde_json::to_string(json)?;
72 self.body = Some(body);
73 self.headers
74 .insert("Content-Type".to_string(), "application/json".to_string());
75 Ok(self)
76 }
77
78 pub fn abort_signal(mut self, signal: impl Into<AbortSignal>) -> Self {
80 self.signal = Some(signal.into());
81 self
82 }
83
84 pub async fn send(mut self) -> Result<Response> {
86 let signal = self.signal.take();
87 let response = until(signal.as_ref(), self.send_inner()).await?;
88 Ok(Response { signal, ..response })
89 }
90
91 async fn send_inner(self) -> Result<Response> {
92 let method = reqwest::Method::from_bytes(self.method.as_str().as_bytes())
93 .expect("Method::as_str is always a valid HTTP method");
94 let mut request = client().request(method, &self.url);
95 for (key, value) in &self.headers {
96 request = request.header(key, value);
97 }
98 if let Some(body) = self.body {
99 request = request.body(body);
100 }
101
102 let response = request.send().await?;
103 Ok(Response {
104 status: response.status().as_u16(),
105 ok: response.status().is_success(),
106 headers: response.headers().clone(),
107 body: Body::new(response),
108 signal: None,
109 })
110 }
111}
112
113async fn until<T>(
115 signal: Option<&AbortSignal>,
116 future: impl std::future::Future<Output = Result<T>>,
117) -> Result<T> {
118 match signal {
119 Some(signal) => signal.until(future).await.map_err(|_| Error::Aborted)?,
120 None => future.await,
121 }
122}
123
124struct Body(Mutex<Option<reqwest::Response>>);
127
128impl Body {
129 fn new(response: reqwest::Response) -> Self {
130 Self(Mutex::new(Some(response)))
131 }
132
133 fn take(&self) -> Result<reqwest::Response> {
134 self.0
135 .lock()
136 .unwrap_or_else(|poisoned| poisoned.into_inner())
137 .take()
138 .ok_or_else(|| Error::Transport("body already consumed".to_string()))
139 }
140
141 fn put_back(&self, response: reqwest::Response) {
142 *self
143 .0
144 .lock()
145 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(response);
146 }
147}
148
149pub struct Response {
152 status: u16,
153 ok: bool,
154 headers: reqwest::header::HeaderMap,
155 body: Body,
156 signal: Option<AbortSignal>,
157}
158
159impl Response {
160 pub fn status(&self) -> u16 {
162 self.status
163 }
164
165 pub fn ok(&self) -> bool {
167 self.ok
168 }
169
170 pub fn header(&self, name: &str) -> Result<Option<String>> {
172 match self.headers.get(name) {
173 Some(value) => Ok(Some(
174 value
175 .to_str()
176 .map_err(|e| Error::Transport(format!("non-UTF-8 header value: {e}")))?
177 .to_string(),
178 )),
179 None => Ok(None),
180 }
181 }
182
183 pub async fn text(&self) -> Result<String> {
186 Ok(String::from_utf8_lossy(&self.bytes().await?).into_owned())
187 }
188
189 pub async fn json<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
191 Ok(serde_json::from_slice(&self.bytes().await?)?)
192 }
193
194 pub async fn json_value(&self) -> Result<Value> {
196 self.json().await
197 }
198
199 pub async fn bytes(&self) -> Result<Vec<u8>> {
201 let response = self.body.take()?;
202 let bytes = until(self.signal.as_ref(), async { Ok(response.bytes().await?) }).await?;
203 Ok(bytes.to_vec())
204 }
205
206 pub fn error_for_status(self) -> Result<Self> {
208 if self.ok() {
209 Ok(self)
210 } else {
211 let status = self.status();
212 let text = format!("HTTP Error {}", status);
213 Err(Error::HttpError(status, text))
214 }
215 }
216
217 pub fn stream_reader(&self) -> Result<StreamReader> {
220 Ok(StreamReader {
221 body: Body::new(self.body.take()?),
222 signal: self.signal.clone(),
223 })
224 }
225}
226
227pub struct StreamReader {
229 body: Body,
230 signal: Option<AbortSignal>,
231}
232
233impl StreamReader {
234 pub async fn read_chunk(&self) -> Result<Option<Vec<u8>>> {
238 let mut response = self.body.take()?;
239 let chunk = until(self.signal.as_ref(), async { Ok(response.chunk().await?) }).await?;
240 self.body.put_back(response);
242 Ok(chunk.map(|chunk| chunk.to_vec()))
243 }
244
245 pub fn cancel(self) -> Result<()> {
247 Ok(())
248 }
249}