Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seanaye/feat/add misc methods #334

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
13 changes: 10 additions & 3 deletions crates/net/src/http/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ 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()
Expand Down Expand Up @@ -37,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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of making this &mut. This makes the method unnecessarily restrictive.

CC: @futursolo, do you have any thoughts on this?

// 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()
}

Expand All @@ -59,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()
}

Expand Down
17 changes: 11 additions & 6 deletions crates/net/src/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -265,12 +265,12 @@ impl Request {
}

/// Gets the body.
pub fn body(&self) -> Option<ReadableStream> {
pub fn body(self) -> Option<ReadableStream> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function can be called multiple times and still be valid.

self.0.body()
}

/// Reads the request to completion, returning it as `FormData`.
pub async fn form_data(&self) -> Result<FormData, Error> {
pub async fn form_data(self) -> Result<FormData, Error> {
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))
Expand All @@ -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<T: DeserializeOwned>(&self) -> Result<T, Error> {
pub async fn json<T: DeserializeOwned>(self) -> Result<T, Error> {
serde_json::from_str::<T>(&self.text().await?).map_err(Error::from)
}

/// Reads the reqeust as a String.
pub async fn text(&self) -> Result<String, Error> {
pub async fn text(self) -> Result<String, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change, while good in theory, also makes the API unnecessarily restrictive.

image

Notice how the object can still be used after text() is called

let promise = self.0.text().unwrap();
let val = JsFuture::from(promise).await.map_err(js_to_error)?;
let string = js_sys::JsString::from(val);
Expand All @@ -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<u8>`
pub async fn binary(&self) -> Result<Vec<u8>, Error> {
pub async fn binary(self) -> Result<Vec<u8>, Error> {
let promise = self.0.array_buffer().map_err(js_to_error)?;
let array_buffer: ArrayBuffer = JsFuture::from(promise)
.await
Expand Down Expand Up @@ -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<Self, Error> {
let clone = self.0.clone().map_err(js_to_error)?;
Ok(Self(clone))
}
}

impl From<web_sys::Request> for Request {
Expand Down
97 changes: 75 additions & 22 deletions crates/net/src/http/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -84,12 +84,12 @@ impl Response {
}

/// Gets the body.
pub fn body(&self) -> Option<web_sys::ReadableStream> {
pub fn body(self) -> Option<web_sys::ReadableStream> {
self.0.body()
}

/// Reads the response to completion, returning it as `FormData`.
pub async fn form_data(&self) -> Result<web_sys::FormData, Error> {
pub async fn form_data(self) -> Result<web_sys::FormData, Error> {
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))
Expand All @@ -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<T: DeserializeOwned>(&self) -> Result<T, Error> {
pub async fn json<T: DeserializeOwned>(self) -> Result<T, Error> {
serde_json::from_str::<T>(&self.text().await?).map_err(Error::from)
}

/// Reads the response as a String.
pub async fn text(&self) -> Result<String, Error> {
pub async fn text(self) -> Result<String, Error> {
let promise = self.0.text().unwrap();
let val = JsFuture::from(promise).await.map_err(js_to_error)?;
let string = js_sys::JsString::from(val);
Expand All @@ -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<u8>`
pub async fn binary(&self) -> Result<Vec<u8>, Error> {
pub async fn binary(self) -> Result<Vec<u8>, Error> {
let promise = self.0.array_buffer().map_err(js_to_error)?;
let array_buffer: ArrayBuffer = JsFuture::from(promise)
.await
Expand All @@ -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<Self, Error> {
let clone = self.0.clone().map_err(js_to_error)?;
Ok(Self(clone))
}
}

impl From<web_sys::Response> for Response {
Expand Down Expand Up @@ -156,6 +162,8 @@ impl fmt::Debug for Response {
pub struct ResponseBuilder {
headers: Headers,
options: web_sys::ResponseInit,
status: Option<u16>,
status_text: Option<String>,
}

impl ResponseBuilder {
Expand All @@ -172,20 +180,37 @@ 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
}

/// 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<u16> {
self.status.to_owned()
}

/// Get the contained status text if it exists
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
}

Expand All @@ -199,7 +224,7 @@ impl ResponseBuilder {
pub fn json<T: serde::Serialize + ?Sized>(self, value: &T) -> Result<Response, Error> {
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
Expand All @@ -214,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, JsValue> {
web_sys::Response::new_with_opt_blob_and_init(Some(self), &init)
}
}

impl IntoRawResponse for &js_sys::Object {
fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
web_sys::Response::new_with_opt_blob_and_init(self, &init)
web_sys::Response::new_with_opt_buffer_source_and_init(Some(self), &init)
}
}

impl IntoRawResponse for Option<&js_sys::Object> {
impl IntoRawResponse for &mut [u8] {
fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
web_sys::Response::new_with_opt_buffer_source_and_init(self, &init)
web_sys::Response::new_with_opt_u8_array_and_init(Some(self), &init)
}
}

impl IntoRawResponse for Option<&mut [u8]> {
impl IntoRawResponse for &web_sys::FormData {
fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
web_sys::Response::new_with_opt_u8_array_and_init(self, &init)
web_sys::Response::new_with_opt_form_data_and_init(Some(self), &init)
}
}

impl IntoRawResponse for Option<&web_sys::FormData> {
impl IntoRawResponse for &web_sys::UrlSearchParams {
fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
web_sys::Response::new_with_opt_form_data_and_init(self, &init)
web_sys::Response::new_with_opt_url_search_params_and_init(Some(self), &init)
}
}

impl IntoRawResponse for Option<&web_sys::UrlSearchParams> {
impl IntoRawResponse for &str {
fn into_raw(self, init: ResponseInit) -> Result<web_sys::Response, JsValue> {
web_sys::Response::new_with_opt_url_search_params_and_init(self, &init)
web_sys::Response::new_with_opt_str_and_init(Some(self), &init)
}
}

Expand All @@ -250,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, JsValue> {
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)
}
}

Expand All @@ -268,6 +299,8 @@ impl Default for ResponseBuilder {
Self {
headers: Headers::new(),
options: web_sys::ResponseInit::new(),
status: None,
status_text: None,
}
}
}
Expand All @@ -279,3 +312,23 @@ impl fmt::Debug for ResponseBuilder {
.finish_non_exhaustive()
}
}

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "structuredClone", catch)]
fn structured_clone(v: &JsValue) -> Result<JsValue, JsValue>;
}

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(),
}
}
}
19 changes: 14 additions & 5 deletions crates/net/tests/http.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use gloo_net::http::Request;
use gloo_net::http::{Headers, Request};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::*;
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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
}
Expand All @@ -137,3 +137,12 @@ 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"))
}