From 093a61f0d8e649fc2b30372847e66920bda6714d Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 9 May 2023 23:28:56 -0400 Subject: [PATCH 01/10] add getter methods and impl clone responsebuilder --- crates/net/src/http/response.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/net/src/http/response.rs b/crates/net/src/http/response.rs index 3f1e6f94..94ddae58 100644 --- a/crates/net/src/http/response.rs +++ b/crates/net/src/http/response.rs @@ -177,6 +177,23 @@ impl ResponseBuilder { self } + /// Get a reference to the contained headers + pub fn get_headers(&self) -> &Headers { + &self.headers + } + + /// Get the contained status code if it exists + pub fn get_status(&self) -> Option { + js_sys::Reflect::get(&self.options, &JsValue::from_str("status")).ok()?.as_f64() + } + + /// Get the contained status text if it exists + pub fn get_status_text(&self) -> Option { + js_sys::Reflect::get(&self.options, &JsValue::from_str("statusText")) + .ok()? + .as_string() + } + /// Set the status code pub fn status(mut self, status: u16) -> Self { self.options.status(status); @@ -279,3 +296,15 @@ impl fmt::Debug for ResponseBuilder { .finish_non_exhaustive() } } + +impl Clone for ResponseBuilder { + fn clone(&self) -> Self { + let headers = Headers::new(); + for (name, value) in self.headers.entries() { + headers.append(name.as_str(), value.as_str()) + } + let options = self.options.clone(); + Self { headers, options } + } + +} From 4278e9a493461a02d645cbe8d44fe64f694c10ba Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 9 May 2023 23:30:07 -0400 Subject: [PATCH 02/10] expose responsebuilder --- crates/net/src/http/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/net/src/http/mod.rs b/crates/net/src/http/mod.rs index 77802502..d08784f0 100644 --- a/crates/net/src/http/mod.rs +++ b/crates/net/src/http/mod.rs @@ -24,4 +24,4 @@ pub use http::Method; pub use query::QueryParams; pub use request::Request; -pub use response::{IntoRawResponse, Response}; +pub use response::{IntoRawResponse, Response, ResponseBuilder}; From d450642988fc7f4585b5867bd4aec5dd6aa527d6 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 9 May 2023 23:30:42 -0400 Subject: [PATCH 03/10] add try clone method --- crates/net/src/http/request.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/net/src/http/request.rs b/crates/net/src/http/request.rs index 537fc264..0a652366 100644 --- a/crates/net/src/http/request.rs +++ b/crates/net/src/http/request.rs @@ -343,6 +343,11 @@ impl Request { .map_err(|e| panic!("fetch returned {:?}, not `Response` - this is a bug", e)) .map(Response::from) } + /// attempts to clone the request via the Request.clone api + pub fn try_clone(&self) -> Result { + let clone = self.0.clone().map_err(js_to_error)?; + Ok(Self(clone)) + } } impl From for Request { From 9233fa42fb762a11ac9608794f6a3b9357ee27d8 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 9 May 2023 23:44:59 -0400 Subject: [PATCH 04/10] cargo fmt --- crates/net/src/http/response.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/net/src/http/response.rs b/crates/net/src/http/response.rs index 94ddae58..a9b456d1 100644 --- a/crates/net/src/http/response.rs +++ b/crates/net/src/http/response.rs @@ -184,7 +184,9 @@ impl ResponseBuilder { /// Get the contained status code if it exists pub fn get_status(&self) -> Option { - js_sys::Reflect::get(&self.options, &JsValue::from_str("status")).ok()?.as_f64() + js_sys::Reflect::get(&self.options, &JsValue::from_str("status")) + .ok()? + .as_f64() } /// Get the contained status text if it exists @@ -306,5 +308,4 @@ impl Clone for ResponseBuilder { let options = self.options.clone(); Self { headers, options } } - } From ca681834967dddd2a689ea7caf5a531041fb2b34 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Wed, 10 May 2023 22:04:36 -0400 Subject: [PATCH 05/10] take ownership of request when reading body --- crates/net/src/http/request.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/net/src/http/request.rs b/crates/net/src/http/request.rs index 0a652366..6a65d759 100644 --- a/crates/net/src/http/request.rs +++ b/crates/net/src/http/request.rs @@ -265,12 +265,12 @@ impl Request { } /// Gets the body. - pub fn body(&self) -> Option { + pub fn body(self) -> Option { self.0.body() } /// Reads the request to completion, returning it as `FormData`. - pub async fn form_data(&self) -> Result { + pub async fn form_data(self) -> Result { let promise = self.0.form_data().map_err(js_to_error)?; let val = JsFuture::from(promise).await.map_err(js_to_error)?; Ok(FormData::from(val)) @@ -279,12 +279,12 @@ impl Request { /// Reads the request to completion, parsing it as JSON. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - pub async fn json(&self) -> Result { + pub async fn json(self) -> Result { serde_json::from_str::(&self.text().await?).map_err(Error::from) } /// Reads the reqeust as a String. - pub async fn text(&self) -> Result { + pub async fn text(self) -> Result { let promise = self.0.text().unwrap(); let val = JsFuture::from(promise).await.map_err(js_to_error)?; let string = js_sys::JsString::from(val); @@ -295,7 +295,7 @@ impl Request { /// /// This works by obtaining the response as an `ArrayBuffer`, creating a `Uint8Array` from it /// and then converting it to `Vec` - pub async fn binary(&self) -> Result, Error> { + pub async fn binary(self) -> Result, Error> { let promise = self.0.array_buffer().map_err(js_to_error)?; let array_buffer: ArrayBuffer = JsFuture::from(promise) .await From 3af657df005ca534db6a2f732f2e787222cf24fd Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Wed, 10 May 2023 22:20:14 -0400 Subject: [PATCH 06/10] take ownership of response when reading body --- crates/net/src/http/response.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/net/src/http/response.rs b/crates/net/src/http/response.rs index a9b456d1..63dca0c9 100644 --- a/crates/net/src/http/response.rs +++ b/crates/net/src/http/response.rs @@ -84,12 +84,12 @@ impl Response { } /// Gets the body. - pub fn body(&self) -> Option { + pub fn body(self) -> Option { self.0.body() } /// Reads the response to completion, returning it as `FormData`. - pub async fn form_data(&self) -> Result { + pub async fn form_data(self) -> Result { let promise = self.0.form_data().map_err(js_to_error)?; let val = JsFuture::from(promise).await.map_err(js_to_error)?; Ok(web_sys::FormData::from(val)) @@ -98,12 +98,12 @@ impl Response { /// Reads the response to completion, parsing it as JSON. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - pub async fn json(&self) -> Result { + pub async fn json(self) -> Result { serde_json::from_str::(&self.text().await?).map_err(Error::from) } /// Reads the response as a String. - pub async fn text(&self) -> Result { + pub async fn text(self) -> Result { let promise = self.0.text().unwrap(); let val = JsFuture::from(promise).await.map_err(js_to_error)?; let string = js_sys::JsString::from(val); @@ -114,7 +114,7 @@ impl Response { /// /// This works by obtaining the response as an `ArrayBuffer`, creating a `Uint8Array` from it /// and then converting it to `Vec` - pub async fn binary(&self) -> Result, Error> { + pub async fn binary(self) -> Result, Error> { let promise = self.0.array_buffer().map_err(js_to_error)?; let array_buffer: ArrayBuffer = JsFuture::from(promise) .await @@ -125,6 +125,12 @@ impl Response { typed_buff.copy_to(&mut body); Ok(body) } + + /// attempts to clone the request via the Response.clone api + pub fn try_clone(&self) -> Result { + let clone = self.0.clone().map_err(js_to_error)?; + Ok(Self(clone)) + } } impl From for Response { From d6ea45a7b258dd15984c379a376cc626e1bf1bc4 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Wed, 10 May 2023 22:20:30 -0400 Subject: [PATCH 07/10] fix tests --- crates/net/tests/http.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/net/tests/http.rs b/crates/net/tests/http.rs index 7f956a6b..a13c0eee 100644 --- a/crates/net/tests/http.rs +++ b/crates/net/tests/http.rs @@ -26,8 +26,8 @@ async fn fetch_json() { let url = format!("{}/get", *HTTPBIN_URL); let resp = Request::get(&url).send().await.unwrap(); - let json: HttpBin = resp.json().await.unwrap(); assert_eq!(resp.status(), 200); + let json: HttpBin = resp.json().await.unwrap(); assert_eq!(json.url, url); } @@ -53,8 +53,8 @@ async fn gzip_response() { .send() .await .unwrap(); - let json: HttpBin = resp.json().await.unwrap(); assert_eq!(resp.status(), 200); + let json: HttpBin = resp.json().await.unwrap(); assert!(json.gzipped); } @@ -95,8 +95,8 @@ async fn post_json() { .send() .await .unwrap(); - let resp: HttpBin = req.json().await.unwrap(); assert_eq!(req.status(), 200); + let resp: HttpBin = req.json().await.unwrap(); assert_eq!(resp.json.data, "data"); assert_eq!(resp.json.num, 42); } @@ -112,8 +112,8 @@ async fn fetch_binary() { .send() .await .unwrap(); - let json = resp.binary().await.unwrap(); assert_eq!(resp.status(), 200); + let json = resp.binary().await.unwrap(); let json: HttpBin = serde_json::from_slice(&json).unwrap(); assert_eq!(json.data, ""); // default is empty string } From 63b98b7d14bbf3ccdc4af01d07fb9789317b8943 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 18 Jul 2023 22:10:56 -0400 Subject: [PATCH 08/10] derive clone headers --- crates/net/src/http/headers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/net/src/http/headers.rs b/crates/net/src/http/headers.rs index 0a9b1515..9b96edd7 100644 --- a/crates/net/src/http/headers.rs +++ b/crates/net/src/http/headers.rs @@ -6,6 +6,7 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt}; // I experimented with using `js_sys::Object` for the headers, since this object is marked // experimental in MDN. However it's in the fetch spec, and it's necessary for appending headers. /// A wrapper around `web_sys::Headers`. +#[derive(Clone)] pub struct Headers { raw: web_sys::Headers, } From 52f1a5ddde1fa79d069a5b431ac2235b7151c3fe Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 1 Aug 2023 23:38:24 -0400 Subject: [PATCH 09/10] improve safety and clone headers --- crates/net/src/http/headers.rs | 14 +++++-- crates/net/src/http/request.rs | 2 +- crates/net/src/http/response.rs | 74 +++++++++++++++++++-------------- crates/net/tests/http.rs | 12 +++++- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/crates/net/src/http/headers.rs b/crates/net/src/http/headers.rs index 9b96edd7..542e8271 100644 --- a/crates/net/src/http/headers.rs +++ b/crates/net/src/http/headers.rs @@ -6,11 +6,17 @@ use wasm_bindgen::{JsCast, UnwrapThrowExt}; // I experimented with using `js_sys::Object` for the headers, since this object is marked // experimental in MDN. However it's in the fetch spec, and it's necessary for appending headers. /// A wrapper around `web_sys::Headers`. -#[derive(Clone)] pub struct Headers { raw: web_sys::Headers, } +impl Clone for Headers { + fn clone(&self) -> Self { + let next = web_sys::Headers::new_with_headers(&self.raw).unwrap(); + Self::from_raw(next) + } +} + impl Default for Headers { fn default() -> Self { Self::new() @@ -38,14 +44,14 @@ impl Headers { /// This method appends a new value onto an existing header, or adds the header if it does not /// already exist. - pub fn append(&self, name: &str, value: &str) { + pub fn append(&mut self, name: &str, value: &str) { // XXX Can this throw? WEBIDL says yes, my experiments with forbidden headers and MDN say // no. self.raw.append(name, value).unwrap_throw() } /// Deletes a header if it is present. - pub fn delete(&self, name: &str) { + pub fn delete(&mut self, name: &str) { self.raw.delete(name).unwrap_throw() } @@ -60,7 +66,7 @@ impl Headers { } /// Overwrites a header with the given name. - pub fn set(&self, name: &str, value: &str) { + pub fn set(&mut self, name: &str, value: &str) { self.raw.set(name, value).unwrap_throw() } diff --git a/crates/net/src/http/request.rs b/crates/net/src/http/request.rs index 6a65d759..6054da0e 100644 --- a/crates/net/src/http/request.rs +++ b/crates/net/src/http/request.rs @@ -64,7 +64,7 @@ impl RequestBuilder { } /// Sets a header. - pub fn header(self, key: &str, value: &str) -> Self { + pub fn header(mut self, key: &str, value: &str) -> Self { self.headers.set(key, value); self } diff --git a/crates/net/src/http/response.rs b/crates/net/src/http/response.rs index 63dca0c9..48feeb36 100644 --- a/crates/net/src/http/response.rs +++ b/crates/net/src/http/response.rs @@ -2,7 +2,7 @@ use std::{convert::From, fmt}; use crate::{js_to_error, Error}; use js_sys::{ArrayBuffer, Uint8Array}; -use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen::{prelude::*, JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; use web_sys::ResponseInit; @@ -162,6 +162,8 @@ impl fmt::Debug for Response { pub struct ResponseBuilder { headers: Headers, options: web_sys::ResponseInit, + status: Option, + status_text: Option, } impl ResponseBuilder { @@ -178,7 +180,7 @@ impl ResponseBuilder { } /// Sets a header. - pub fn header(self, key: &str, value: &str) -> Self { + pub fn header(mut self, key: &str, value: &str) -> Self { self.headers.set(key, value); self } @@ -189,28 +191,26 @@ impl ResponseBuilder { } /// Get the contained status code if it exists - pub fn get_status(&self) -> Option { - js_sys::Reflect::get(&self.options, &JsValue::from_str("status")) - .ok()? - .as_f64() + pub fn get_status(&self) -> Option { + self.status.to_owned() } /// Get the contained status text if it exists - pub fn get_status_text(&self) -> Option { - js_sys::Reflect::get(&self.options, &JsValue::from_str("statusText")) - .ok()? - .as_string() + pub fn get_status_text(&self) -> Option<&str> { + self.status_text.as_deref() } /// Set the status code pub fn status(mut self, status: u16) -> Self { self.options.status(status); + self.status = Some(status); self } /// Set the status text - pub fn status_text(mut self, status_text: &str) -> Self { - self.options.status_text(status_text); + pub fn status_text(mut self, status_text: String) -> Self { + self.options.status_text(status_text.as_str()); + self.status_text = Some(status_text); self } @@ -224,7 +224,7 @@ impl ResponseBuilder { pub fn json(self, value: &T) -> Result { let json = serde_json::to_string(value)?; self.header("Content-Type", "application/json") - .body(Some(json.as_str())) + .body(json.as_str()) } /// Set the response body and return the response @@ -239,33 +239,39 @@ impl ResponseBuilder { } } -impl IntoRawResponse for Option<&web_sys::Blob> { +impl IntoRawResponse for &web_sys::Blob { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_blob_and_init(self, &init) + web_sys::Response::new_with_opt_blob_and_init(Some(self), &init) } } -impl IntoRawResponse for Option<&js_sys::Object> { +impl IntoRawResponse for &js_sys::Object { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_buffer_source_and_init(self, &init) + web_sys::Response::new_with_opt_buffer_source_and_init(Some(self), &init) } } -impl IntoRawResponse for Option<&mut [u8]> { +impl IntoRawResponse for &mut [u8] { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_u8_array_and_init(self, &init) + web_sys::Response::new_with_opt_u8_array_and_init(Some(self), &init) } } -impl IntoRawResponse for Option<&web_sys::FormData> { +impl IntoRawResponse for &web_sys::FormData { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_form_data_and_init(self, &init) + web_sys::Response::new_with_opt_form_data_and_init(Some(self), &init) } } -impl IntoRawResponse for Option<&web_sys::UrlSearchParams> { +impl IntoRawResponse for &web_sys::UrlSearchParams { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_url_search_params_and_init(self, &init) + web_sys::Response::new_with_opt_url_search_params_and_init(Some(self), &init) + } +} + +impl IntoRawResponse for &str { + fn into_raw(self, init: ResponseInit) -> Result { + web_sys::Response::new_with_opt_str_and_init(Some(self), &init) } } @@ -275,9 +281,9 @@ impl IntoRawResponse for Option<&str> { } } -impl IntoRawResponse for Option<&web_sys::ReadableStream> { +impl IntoRawResponse for &web_sys::ReadableStream { fn into_raw(self, init: ResponseInit) -> Result { - web_sys::Response::new_with_opt_readable_stream_and_init(self, &init) + web_sys::Response::new_with_opt_readable_stream_and_init(Some(self), &init) } } @@ -293,6 +299,9 @@ impl Default for ResponseBuilder { Self { headers: Headers::new(), options: web_sys::ResponseInit::new(), + status: None, + status_text: None, + } } } @@ -305,13 +314,16 @@ impl fmt::Debug for ResponseBuilder { } } + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_name = "structuredClone", catch)] + fn structured_clone(v: &JsValue) -> Result; +} + impl Clone for ResponseBuilder { fn clone(&self) -> Self { - let headers = Headers::new(); - for (name, value) in self.headers.entries() { - headers.append(name.as_str(), value.as_str()) - } - let options = self.options.clone(); - Self { headers, options } + let options_clone: ResponseInit = structured_clone(&self.options).unwrap_throw().unchecked_into(); + Self { headers: self.headers.clone(), options: options_clone, status: self.status, status_text: self.status_text.clone() } } } diff --git a/crates/net/tests/http.rs b/crates/net/tests/http.rs index a13c0eee..15596f6a 100644 --- a/crates/net/tests/http.rs +++ b/crates/net/tests/http.rs @@ -1,4 +1,4 @@ -use gloo_net::http::Request; +use gloo_net::http::{Request, Headers}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use wasm_bindgen_test::*; @@ -137,3 +137,13 @@ async fn query_preserve_duplicate_params() { .unwrap(); assert_eq!(resp.url(), format!("{}/get?q=1&q=2", *HTTPBIN_URL)); } + +#[wasm_bindgen_test] +fn clone_headers() { + let mut headers = Headers::new(); + headers.append("Content-Type", "application/json"); + let other = headers.clone(); + headers.append("Content-Type", "text/html"); + assert_ne!(headers.get("Content-Type"), other.get("Content-Type")) +} + From cf92a0b5dec735b717925ef5223e406b89fbecf6 Mon Sep 17 00:00:00 2001 From: Sean Aye Date: Tue, 1 Aug 2023 23:41:19 -0400 Subject: [PATCH 10/10] run cargo fmt --- crates/net/src/http/response.rs | 13 +++++++++---- crates/net/tests/http.rs | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/net/src/http/response.rs b/crates/net/src/http/response.rs index 48feeb36..8ab62d6f 100644 --- a/crates/net/src/http/response.rs +++ b/crates/net/src/http/response.rs @@ -301,7 +301,6 @@ impl Default for ResponseBuilder { options: web_sys::ResponseInit::new(), status: None, status_text: None, - } } } @@ -314,7 +313,6 @@ impl fmt::Debug for ResponseBuilder { } } - #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = "structuredClone", catch)] @@ -323,7 +321,14 @@ extern "C" { impl Clone for ResponseBuilder { fn clone(&self) -> Self { - let options_clone: ResponseInit = structured_clone(&self.options).unwrap_throw().unchecked_into(); - Self { headers: self.headers.clone(), options: options_clone, status: self.status, status_text: self.status_text.clone() } + let options_clone: ResponseInit = structured_clone(&self.options) + .unwrap_throw() + .unchecked_into(); + Self { + headers: self.headers.clone(), + options: options_clone, + status: self.status, + status_text: self.status_text.clone(), + } } } diff --git a/crates/net/tests/http.rs b/crates/net/tests/http.rs index 15596f6a..8fb999ff 100644 --- a/crates/net/tests/http.rs +++ b/crates/net/tests/http.rs @@ -1,4 +1,4 @@ -use gloo_net::http::{Request, Headers}; +use gloo_net::http::{Headers, Request}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; use wasm_bindgen_test::*; @@ -146,4 +146,3 @@ fn clone_headers() { headers.append("Content-Type", "text/html"); assert_ne!(headers.get("Content-Type"), other.get("Content-Type")) } -