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(mut self, body: impl Into<JsValue>) -> Result<Request, Error> {
53 self.options.body(Some(&body.into()));
54
55 self.try_into()
56 }
57
58 pub fn cache(mut self, cache: RequestCache) -> Self {
60 self.options.cache(cache);
61 self
62 }
63
64 pub fn credentials(mut self, credentials: RequestCredentials) -> Self {
67 self.options.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(mut self, integrity: &str) -> Self {
124 self.options.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(mut self, method: Method) -> Self {
142 self.options.method(method.as_ref());
143 self
144 }
145
146 pub fn mode(mut self, mode: RequestMode) -> Self {
148 self.options.mode(mode);
149 self
150 }
151
152 pub fn observe(mut self, observe: &ObserverCallback) -> Self {
154 self.options.observe(observe);
155 self
156 }
157
158 pub fn redirect(mut self, redirect: RequestRedirect) -> Self {
166 self.options.redirect(redirect);
167 self
168 }
169
170 pub fn referrer(mut self, referrer: &str) -> Self {
174 self.options.referrer(referrer);
175 self
176 }
177
178 pub fn referrer_policy(mut self, referrer_policy: ReferrerPolicy) -> Self {
182 self.options.referrer_policy(referrer_policy);
183 self
184 }
185
186 pub fn abort_signal(mut self, signal: Option<&AbortSignal>) -> Self {
188 self.options.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(mut 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 let combined_query = match url.search().as_str() {
212 "" => value.query.to_string(),
213 _ => format!("{}&{}", url.search(), value.query),
214 };
215 url.set_search(&combined_query);
216
217 let final_url = String::from(url.to_string());
218 value.options.headers(&value.headers.into_raw());
219 let request = web_sys::Request::new_with_str_and_init(&final_url, &value.options)
220 .map_err(js_to_error)?;
221
222 Ok(request.into())
223 }
224}
225
226impl fmt::Debug for RequestBuilder {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 f.debug_struct("Request").field("url", &self.url).finish()
229 }
230}
231
232pub struct Request(web_sys::Request);
234
235impl Request {
236 pub fn get(url: &str) -> RequestBuilder {
238 RequestBuilder::new(url).method(Method::GET)
239 }
240
241 pub fn post(url: &str) -> RequestBuilder {
243 RequestBuilder::new(url).method(Method::POST)
244 }
245
246 pub fn put(url: &str) -> RequestBuilder {
248 RequestBuilder::new(url).method(Method::PUT)
249 }
250
251 pub fn delete(url: &str) -> RequestBuilder {
253 RequestBuilder::new(url).method(Method::DELETE)
254 }
255
256 pub fn patch(url: &str) -> RequestBuilder {
258 RequestBuilder::new(url).method(Method::PATCH)
259 }
260
261 pub fn url(&self) -> String {
263 self.0.url()
264 }
265
266 pub fn headers(&self) -> Headers {
268 Headers::from_raw(self.0.headers())
269 }
270
271 pub fn body_used(&self) -> bool {
275 self.0.body_used()
276 }
277
278 pub fn body(&self) -> Option<ReadableStream> {
280 self.0.body()
281 }
282
283 pub async fn form_data(&self) -> Result<FormData, Error> {
285 let promise = self.0.form_data().map_err(js_to_error)?;
286 let val = JsFuture::from(promise).await.map_err(js_to_error)?;
287 Ok(FormData::from(val))
288 }
289
290 #[cfg(feature = "json")]
292 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
293 pub async fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
294 serde_json::from_str::<T>(&self.text().await?).map_err(Error::from)
295 }
296
297 pub async fn text(&self) -> Result<String, Error> {
299 let promise = self.0.text().unwrap();
300 let val = JsFuture::from(promise).await.map_err(js_to_error)?;
301 let string = js_sys::JsString::from(val);
302 Ok(String::from(&string))
303 }
304
305 pub async fn binary(&self) -> Result<Vec<u8>, Error> {
310 let promise = self.0.array_buffer().map_err(js_to_error)?;
311 let array_buffer: ArrayBuffer = JsFuture::from(promise)
312 .await
313 .map_err(js_to_error)?
314 .unchecked_into();
315 let typed_buff: Uint8Array = Uint8Array::new(&array_buffer);
316 let mut body = vec![0; typed_buff.length() as usize];
317 typed_buff.copy_to(&mut body);
318 Ok(body)
319 }
320
321 pub fn mode(&self) -> RequestMode {
323 self.0.mode()
324 }
325
326 pub fn method(&self) -> Method {
328 Method::from_str(self.0.method().as_str()).unwrap()
329 }
330
331 pub async fn send(self) -> Result<Response, Error> {
333 let request = self.0;
334 let promise = fetch_with_request(&request);
335 let response = JsFuture::from(promise).await.map_err(js_to_error)?;
336 response
337 .dyn_into::<web_sys::Response>()
338 .map_err(|e| panic!("fetch returned {:?}, not `Response` - this is a bug", e))
339 .map(Response::from)
340 }
341}
342
343impl From<web_sys::Request> for Request {
344 fn from(raw: web_sys::Request) -> Self {
345 Request(raw)
346 }
347}
348
349impl From<Request> for web_sys::Request {
350 fn from(val: Request) -> Self {
351 val.0
352 }
353}
354
355impl fmt::Debug for Request {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 f.debug_struct("Request")
358 .field("url", &self.url())
359 .field("headers", &self.headers())
360 .field("body_used", &self.body_used())
361 .finish()
362 }
363}