1 use http::Method;
2 use js_sys::Uint8Array;
3 use std::future::Future;
4 use wasm_bindgen::UnwrapThrowExt as _;
5 use url::Url;
6 
7 use super::{Request, RequestBuilder, Response};
8 use crate::IntoUrl;
9 
10 /// dox
11 #[derive(Clone, Debug)]
12 pub struct Client(());
13 
14 /// dox
15 #[derive(Debug)]
16 pub struct ClientBuilder(());
17 
18 impl Client {
19     /// dox
new() -> Self20     pub fn new() -> Self {
21         Client::builder().build().unwrap_throw()
22     }
23 
24     /// dox
builder() -> ClientBuilder25     pub fn builder() -> ClientBuilder {
26         ClientBuilder::new()
27     }
28 
29     /// Convenience method to make a `GET` request to a URL.
30     ///
31     /// # Errors
32     ///
33     /// This method fails whenever supplied `Url` cannot be parsed.
get<U: IntoUrl>(&self, url: U) -> RequestBuilder34     pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
35         self.request(Method::GET, url)
36     }
37 
38     /// Convenience method to make a `POST` request to a URL.
39     ///
40     /// # Errors
41     ///
42     /// This method fails whenever supplied `Url` cannot be parsed.
post<U: IntoUrl>(&self, url: U) -> RequestBuilder43     pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
44         self.request(Method::POST, url)
45     }
46 
47     /// Convenience method to make a `PUT` request to a URL.
48     ///
49     /// # Errors
50     ///
51     /// This method fails whenever supplied `Url` cannot be parsed.
put<U: IntoUrl>(&self, url: U) -> RequestBuilder52     pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
53         self.request(Method::PUT, url)
54     }
55 
56     /// Convenience method to make a `PATCH` request to a URL.
57     ///
58     /// # Errors
59     ///
60     /// This method fails whenever supplied `Url` cannot be parsed.
patch<U: IntoUrl>(&self, url: U) -> RequestBuilder61     pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
62         self.request(Method::PATCH, url)
63     }
64 
65     /// Convenience method to make a `DELETE` request to a URL.
66     ///
67     /// # Errors
68     ///
69     /// This method fails whenever supplied `Url` cannot be parsed.
delete<U: IntoUrl>(&self, url: U) -> RequestBuilder70     pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
71         self.request(Method::DELETE, url)
72     }
73 
74     /// Convenience method to make a `HEAD` request to a URL.
75     ///
76     /// # Errors
77     ///
78     /// This method fails whenever supplied `Url` cannot be parsed.
head<U: IntoUrl>(&self, url: U) -> RequestBuilder79     pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
80         self.request(Method::HEAD, url)
81     }
82 
83     /// Start building a `Request` with the `Method` and `Url`.
84     ///
85     /// Returns a `RequestBuilder`, which will allow setting headers and
86     /// request body before sending.
87     ///
88     /// # Errors
89     ///
90     /// This method fails whenever supplied `Url` cannot be parsed.
request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder91     pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
92         let req = url.into_url().map(move |url| Request::new(method, url));
93         RequestBuilder::new(self.clone(), req)
94     }
95 
execute_request( &self, req: Request, ) -> impl Future<Output = crate::Result<Response>>96     pub(super) fn execute_request(
97         &self,
98         req: Request,
99     ) -> impl Future<Output = crate::Result<Response>> {
100         fetch(req)
101     }
102 }
103 
fetch(req: Request) -> crate::Result<Response>104 async fn fetch(req: Request) -> crate::Result<Response> {
105     // Build the js Request
106     let mut init = web_sys::RequestInit::new();
107     init.method(req.method().as_str());
108 
109     let js_headers = web_sys::Headers::new()
110         .map_err(crate::error::wasm)
111         .map_err(crate::error::builder)?;
112 
113     for (name, value) in req.headers() {
114         js_headers
115             .append(
116                 name.as_str(),
117                 value.to_str().map_err(crate::error::builder)?,
118             )
119             .map_err(crate::error::wasm)
120             .map_err(crate::error::builder)?;
121     }
122     init.headers(&js_headers.into());
123 
124     // When req.cors is true, do nothing because the default mode is 'cors'
125     if !req.cors {
126         init.mode(web_sys::RequestMode::NoCors);
127     }
128 
129     if let Some(body) = req.body() {
130         let body_bytes: &[u8] = body.bytes();
131         let body_array: Uint8Array = body_bytes.into();
132         init.body(Some(&body_array.into()));
133     }
134 
135     let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
136         .map_err(crate::error::wasm)
137         .map_err(crate::error::builder)?;
138 
139     // Await the fetch() promise
140     let p = web_sys::window()
141         .expect("window should exist")
142         .fetch_with_request(&js_req);
143     let js_resp = super::promise::<web_sys::Response>(p)
144         .await
145         .map_err(crate::error::request)?;
146 
147     // Convert from the js Response
148     let mut resp = http::Response::builder()
149         .status(js_resp.status());
150 
151     let url = Url::parse(&js_resp.url()).expect_throw("url parse");
152 
153     let js_headers = js_resp.headers();
154     let js_iter = js_sys::try_iter(&js_headers)
155         .expect_throw("headers try_iter")
156         .expect_throw("headers have an iterator");
157 
158     for item in js_iter {
159         let item = item.expect_throw("headers iterator doesn't throw");
160         let v: Vec<String> = item.into_serde().expect_throw("headers into_serde");
161         resp = resp.header(
162             v.get(0).expect_throw("headers name"),
163             v.get(1).expect_throw("headers value"),
164         );
165     }
166 
167     resp.body(js_resp)
168         .map(|resp| Response::new(resp, url))
169         .map_err(crate::error::request)
170 }
171 
172 // ===== impl ClientBuilder =====
173 
174 impl ClientBuilder {
175     /// dox
new() -> Self176     pub fn new() -> Self {
177         ClientBuilder(())
178     }
179 
180     /// dox
build(self) -> Result<Client, crate::Error>181     pub fn build(self) -> Result<Client, crate::Error> {
182         Ok(Client(()))
183     }
184 }
185