wasm: fix standalone/multipart body conversion to JsValue (#1364)
This commit is contained in:
		| @@ -166,7 +166,8 @@ features = [ | |||||||
|     "Blob", |     "Blob", | ||||||
|     "BlobPropertyBag", |     "BlobPropertyBag", | ||||||
|     "ServiceWorkerGlobalScope", |     "ServiceWorkerGlobalScope", | ||||||
|     "RequestCredentials" |     "RequestCredentials", | ||||||
|  |     "File" | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [target.'cfg(target_arch = "wasm32")'.dev-dependencies] | [target.'cfg(target_arch = "wasm32")'.dev-dependencies] | ||||||
|   | |||||||
							
								
								
									
										136
									
								
								src/wasm/body.rs
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								src/wasm/body.rs
									
									
									
									
									
								
							| @@ -19,8 +19,12 @@ pub struct Body { | |||||||
|  |  | ||||||
| enum Inner { | enum Inner { | ||||||
|     Bytes(Bytes), |     Bytes(Bytes), | ||||||
|  |     /// MultipartForm holds a multipart/form-data body. | ||||||
|     #[cfg(feature = "multipart")] |     #[cfg(feature = "multipart")] | ||||||
|     Multipart(Form), |     MultipartForm(Form), | ||||||
|  |     /// MultipartPart holds the body of a multipart/form-data part. | ||||||
|  |     #[cfg(feature = "multipart")] | ||||||
|  |     MultipartPart(Bytes), | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Body { | impl Body { | ||||||
| @@ -32,7 +36,9 @@ impl Body { | |||||||
|         match &self.inner { |         match &self.inner { | ||||||
|             Inner::Bytes(bytes) => Some(bytes.as_ref()), |             Inner::Bytes(bytes) => Some(bytes.as_ref()), | ||||||
|             #[cfg(feature = "multipart")] |             #[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> { |     pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> { | ||||||
| @@ -44,11 +50,20 @@ impl Body { | |||||||
|                 Ok(js_value.to_owned()) |                 Ok(js_value.to_owned()) | ||||||
|             } |             } | ||||||
|             #[cfg(feature = "multipart")] |             #[cfg(feature = "multipart")] | ||||||
|             Inner::Multipart(form) => { |             Inner::MultipartForm(form) => { | ||||||
|                 let form_data = form.to_form_data()?; |                 let form_data = form.to_form_data()?; | ||||||
|                 let js_value: &JsValue = form_data.as_ref(); |                 let js_value: &JsValue = form_data.as_ref(); | ||||||
|                 Ok(js_value.to_owned()) |                 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()) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -56,7 +71,23 @@ impl Body { | |||||||
|     #[cfg(feature = "multipart")] |     #[cfg(feature = "multipart")] | ||||||
|     pub(crate) fn from_form(f: Form) -> Body { |     pub(crate) fn from_form(f: Form) -> Body { | ||||||
|         Self { |         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), | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -64,7 +95,9 @@ impl Body { | |||||||
|         match &self.inner { |         match &self.inner { | ||||||
|             Inner::Bytes(bytes) => bytes.is_empty(), |             Inner::Bytes(bytes) => bytes.is_empty(), | ||||||
|             #[cfg(feature = "multipart")] |             #[cfg(feature = "multipart")] | ||||||
|             Inner::Multipart(form) => form.is_empty(), |             Inner::MultipartForm(form) => form.is_empty(), | ||||||
|  |             #[cfg(feature = "multipart")] | ||||||
|  |             Inner::MultipartPart(bytes) => bytes.is_empty(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -74,7 +107,11 @@ impl Body { | |||||||
|                 inner: Inner::Bytes(bytes.clone()), |                 inner: Inner::Bytes(bytes.clone()), | ||||||
|             }), |             }), | ||||||
|             #[cfg(feature = "multipart")] |             #[cfg(feature = "multipart")] | ||||||
|             Inner::Multipart(_) => None, |             Inner::MultipartForm(_) => None, | ||||||
|  |             #[cfg(feature = "multipart")] | ||||||
|  |             Inner::MultipartPart(bytes) => Some(Self { | ||||||
|  |                 inner: Inner::MultipartPart(bytes.clone()), | ||||||
|  |             }), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -130,7 +167,8 @@ impl fmt::Debug for Body { | |||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     // use js_sys::{Array, Uint8Array}; |     use crate::Body; | ||||||
|  |     use js_sys::Uint8Array; | ||||||
|     use wasm_bindgen::prelude::*; |     use wasm_bindgen::prelude::*; | ||||||
|     use wasm_bindgen_test::*; |     use wasm_bindgen_test::*; | ||||||
|  |  | ||||||
| @@ -146,16 +184,12 @@ mod tests { | |||||||
|  |  | ||||||
|     #[wasm_bindgen_test] |     #[wasm_bindgen_test] | ||||||
|     async fn test_body() { |     async fn test_body() { | ||||||
|         use crate::Body; |  | ||||||
|  |  | ||||||
|         let body = Body::from("TEST"); |         let body = Body::from("TEST"); | ||||||
|         assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap()); |         assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[wasm_bindgen_test] |     #[wasm_bindgen_test] | ||||||
|     async fn test_body_js() { |     async fn test_body_js_static_str() { | ||||||
|         use crate::Body; |  | ||||||
|  |  | ||||||
|         let body_value = "TEST"; |         let body_value = "TEST"; | ||||||
|         let body = Body::from(body_value); |         let body = Body::from(body_value); | ||||||
|  |  | ||||||
| @@ -176,4 +210,82 @@ mod tests { | |||||||
|  |  | ||||||
|         assert_eq!(text.as_string().expect("text is not a string"), body_value); |         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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -150,7 +150,7 @@ impl Part { | |||||||
|     fn new(value: Body) -> Part { |     fn new(value: Body) -> Part { | ||||||
|         Part { |         Part { | ||||||
|             meta: PartMetadata::new(), |             meta: PartMetadata::new(), | ||||||
|             value, |             value: value.into_part(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -191,7 +191,7 @@ impl Part { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // BUG: the return value of to_js_value() is not valid if |         // 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()?; |         let js_value = self.value.to_js_value()?; | ||||||
|         Blob::new_with_u8_array_sequence_and_options(&js_value, &properties) |         Blob::new_with_u8_array_sequence_and_options(&js_value, &properties) | ||||||
|             .map_err(crate::error::wasm) |             .map_err(crate::error::wasm) | ||||||
| @@ -277,4 +277,84 @@ impl PartMetadata { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[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); | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user