yew_stdweb/services/
fetch.rs

1//! Service to send HTTP-request to a server.
2
3cfg_if::cfg_if! {
4    if #[cfg(feature = "std_web")] {
5        mod std_web;
6        pub use std_web::*;
7    } else if #[cfg(feature = "web_sys")] {
8        mod web_sys;
9        pub use self::web_sys::*;
10    }
11}
12
13/// Type to set referrer for fetch.
14#[derive(Debug)]
15pub enum Referrer {
16    /// `<same-origin URL>` value of referrer.
17    SameOriginUrl(String),
18    /// `about:client` value of referrer.
19    AboutClient,
20    /// `<empty string>` value of referrer.
21    Empty,
22}
23
24#[cfg(test)]
25#[cfg(all(feature = "wasm_test", feature = "httpbin_test"))]
26mod tests {
27    use super::*;
28    use crate::callback::{test_util::CallbackFuture, Callback};
29    use crate::format::{Json, Nothing};
30    use crate::utils;
31    #[cfg(feature = "web_sys")]
32    use ::web_sys::ReferrerPolicy;
33    use serde::Deserialize;
34    use ssri::Integrity;
35    use std::collections::HashMap;
36    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
37
38    wasm_bindgen_test_configure!(run_in_browser);
39
40    const fn httpbin_base_url() -> &'static str {
41        // we can't do this at runtime because we're running in the browser.
42        env!("HTTPBIN_URL")
43    }
44
45    #[derive(Deserialize, Debug)]
46    struct HttpBin {
47        headers: HashMap<String, String>,
48        origin: String,
49        url: String,
50    }
51
52    #[derive(Deserialize, Debug)]
53    struct HttpBinHeaders {
54        headers: HashMap<String, String>,
55    }
56
57    #[test]
58    async fn fetch_referrer_default() {
59        let request = Request::get(format!("{}/get", httpbin_base_url()))
60            .body(Nothing)
61            .unwrap();
62        let options = FetchOptions::default();
63        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
64        let callback: Callback<_> = cb_future.clone().into();
65        let _task = FetchService::fetch_with_options(request, options, callback);
66        let resp = cb_future.await;
67        assert_eq!(resp.status(), StatusCode::OK);
68        if let Json(Ok(http_bin)) = resp.body() {
69            assert!(http_bin.headers.get("Referer").is_some());
70        } else {
71            assert!(false, "unexpected resp: {:#?}", resp);
72        }
73    }
74
75    #[test]
76    async fn fetch_referrer_same_origin_url() {
77        let request = Request::get(format!("{}/get", httpbin_base_url()))
78            .body(Nothing)
79            .unwrap();
80        let options = FetchOptions {
81            referrer: Some(Referrer::SameOriginUrl(String::from("same-origin"))),
82            referrer_policy: Some(ReferrerPolicy::NoReferrerWhenDowngrade),
83            ..FetchOptions::default()
84        };
85        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
86        let callback: Callback<_> = cb_future.clone().into();
87        let _task = FetchService::fetch_with_options(request, options, callback);
88        let resp = cb_future.await;
89        assert_eq!(resp.status(), StatusCode::OK);
90        if let Json(Ok(http_bin)) = resp.body() {
91            let referrer = http_bin.headers.get("Referer").expect("no referer set");
92            assert!(referrer.ends_with("/same-origin"));
93        } else {
94            assert!(false, "unexpected resp: {:#?}", resp);
95        }
96    }
97
98    #[test]
99    async fn fetch_referrer_about_client() {
100        let request = Request::get(format!("{}/get", httpbin_base_url()))
101            .body(Nothing)
102            .unwrap();
103        let options = FetchOptions {
104            referrer: Some(Referrer::AboutClient),
105            ..FetchOptions::default()
106        };
107        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
108        let callback: Callback<_> = cb_future.clone().into();
109        let _task = FetchService::fetch_with_options(request, options, callback);
110        let resp = cb_future.await;
111        assert_eq!(resp.status(), StatusCode::OK);
112        if let Json(Ok(http_bin)) = resp.body() {
113            assert!(http_bin.headers.get("Referer").is_some());
114        } else {
115            assert!(false, "unexpected resp: {:#?}", resp);
116        }
117    }
118
119    #[test]
120    async fn fetch_referrer_empty() {
121        let request = Request::get(format!("{}/get", httpbin_base_url()))
122            .body(Nothing)
123            .unwrap();
124        let options = FetchOptions {
125            referrer: Some(Referrer::Empty),
126            ..FetchOptions::default()
127        };
128        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
129        let callback: Callback<_> = cb_future.clone().into();
130        let _task = FetchService::fetch_with_options(request, options, callback);
131        let resp = cb_future.await;
132        assert_eq!(resp.status(), StatusCode::OK);
133        if let Json(Ok(http_bin)) = resp.body() {
134            assert!(http_bin.headers.get("Referer").is_none());
135        } else {
136            assert!(false, "unexpected resp: {:#?}", resp);
137        }
138    }
139
140    #[test]
141    async fn fetch_redirect_default() {
142        let request = Request::get(format!("{}/relative-redirect/1", httpbin_base_url()))
143            .body(Nothing)
144            .unwrap();
145        let options = FetchOptions::default();
146        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
147        let callback: Callback<_> = cb_future.clone().into();
148        let _task = FetchService::fetch_with_options(request, options, callback);
149        let resp = cb_future.await;
150        assert_eq!(resp.status(), StatusCode::OK);
151        if let Json(Ok(http_bin)) = resp.body() {
152            assert_eq!(http_bin.url, format!("{}/get", httpbin_base_url()));
153        } else {
154            assert!(false, "unexpected resp: {:#?}", resp);
155        }
156    }
157
158    #[test]
159    async fn fetch_redirect_follow() {
160        let request = Request::get(format!("{}/relative-redirect/1", httpbin_base_url()))
161            .body(Nothing)
162            .unwrap();
163        let options = FetchOptions {
164            redirect: Some(Redirect::Follow),
165            ..FetchOptions::default()
166        };
167        let cb_future = CallbackFuture::<Response<Json<Result<HttpBin, anyhow::Error>>>>::default();
168        let callback: Callback<_> = cb_future.clone().into();
169        let _task = FetchService::fetch_with_options(request, options, callback);
170        let resp = cb_future.await;
171        assert_eq!(resp.status(), StatusCode::OK);
172        if let Json(Ok(http_bin)) = resp.body() {
173            assert_eq!(http_bin.url, format!("{}/get", httpbin_base_url()));
174        } else {
175            assert!(false, "unexpected resp: {:#?}", resp);
176        }
177    }
178
179    #[test]
180    async fn fetch_redirect_error() {
181        let request = Request::get(format!("{}/relative-redirect/1", httpbin_base_url()))
182            .body(Nothing)
183            .unwrap();
184        let options = FetchOptions {
185            redirect: Some(Redirect::Error),
186            ..FetchOptions::default()
187        };
188        let cb_future = CallbackFuture::<Response<Result<String, anyhow::Error>>>::default();
189        let callback: Callback<_> = cb_future.clone().into();
190        let _task = FetchService::fetch_with_options(request, options, callback);
191        let resp = cb_future.await;
192        assert_eq!(resp.status(), StatusCode::REQUEST_TIMEOUT);
193    }
194
195    #[test]
196    async fn fetch_redirect_manual() {
197        let request = Request::get(format!("{}/relative-redirect/1", httpbin_base_url()))
198            .body(Nothing)
199            .unwrap();
200        let options = FetchOptions {
201            redirect: Some(Redirect::Manual),
202            ..FetchOptions::default()
203        };
204        let cb_future = CallbackFuture::<Response<Result<String, anyhow::Error>>>::default();
205        let callback: Callback<_> = cb_future.clone().into();
206        let _task = FetchService::fetch_with_options(request, options, callback);
207        let resp = cb_future.await;
208        assert_eq!(resp.status(), StatusCode::OK);
209        // body is empty because the response is opaque for manual redirects
210        assert_eq!(resp.body().as_ref().unwrap(), &String::from(""));
211    }
212
213    #[test]
214    async fn fetch_integrity() {
215        let resource = "Yew SRI Test";
216        let request = Request::get(format!(
217            "{}/base64/{}",
218            httpbin_base_url(),
219            base64::encode_config(resource, base64::URL_SAFE)
220        ))
221        .body(Nothing)
222        .unwrap();
223        let options = FetchOptions {
224            integrity: Some(Integrity::from(resource).to_string()),
225            ..FetchOptions::default()
226        };
227        let cb_future = CallbackFuture::<Response<Result<String, anyhow::Error>>>::default();
228        let callback: Callback<_> = cb_future.clone().into();
229        let _task = FetchService::fetch_with_options(request, options, callback);
230        let resp = cb_future.await;
231        assert_eq!(resp.status(), StatusCode::OK);
232        assert_eq!(resp.body().as_ref().unwrap(), resource);
233    }
234
235    #[test]
236    async fn fetch_integrity_fail() {
237        let resource = "Yew SRI Test";
238        let request = Request::get(format!(
239            "{}/base64/{}",
240            httpbin_base_url(),
241            base64::encode_config(resource, base64::URL_SAFE)
242        ))
243        .body(Nothing)
244        .unwrap();
245        let options = FetchOptions {
246            integrity: Some(Integrity::from("Yew SRI Test fail").to_string()),
247            ..FetchOptions::default()
248        };
249        let cb_future = CallbackFuture::<Response<Result<String, anyhow::Error>>>::default();
250        let callback: Callback<_> = cb_future.clone().into();
251        let _task = FetchService::fetch_with_options(request, options, callback);
252        let resp = cb_future.await;
253        assert!(resp.body().is_err());
254    }
255
256    #[test]
257    async fn fetch_fail() {
258        let request = Request::get("https://fetch.fail").body(Nothing).unwrap();
259        let cb_future = CallbackFuture::<Response<Result<String, anyhow::Error>>>::default();
260        let callback: Callback<_> = cb_future.clone().into();
261        let _task = FetchService::fetch(request, callback);
262        let resp = cb_future.await;
263        #[cfg(feature = "std_web")]
264        assert!(resp.body().is_err());
265        #[cfg(feature = "web_sys")]
266        assert!(resp
267            .body()
268            .as_ref()
269            .unwrap_err()
270            .to_string()
271            .starts_with("TypeError:"));
272    }
273
274    #[test]
275    async fn fetch_referrer_policy_no_referrer() {
276        let request = Request::get(format!("{}/headers", httpbin_base_url()))
277            .body(Nothing)
278            .unwrap();
279        let options = FetchOptions {
280            referrer_policy: Some(ReferrerPolicy::NoReferrer),
281            ..FetchOptions::default()
282        };
283        let cb_future =
284            CallbackFuture::<Response<Json<Result<HttpBinHeaders, anyhow::Error>>>>::default();
285        let callback: Callback<_> = cb_future.clone().into();
286        let _task = FetchService::fetch_with_options(request, options, callback);
287        let resp = cb_future.await;
288        assert_eq!(resp.status(), StatusCode::OK);
289        if let Json(Ok(httpbin_headers)) = resp.body() {
290            assert_eq!(httpbin_headers.headers.get("Referer"), None);
291        } else {
292            assert!(false, "unexpected resp: {:#?}", resp);
293        }
294    }
295
296    #[test]
297    async fn fetch_referrer_policy_origin() {
298        let request = Request::get(format!("{}/headers", httpbin_base_url()))
299            .body(Nothing)
300            .unwrap();
301        let options = FetchOptions {
302            referrer_policy: Some(ReferrerPolicy::Origin),
303            ..FetchOptions::default()
304        };
305        let cb_future =
306            CallbackFuture::<Response<Json<Result<HttpBinHeaders, anyhow::Error>>>>::default();
307        let callback: Callback<_> = cb_future.clone().into();
308        let _task = FetchService::fetch_with_options(request, options, callback);
309        let resp = cb_future.await;
310        assert_eq!(resp.status(), StatusCode::OK);
311        if let Json(Ok(httpbin_headers)) = resp.body() {
312            assert!(httpbin_headers
313                .headers
314                .get("Referer")
315                .unwrap()
316                .starts_with(&utils::origin().unwrap()));
317        } else {
318            assert!(false, "unexpected resp: {:#?}", resp);
319        }
320    }
321}