awsm_web/loaders/
fetch.rs

1//! These fetches are idiomatic Rusty wrappers around the web_sys fetch primitives
2//! They abort when dropped (just like Rust futures should!)
3//! To abort imperatively (not via dropping the future), use the _abortable variants and pass in an AbortController
4
5use crate::errors::{Error, NativeError};
6use std::ops::Deref;
7//Don't know why awsm_web needs FutureExt but awsm_renderer doesn't...
8use js_sys::ArrayBuffer;
9use wasm_bindgen::JsCast;
10use wasm_bindgen::prelude::*;
11use wasm_bindgen_futures::JsFuture;
12use web_sys::{ Request, AbortSignal,RequestInit, File, Blob};
13
14#[cfg(feature = "serde")]
15use serde::{Serialize, de::DeserializeOwned};
16use super::helpers::AbortController;
17
18/** Thanks for the help Pauan! 
19 */
20
21pub struct Response {
22    response: web_sys::Response,
23    //If the abort controller is created here
24    //needs to last as long as the response
25    _abort: Option<AbortController>,
26}
27
28impl Deref for Response {
29    type Target = web_sys::Response;
30
31    fn deref(&self) -> &Self::Target {
32        &self.response
33    }
34}
35
36impl Response {
37    pub async fn text(self) -> Result<String, Error> {
38        JsFuture::from(
39            self.response
40                .text()
41                .map_err(|err| Error::from(err))?
42        ).await
43            .map_err(|err| Error::from(err))
44            .map(|value| value.as_string().unwrap_throw())
45    }
46
47    pub async fn json_raw(self) -> Result<JsValue, Error> {
48        JsFuture::from(
49            self.response
50                .json()
51                .map_err(|err| Error::from(err))?
52        ).await.map_err(|err| Error::from(err))
53    }
54
55    pub async fn array_buffer(self) -> Result<ArrayBuffer, Error> { 
56        JsFuture::from(
57            self.response
58                .array_buffer()
59                .map_err(|err| Error::from(err))?
60        ).await
61            .map_err(|err| Error::from(err))
62            .map(|value| value.into()) 
63    }
64
65    #[cfg(feature = "serde")]
66    pub async fn json_from_obj<T: DeserializeOwned>(self) -> Result<T, Error> { 
67        let data = self.json_raw().await?;
68
69        serde_wasm_bindgen::from_value(data).map_err(|err| Error::from(JsValue::from(err)))
70    }
71
72    #[cfg(feature = "serde")]
73    pub async fn json_from_str<T: DeserializeOwned>(self) -> Result<T, Error> { 
74        let data = self.text().await?;
75
76        serde_json::from_str(&data).map_err(|err| Error::from(err))
77    }
78
79    #[cfg(feature = "audio")]
80    pub async fn audio(self, ctx: &web_sys::AudioContext) -> Result<web_sys::AudioBuffer, Error> {
81        let buffer = self.array_buffer().await?;
82
83        super::audio::audio_buffer(&buffer, &ctx).await
84    }
85
86    #[cfg(feature = "image")]
87    pub async fn image(self, mime_type:&str) -> Result<web_sys::HtmlImageElement, Error> { 
88        let buffer= self.array_buffer().await?;
89
90        super::image::load_js_value(&buffer, mime_type).await
91    }
92}
93
94/// Warning: Will overwrite the init's signal for aborting in all cases.
95/// Even if abort_controller is None (in this case it creates it locally)
96/// This allows aborting the fetch if the future is dropped
97///
98/// Generally, this is for internal use and it's recommended to use the other helper functions
99/// It's made pub to avoid needing a helper function to cover *every* scenario
100///
101/// The Error type can be checked if it was aborted by calling .is_abort()
102pub async fn fetch_req(req: &Request, abort_controller: Option<&AbortController>, init:&mut RequestInit) -> Result<Response, Error> {
103    //The Response can only take ownership of a locally created abort_controller
104    //But we apply it to the init arg in both cases
105    let abort = match abort_controller {
106        Some(a) => { 
107            init.signal(Some(&a.signal()));
108            None
109        },
110        None => {
111            let a = AbortController::new();
112            init.signal(Some(&a.signal()));
113            Some(a)
114        }
115    };
116
117    let future = web_sys::window()
118        .unwrap_throw()
119        .fetch_with_request_and_init(req, init);
120
121    JsFuture::from(future).await
122        .map(|response| {
123            let response = response.unchecked_into::<web_sys::Response>();
124            Response { response, _abort: abort }
125        })
126        .map_err(|err| err.into())
127
128}
129
130pub async fn fetch_url(url:&str) -> Result<Response, Error> {
131    fetch_url_abortable(url, None).await
132}
133pub async fn fetch_url_abortable(url:&str, abort_controller: Option<&AbortController>) -> Result<Response, Error> {
134    fetch_req(&Request::new_with_str(url)?, abort_controller, &mut RequestInit::new()).await
135}
136
137pub async fn fetch_with_headers<A: AsRef<str>, B: AsRef<str>>(url: &str, method:&str, include_credentials: bool, pairs: &[(A, B)]) -> Result<Response, Error> {
138    fetch_with_headers_abortable(url, method, include_credentials, None, pairs).await
139}
140
141pub async fn fetch_with_headers_abortable<A: AsRef<str>, B: AsRef<str>>(url: &str, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, pairs: &[(A, B)]) -> Result<Response, Error> {
142    let mut req_init = web_sys::RequestInit::new();
143    req_init.method(method);
144    if include_credentials {
145        req_init.credentials(web_sys::RequestCredentials::Include);
146    }
147    
148
149    let req = web_sys::Request::new_with_str_and_init(url, &req_init)?;
150
151    let headers = req.headers();
152
153    for (name, value) in pairs.iter() {
154        headers.set(name.as_ref(), value.as_ref())?;
155    }
156
157    fetch_req(&req, abort_controller, &mut req_init).await
158}
159
160pub async fn fetch_upload_body_with_headers<A: AsRef<str>, B: AsRef<str>>(url: &str, body:&JsValue, method:&str, include_credentials: bool, pairs: &[(A, B)]) -> Result<Response, Error> {
161    fetch_upload_body_with_headers_abortable(url, body, method, include_credentials, None, pairs).await
162}
163
164pub async fn fetch_upload_body_with_headers_abortable<A: AsRef<str>, B: AsRef<str>>(url: &str, body:&JsValue, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, pairs: &[(A, B)]) -> Result<Response, Error> {
165    let mut req_init = web_sys::RequestInit::new();
166    req_init.method(method);
167    if include_credentials {
168        req_init.credentials(web_sys::RequestCredentials::Include);
169    }
170    req_init.body(Some(body));
171    
172
173    let req = web_sys::Request::new_with_str_and_init(url, &req_init)?;
174
175    let headers = req.headers();
176
177    for (name, value) in pairs.iter() {
178        headers.set(name.as_ref(), value.as_ref())?;
179    }
180
181    fetch_req(&req, abort_controller, &mut req_init).await
182}
183pub async fn fetch_upload_body(url:&str, body:&JsValue, method:&str) -> Result<Response, Error> {
184    fetch_upload_body_abortable(url, body, method, None).await
185}
186
187pub async fn fetch_upload_body_abortable(url:&str, body:&JsValue, method:&str, abort_controller: Option<&AbortController>) -> Result<Response, Error> {
188    let mut req_init = web_sys::RequestInit::new();
189    req_init.method(method);
190    req_init.body(Some(body));
191
192    let req = web_sys::Request::new_with_str_and_init(url, &req_init)?;
193
194    fetch_req(&req, abort_controller, &mut req_init).await
195
196}
197
198pub async fn fetch_upload_blob_with_headers<A: AsRef<str>, B: AsRef<str>>(url: &str, blob:&Blob, method:&str, include_credentials: bool, pairs: &[(A, B)]) -> Result<Response, Error> {
199    fetch_upload_blob_with_headers_abortable(url, blob, method, include_credentials, None, pairs).await
200}
201
202pub async fn fetch_upload_blob_with_headers_abortable<A: AsRef<str>, B: AsRef<str>>(url: &str, blob:&Blob, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, pairs: &[(A, B)]) -> Result<Response, Error> {
203    fetch_upload_body_with_headers_abortable(url, blob, method, include_credentials, abort_controller, pairs).await
204}
205
206pub async fn fetch_upload_blob(url:&str, blob:&Blob, method:&str) -> Result<Response, Error> {
207    fetch_upload_blob_abortable(url, blob, method, None).await
208}
209
210pub async fn fetch_upload_blob_abortable(url:&str, blob:&Blob, method:&str, abort_controller: Option<&AbortController>) -> Result<Response, Error> {
211    fetch_upload_body_abortable(url, blob, method, abort_controller).await
212}
213
214pub async fn fetch_upload_file_with_headers<A: AsRef<str>, B: AsRef<str>>(url: &str, file:&File, method:&str, include_credentials: bool, pairs: &[(A, B)]) -> Result<Response, Error> {
215    fetch_upload_file_with_headers_abortable(url, file, method, include_credentials, None, pairs).await
216}
217
218pub async fn fetch_upload_file_with_headers_abortable<A: AsRef<str>, B: AsRef<str>>(url: &str, file:&File, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, pairs: &[(A, B)]) -> Result<Response, Error> {
219    fetch_upload_body_with_headers_abortable(url, file, method, include_credentials, abort_controller, pairs).await
220}
221
222pub async fn fetch_upload_file(url:&str, file:&File, method:&str) -> Result<Response, Error> {
223    fetch_upload_file_abortable(url, file, method, None).await
224}
225
226pub async fn fetch_upload_file_abortable(url:&str, file:&File, method:&str, abort_controller: Option<&AbortController>) -> Result<Response, Error> {
227    fetch_upload_body_abortable(url, file, method, abort_controller).await
228}
229
230#[cfg(feature = "serde_json")]
231pub async fn fetch_with_data(url: &str, method:&str, include_credentials: bool, data:Option<impl Serialize>) -> Result<Response, Error> {
232    fetch_with_data_abortable(url, method, include_credentials, None, data).await
233}
234
235#[cfg(feature = "serde_json")]
236pub async fn fetch_with_data_abortable(url: &str, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, data:Option<impl Serialize>) -> Result<Response, Error> {
237    let mut req_init = web_sys::RequestInit::new();
238    req_init.method(method);
239    if include_credentials {
240        req_init.credentials(web_sys::RequestCredentials::Include);
241    }
242    
243
244    let req = match data {
245        None => web_sys::Request::new_with_str_and_init(url, &req_init)?,
246
247        Some(data) => {
248            let json_str = serde_json::to_string(&data).map_err(|err| JsValue::from_str(&err.to_string()))?;
249            //req_init.mode(web_sys::RequestMode::Cors);
250            req_init.body(Some(&JsValue::from_str(&json_str)));
251            let req = web_sys::Request::new_with_str_and_init(url, &req_init)?;
252            req.headers().set("Content-Type", "application/json")?;
253
254            req
255        }
256    };
257
258    fetch_req(&req, abort_controller, &mut req_init).await
259}
260
261
262#[cfg(feature = "serde_json")]
263pub async fn fetch_with_headers_and_data<A: AsRef<str>, B: AsRef<str>>(url: &str, method:&str, include_credentials: bool, pairs: &[(A, B)], data:Option<impl Serialize>) -> Result<Response, Error> {
264    fetch_with_headers_and_data_abortable(url, method, include_credentials, None, pairs, data).await
265}
266
267#[cfg(feature = "serde_json")]
268pub async fn fetch_with_headers_and_data_abortable<A: AsRef<str>, B: AsRef<str>>(url: &str, method:&str, include_credentials: bool, abort_controller: Option<&AbortController>, pairs: &[(A, B)], data:Option<impl Serialize>) -> Result<Response, Error> {
269    let mut req_init = web_sys::RequestInit::new();
270    req_init.method(method);
271    if include_credentials {
272        req_init.credentials(web_sys::RequestCredentials::Include);
273    }
274    
275
276    let req = match data {
277        None => web_sys::Request::new_with_str_and_init(url, &req_init)?,
278
279        Some(data) => {
280            let json_str = serde_json::to_string(&data).map_err(|err| JsValue::from_str(&err.to_string()))?;
281            //req_init.mode(web_sys::RequestMode::Cors);
282            req_init.body(Some(&JsValue::from_str(&json_str)));
283            let req = web_sys::Request::new_with_str_and_init(url, &req_init)?;
284            req.headers().set("Content-Type", "application/json")?;
285
286            req
287        }
288    };
289
290    let headers = req.headers();
291
292    for (name, value) in pairs.iter() {
293        headers.set(name.as_ref(), value.as_ref())?;
294    }
295
296    fetch_req(&req, abort_controller, &mut req_init).await
297}