1use crate::http::{Headers, QueryParams, Response};
2use crate::{js_to_error, Error};
3use http::Method;
4use js_sys::{ArrayBuffer, Uint8Array};
5use std::convert::{From, TryFrom, TryInto};
6use std::fmt;
7use std::str::FromStr;
8use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
9use wasm_bindgen_futures::JsFuture;
10use web_sys::{
11 AbortSignal, FormData, ObserverCallback, ReadableStream, ReferrerPolicy, RequestCache,
12 RequestCredentials, RequestMode, RequestRedirect,
13};
14
15#[cfg(feature = "json")]
16#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
17use serde::de::DeserializeOwned;
18
19#[wasm_bindgen]
20extern "C" {
21 #[wasm_bindgen(js_name = "fetch")]
27 fn fetch_with_request(request: &web_sys::Request) -> js_sys::Promise;
28}
29
30pub struct RequestBuilder {
32 options: web_sys::RequestInit,
33 headers: Headers,
34 query: QueryParams,
35 url: String,
36}
37
38impl RequestBuilder {
39 pub fn new(url: &str) -> Self {
43 Self {
44 options: web_sys::RequestInit::new(),
45 headers: Headers::new(),
46 query: QueryParams::new(),
47 url: url.into(),
48 }
49 }
50
51 pub fn body(self, body: impl Into<JsValue>) -> Result<Request, Error> {
53 self.options.set_body(&body.into());
54
55 self.try_into()
56 }
57
58 pub fn cache(self, cache: RequestCache) -> Self {
60 self.options.set_cache(cache);
61 self
62 }
63
64 pub fn credentials(self, credentials: RequestCredentials) -> Self {
67 self.options.set_credentials(credentials);
68 self
69 }
70
71 pub fn headers(mut self, headers: Headers) -> Self {
73 self.headers = headers;
74 self
75 }
76
77 pub fn header(self, key: &str, value: &str) -> Self {
79 self.headers.set(key, value);
80 self
81 }
82
83 pub fn query<'a, T, V>(self, params: T) -> Self
111 where
112 T: IntoIterator<Item = (&'a str, V)>,
113 V: AsRef<str>,
114 {
115 for (name, value) in params {
116 self.query.append(name, value.as_ref());
117 }
118 self
119 }
120
121 pub fn integrity(self, integrity: &str) -> Self {
124 self.options.set_integrity(integrity);
125 self
126 }
127
128 #[cfg(feature = "json")]
134 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
135 pub fn json<T: serde::Serialize + ?Sized>(self, value: &T) -> Result<Request, Error> {
136 let json = serde_json::to_string(value)?;
137 self.header("Content-Type", "application/json").body(json)
138 }
139
140 pub fn method(self, method: Method) -> Self {
142 self.options.set_method(method.as_ref());
143 self
144 }
145
146 pub fn mode(self, mode: RequestMode) -> Self {
148 self.options.set_mode(mode);
149 self
150 }
151
152 pub fn observe(self, observe: &ObserverCallback) -> Self {
154 self.options.set_observe(observe);
155 self
156 }
157
158 pub fn redirect(self, redirect: RequestRedirect) -> Self {
166 self.options.set_redirect(redirect);
167 self
168 }
169
170 pub fn referrer(self, referrer: &str) -> Self {
174 self.options.set_referrer(referrer);
175 self
176 }
177
178 pub fn referrer_policy(self, referrer_policy: ReferrerPolicy) -> Self {
182 self.options.set_referrer_policy(referrer_policy);
183 self
184 }
185
186 pub fn abort_signal(self, signal: Option<&AbortSignal>) -> Self {
188 self.options.set_signal(signal);
189 self
190 }
191 pub async fn send(self) -> Result<Response, Error> {
193 let req: Request = self.try_into()?;
194 req.send().await
195 }
196 pub fn build(self) -> Result<Request, crate::error::Error> {
198 self.try_into()
199 }
200}
201
202impl TryFrom<RequestBuilder> for Request {
203 type Error = crate::error::Error;
204
205 fn try_from(value: RequestBuilder) -> Result<Self, Self::Error> {
206 let request = web_sys::Request::new_with_str(&value.url).map_err(js_to_error)?;
210 let url = web_sys::Url::new(&request.url()).map_err(js_to_error)?;
211
212 let url_search = url.search();
213 let combined_query = if url_search.is_empty() {
214 value.query.to_string()
215 } else {
216 let mut query = value.query.to_string();
217 if !query.is_empty() {
218 query = format!("&{query}");
219 }
220 format!("{url_search}{query}")
221 };
222 url.set_search(&combined_query);
223
224 let final_url = String::from(url.to_string());
225 value.options.set_headers(&value.headers.into_raw());
226 let request = web_sys::Request::new_with_str_and_init(&final_url, &value.options)
227 .map_err(js_to_error)?;
228
229 Ok(request.into())
230 }
231}
232
233impl fmt::Debug for RequestBuilder {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 f.debug_struct("Request").field("url", &self.url).finish()
236 }
237}
238
239pub struct Request(web_sys::Request);
241
242impl Request {
243 pub fn get(url: &str) -> RequestBuilder {
245 RequestBuilder::new(url).method(Method::GET)
246 }
247
248 pub fn post(url: &str) -> RequestBuilder {
250 RequestBuilder::new(url).method(Method::POST)
251 }
252
253 pub fn put(url: &str) -> RequestBuilder {
255 RequestBuilder::new(url).method(Method::PUT)
256 }
257
258 pub fn delete(url: &str) -> RequestBuilder {
260 RequestBuilder::new(url).method(Method::DELETE)
261 }
262
263 pub fn patch(url: &str) -> RequestBuilder {
265 RequestBuilder::new(url).method(Method::PATCH)
266 }
267
268 pub fn url(&self) -> String {
270 self.0.url()
271 }
272
273 pub fn headers(&self) -> Headers {
275 Headers::from_raw(self.0.headers())
276 }
277
278 pub fn body_used(&self) -> bool {
282 self.0.body_used()
283 }
284
285 pub fn body(&self) -> Option<ReadableStream> {
287 self.0.body()
288 }
289
290 pub async fn form_data(&self) -> Result<FormData, Error> {
292 let promise = self.0.form_data().map_err(js_to_error)?;
293 let val = JsFuture::from(promise).await.map_err(js_to_error)?;
294 Ok(FormData::from(val))
295 }
296
297 #[cfg(feature = "json")]
299 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
300 pub async fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
301 serde_json::from_str::<T>(&self.text().await?).map_err(Error::from)
302 }
303
304 pub async fn text(&self) -> Result<String, Error> {
306 let promise = self.0.text().unwrap();
307 let val = JsFuture::from(promise).await.map_err(js_to_error)?;
308 let string = js_sys::JsString::from(val);
309 Ok(String::from(&string))
310 }
311
312 pub async fn binary(&self) -> Result<Vec<u8>, Error> {
317 let promise = self.0.array_buffer().map_err(js_to_error)?;
318 let array_buffer: ArrayBuffer = JsFuture::from(promise)
319 .await
320 .map_err(js_to_error)?
321 .unchecked_into();
322 let typed_buff: Uint8Array = Uint8Array::new(&array_buffer);
323 let mut body = vec![0; typed_buff.length() as usize];
324 typed_buff.copy_to(&mut body);
325 Ok(body)
326 }
327
328 pub fn mode(&self) -> RequestMode {
330 self.0.mode()
331 }
332
333 pub fn method(&self) -> Method {
335 Method::from_str(self.0.method().as_str()).unwrap()
336 }
337
338 pub async fn send(self) -> Result<Response, Error> {
340 let request = self.0;
341 let promise = fetch_with_request(&request);
342 let response = JsFuture::from(promise).await.map_err(js_to_error)?;
343 response
344 .dyn_into::<web_sys::Response>()
345 .map_err(|e| panic!("fetch returned {:?}, not `Response` - this is a bug", e))
346 .map(Response::from)
347 }
348}
349
350impl From<web_sys::Request> for Request {
351 fn from(raw: web_sys::Request) -> Self {
352 Request(raw)
353 }
354}
355
356impl From<Request> for web_sys::Request {
357 fn from(val: Request) -> Self {
358 val.0
359 }
360}
361
362impl fmt::Debug for Request {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 f.debug_struct("Request")
365 .field("url", &self.url())
366 .field("headers", &self.headers())
367 .field("body_used", &self.body_used())
368 .finish()
369 }
370}