Skip to content

Commit

Permalink
wasm: fix standalone/multipart body conversion to JsValue (#1364)
Browse files Browse the repository at this point in the history
  • Loading branch information
nwolber committed Nov 19, 2021
1 parent ab49de8 commit 0ef1a2e
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 16 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ features = [
"Blob",
"BlobPropertyBag",
"ServiceWorkerGlobalScope",
"RequestCredentials"
"RequestCredentials",
"File"
]

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
Expand Down
136 changes: 124 additions & 12 deletions src/wasm/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ pub struct Body {

enum Inner {
Bytes(Bytes),
/// MultipartForm holds a multipart/form-data body.
#[cfg(feature = "multipart")]
Multipart(Form),
MultipartForm(Form),
/// MultipartPart holds the body of a multipart/form-data part.
#[cfg(feature = "multipart")]
MultipartPart(Bytes),
}

impl Body {
Expand All @@ -32,7 +36,9 @@ impl Body {
match &self.inner {
Inner::Bytes(bytes) => Some(bytes.as_ref()),
#[cfg(feature = "multipart")]
Inner::Multipart(_) => None,
Inner::MultipartForm(_) => None,
#[cfg(feature = "multipart")]
Inner::MultipartPart(bytes) => Some(bytes.as_ref()),
}
}
pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
Expand All @@ -44,27 +50,54 @@ impl Body {
Ok(js_value.to_owned())
}
#[cfg(feature = "multipart")]
Inner::Multipart(form) => {
Inner::MultipartForm(form) => {
let form_data = form.to_form_data()?;
let js_value: &JsValue = form_data.as_ref();
Ok(js_value.to_owned())
}
#[cfg(feature = "multipart")]
Inner::MultipartPart(body_bytes) => {
let body_bytes: &[u8] = body_bytes.as_ref();
let body_uint8_array: Uint8Array = body_bytes.into();
let body_array = js_sys::Array::new();
body_array.push(&body_uint8_array);
let js_value: &JsValue = body_array.as_ref();
Ok(js_value.to_owned())
}
}
}

#[inline]
#[cfg(feature = "multipart")]
pub(crate) fn from_form(f: Form) -> Body {
Self {
inner: Inner::Multipart(f),
inner: Inner::MultipartForm(f),
}
}

/// into_part turns a regular body into the body of a mutlipart/form-data part.
#[cfg(feature = "multipart")]
pub(crate) fn into_part(self) -> Body {
match self.inner {
Inner::Bytes(bytes) => Self {
inner: Inner::MultipartPart(bytes),
},
Inner::MultipartForm(form) => Self {
inner: Inner::MultipartForm(form),
},
Inner::MultipartPart(bytes) => Self {
inner: Inner::MultipartPart(bytes),
},
}
}

pub(crate) fn is_empty(&self) -> bool {
match &self.inner {
Inner::Bytes(bytes) => bytes.is_empty(),
#[cfg(feature = "multipart")]
Inner::Multipart(form) => form.is_empty(),
Inner::MultipartForm(form) => form.is_empty(),
#[cfg(feature = "multipart")]
Inner::MultipartPart(bytes) => bytes.is_empty(),
}
}

Expand All @@ -74,7 +107,11 @@ impl Body {
inner: Inner::Bytes(bytes.clone()),
}),
#[cfg(feature = "multipart")]
Inner::Multipart(_) => None,
Inner::MultipartForm(_) => None,
#[cfg(feature = "multipart")]
Inner::MultipartPart(bytes) => Some(Self {
inner: Inner::MultipartPart(bytes.clone()),
}),
}
}
}
Expand Down Expand Up @@ -130,7 +167,8 @@ impl fmt::Debug for Body {

#[cfg(test)]
mod tests {
// use js_sys::{Array, Uint8Array};
use crate::Body;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;

Expand All @@ -146,16 +184,12 @@ mod tests {

#[wasm_bindgen_test]
async fn test_body() {
use crate::Body;

let body = Body::from("TEST");
assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
}

#[wasm_bindgen_test]
async fn test_body_js() {
use crate::Body;

async fn test_body_js_static_str() {
let body_value = "TEST";
let body = Body::from(body_value);

Expand All @@ -176,4 +210,82 @@ mod tests {

assert_eq!(text.as_string().expect("text is not a string"), body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_string() {
let body_value = "TEST".to_string();
let body = Body::from(body_value.clone());

let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));

let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get request body as text");

assert_eq!(text.as_string().expect("text is not a string"), body_value);
}

#[wasm_bindgen_test]
async fn test_body_js_static_u8_slice() {
let body_value: &'static [u8] = b"\x00\x42";
let body = Body::from(body_value);

let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));

let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");

let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");

let v = Uint8Array::new(&array_buffer).to_vec();

assert_eq!(v, body_value);
}

#[wasm_bindgen_test]
async fn test_body_js_vec_u8() {
let body_value = vec![0u8, 42];
let body = Body::from(body_value.clone());

let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));

let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");

let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");

let v = Uint8Array::new(&array_buffer).to_vec();

assert_eq!(v, body_value);
}
}
86 changes: 83 additions & 3 deletions src/wasm/multipart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl Part {
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value,
value: value.into_part(),
}
}

Expand Down Expand Up @@ -191,7 +191,7 @@ impl Part {
}

// BUG: the return value of to_js_value() is not valid if
// it is a Multipart variant.
// it is a MultipartForm variant.
let js_value = self.value.to_js_value()?;
Blob::new_with_u8_array_sequence_and_options(&js_value, &properties)
.map_err(crate::error::wasm)
Expand Down Expand Up @@ -277,4 +277,84 @@ impl PartMetadata {
}

#[cfg(test)]
mod tests {}
mod tests {

use wasm_bindgen_test::*;

wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
async fn test_multipart_js() {
use super::{Form, Part};
use js_sys::Uint8Array;
use wasm_bindgen::JsValue;
use web_sys::{File, FormData};

let text_file_name = "test.txt";
let text_file_type = "text/plain";
let text_content = "TEST";
let text_part = Part::text(text_content)
.file_name(text_file_name)
.mime_str(text_file_type)
.expect("invalid mime type");

let binary_file_name = "binary.bin";
let binary_file_type = "application/octet-stream";
let binary_content = vec![0u8, 42];
let binary_part = Part::bytes(binary_content.clone())
.file_name(binary_file_name)
.mime_str(binary_file_type)
.expect("invalid mime type");

let text_name = "text part";
let binary_name = "binary part";
let form = Form::new()
.part(text_name, text_part)
.part(binary_name, binary_part);

let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
form.to_form_data()
.expect("could not convert to FormData")
.as_ref(),
));

let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");

let form_data_promise = js_req.form_data().expect("could not get form_data promise");

let form_data = crate::wasm::promise::<FormData>(form_data_promise)
.await
.expect("could not get body as form data");

// check text part
let text_file = File::from(form_data.get(text_name));
assert_eq!(text_file.name(), text_file_name);
assert_eq!(text_file.type_(), text_file_type);

let text_promise = text_file.text();
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get text body as text");
assert_eq!(
text.as_string().expect("text is not a string"),
text_content
);

// check binary part
let binary_file = File::from(form_data.get(binary_name));
assert_eq!(binary_file.name(), binary_file_name);
assert_eq!(binary_file.type_(), binary_file_type);

let binary_array_buffer_promise = binary_file.array_buffer();
let array_buffer = crate::wasm::promise::<JsValue>(binary_array_buffer_promise)
.await
.expect("could not get request body as array buffer");

let binary = Uint8Array::new(&array_buffer).to_vec();

assert_eq!(binary, binary_content);
}
}

0 comments on commit 0ef1a2e

Please sign in to comment.