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