,
-}
-
-impl Form {
- pub(crate) fn is_empty(&self) -> bool {
- self.inner.fields.is_empty()
- }
-}
-
-/// A field in a multipart form.
-pub struct Part {
- meta: PartMetadata,
- value: Body,
-}
-
-pub(crate) struct FormParts {
- pub(crate) fields: Vec<(Cow<'static, str>, P)>,
-}
-
-pub(crate) struct PartMetadata {
- mime: Option,
- file_name: Option>,
- pub(crate) headers: HeaderMap,
-}
-
-pub(crate) trait PartProps {
- fn metadata(&self) -> &PartMetadata;
-}
-
-// ===== impl Form =====
-
-impl Default for Form {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl Form {
- /// Creates a new async Form without any content.
- pub fn new() -> Form {
- Form {
- inner: FormParts::new(),
- }
- }
-
- /// Add a data field with supplied name and value.
- ///
- /// # Examples
- ///
- /// ```
- /// let form = reqwest::multipart::Form::new()
- /// .text("username", "seanmonstar")
- /// .text("password", "secret");
- /// ```
- pub fn text(self, name: T, value: U) -> Form
- where
- T: Into>,
- U: Into>,
- {
- self.part(name, Part::text(value))
- }
-
- /// Adds a customized Part.
- pub fn part(self, name: T, part: Part) -> Form
- where
- T: Into>,
- {
- self.with_inner(move |inner| inner.part(name, part))
- }
-
- fn with_inner(self, func: F) -> Self
- where
- F: FnOnce(FormParts) -> FormParts,
- {
- Form {
- inner: func(self.inner),
- }
- }
-
- pub(crate) fn to_form_data(&self) -> crate::Result {
- let form = FormData::new()
- .map_err(crate::error::wasm)
- .map_err(crate::error::builder)?;
-
- for (name, part) in self.inner.fields.iter() {
- part.append_to_form(name, &form)
- .map_err(crate::error::wasm)
- .map_err(crate::error::builder)?;
- }
- Ok(form)
- }
-}
-
-impl fmt::Debug for Form {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- self.inner.fmt_fields("Form", f)
- }
-}
-
-// ===== impl Part =====
-
-impl Part {
- /// Makes a text parameter.
- pub fn text(value: T) -> Part
- where
- T: Into>,
- {
- let body = match value.into() {
- Cow::Borrowed(slice) => Body::from(slice),
- Cow::Owned(string) => Body::from(string),
- };
- Part::new(body)
- }
-
- /// Makes a new parameter from arbitrary bytes.
- pub fn bytes(value: T) -> Part
- where
- T: Into>,
- {
- let body = match value.into() {
- Cow::Borrowed(slice) => Body::from(slice),
- Cow::Owned(vec) => Body::from(vec),
- };
- Part::new(body)
- }
-
- /// Makes a new parameter from an arbitrary stream.
- pub fn stream>(value: T) -> Part {
- Part::new(value.into())
- }
-
- fn new(value: Body) -> Part {
- Part {
- meta: PartMetadata::new(),
- value: value.into_part(),
- }
- }
-
- /// Tries to set the mime of this part.
- pub fn mime_str(self, mime: &str) -> crate::Result {
- Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
- }
-
- // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
- fn mime(self, mime: Mime) -> Part {
- self.with_inner(move |inner| inner.mime(mime))
- }
-
- /// Sets the filename, builder style.
- pub fn file_name(self, filename: T) -> Part
- where
- T: Into>,
- {
- self.with_inner(move |inner| inner.file_name(filename))
- }
-
- /// Sets custom headers for the part.
- pub fn headers(self, headers: HeaderMap) -> Part {
- self.with_inner(move |inner| inner.headers(headers))
- }
-
- fn with_inner(self, func: F) -> Self
- where
- F: FnOnce(PartMetadata) -> PartMetadata,
- {
- Part {
- meta: func(self.meta),
- value: self.value,
- }
- }
-
- fn append_to_form(
- &self,
- name: &str,
- form: &web_sys::FormData,
- ) -> Result<(), wasm_bindgen::JsValue> {
- let single = self
- .value
- .as_single()
- .expect("A part's body can't be multipart itself");
-
- let mut mime_type = self.metadata().mime.as_ref();
-
- // The JS fetch API doesn't support file names and mime types for strings. So we do our best
- // effort to use `append_with_str` and fallback to `append_with_blob_*` if that's not
- // possible.
- if let super::body::Single::Text(text) = single {
- if mime_type.is_none() || mime_type == Some(&mime_guess::mime::TEXT_PLAIN) {
- if self.metadata().file_name.is_none() {
- return form.append_with_str(name, text);
- }
- } else {
- mime_type = Some(&mime_guess::mime::TEXT_PLAIN);
- }
- }
-
- let blob = self.blob(mime_type)?;
-
- if let Some(file_name) = &self.metadata().file_name {
- form.append_with_blob_and_filename(name, &blob, file_name)
- } else {
- form.append_with_blob(name, &blob)
- }
- }
-
- fn blob(&self, mime_type: Option<&Mime>) -> crate::Result {
- use web_sys::Blob;
- use web_sys::BlobPropertyBag;
- let mut properties = BlobPropertyBag::new();
- if let Some(mime) = mime_type {
- properties.type_(mime.as_ref());
- }
-
- let js_value = self
- .value
- .as_single()
- .expect("A part's body can't be set to a multipart body")
- .to_js_value();
-
- let body_array = js_sys::Array::new();
- body_array.push(&js_value);
-
- Blob::new_with_u8_array_sequence_and_options(body_array.as_ref(), &properties)
- .map_err(crate::error::wasm)
- .map_err(crate::error::builder)
- }
-}
-
-impl fmt::Debug for Part {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let mut dbg = f.debug_struct("Part");
- dbg.field("value", &self.value);
- self.meta.fmt_fields(&mut dbg);
- dbg.finish()
- }
-}
-
-impl PartProps for Part {
- fn metadata(&self) -> &PartMetadata {
- &self.meta
- }
-}
-
-// ===== impl FormParts =====
-
-impl FormParts {
- pub(crate) fn new() -> Self {
- FormParts { fields: Vec::new() }
- }
-
- /// Adds a customized Part.
- pub(crate) fn part(mut self, name: T, part: P) -> Self
- where
- T: Into>,
- {
- self.fields.push((name.into(), part));
- self
- }
-}
-
-impl FormParts {
- pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
- f.debug_struct(ty_name)
- .field("parts", &self.fields)
- .finish()
- }
-}
-
-// ===== impl PartMetadata =====
-
-impl PartMetadata {
- pub(crate) fn new() -> Self {
- PartMetadata {
- mime: None,
- file_name: None,
- headers: HeaderMap::default(),
- }
- }
-
- pub(crate) fn mime(mut self, mime: Mime) -> Self {
- self.mime = Some(mime);
- self
- }
-
- pub(crate) fn file_name(mut self, filename: T) -> Self
- where
- T: Into>,
- {
- self.file_name = Some(filename.into());
- self
- }
-
- pub(crate) fn headers(mut self, headers: T) -> Self
- where
- T: Into,
- {
- self.headers = headers.into();
- self
- }
-}
-
-impl PartMetadata {
- pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
- &self,
- debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
- ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
- debug_struct
- .field("mime", &self.mime)
- .field("file_name", &self.file_name)
- .field("headers", &self.headers)
- }
-}
-
-#[cfg(test)]
-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 string_name = "string";
- let string_content = "CONTENT";
- let string_part = Part::text(string_content);
-
- let text_name = "text part";
- let binary_name = "binary part";
- let form = Form::new()
- .part(text_name, text_part)
- .part(binary_name, binary_part)
- .part(string_name, string_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::(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::(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);
-
- // check string part
- let string = form_data
- .get(string_name)
- .as_string()
- .expect("content is not a string");
- assert_eq!(string, string_content);
-
- let binary_array_buffer_promise = binary_file.array_buffer();
- let array_buffer = crate::wasm::promise::(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);
- }
-}
diff --git a/src/wasm/component/request.rs b/src/wasm/component/request.rs
index d122fdae0..1c1e442ae 100644
--- a/src/wasm/component/request.rs
+++ b/src/wasm/component/request.rs
@@ -7,10 +7,10 @@ use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
-use web_sys::RequestCredentials;
-use super::{Body, Client, Response};
+use super::{Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
+use crate::Body;
/// A request which can be executed with `Client::execute()`.
pub struct Request {
@@ -18,8 +18,6 @@ pub struct Request {
url: Url,
headers: HeaderMap,
body: Option,
- pub(super) cors: bool,
- pub(super) credentials: Option,
}
/// A builder to construct the properties of a `Request`.
@@ -37,8 +35,6 @@ impl Request {
url,
headers: HeaderMap::new(),
body: None,
- cors: true,
- credentials: None,
}
}
@@ -104,8 +100,6 @@ impl Request {
url: self.url.clone(),
headers: self.headers.clone(),
body,
- cors: self.cors,
- credentials: self.credentials,
})
}
}
@@ -241,16 +235,6 @@ impl RequestBuilder {
self
}
- /// TODO
- #[cfg(feature = "multipart")]
- #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
- pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
- if let Ok(ref mut req) = self.request {
- *req.body_mut() = Some(Body::from_form(multipart))
- }
- self
- }
-
/// Add a `Header` to this Request.
pub fn header(mut self, key: K, value: V) -> RequestBuilder
where
@@ -287,70 +271,6 @@ impl RequestBuilder {
self
}
- /// Disable CORS on fetching the request.
- ///
- /// # WASM
- ///
- /// This option is only effective with WebAssembly target.
- ///
- /// The [request mode][mdn] will be set to 'no-cors'.
- ///
- /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
- pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
- if let Ok(ref mut req) = self.request {
- req.cors = false;
- }
- self
- }
-
- /// Set fetch credentials to 'same-origin'
- ///
- /// # WASM
- ///
- /// This option is only effective with WebAssembly target.
- ///
- /// The [request credentials][mdn] will be set to 'same-origin'.
- ///
- /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
- pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
- if let Ok(ref mut req) = self.request {
- req.credentials = Some(RequestCredentials::SameOrigin);
- }
- self
- }
-
- /// Set fetch credentials to 'include'
- ///
- /// # WASM
- ///
- /// This option is only effective with WebAssembly target.
- ///
- /// The [request credentials][mdn] will be set to 'include'.
- ///
- /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
- pub fn fetch_credentials_include(mut self) -> RequestBuilder {
- if let Ok(ref mut req) = self.request {
- req.credentials = Some(RequestCredentials::Include);
- }
- self
- }
-
- /// Set fetch credentials to 'omit'
- ///
- /// # WASM
- ///
- /// This option is only effective with WebAssembly target.
- ///
- /// The [request credentials][mdn] will be set to 'omit'.
- ///
- /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
- pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
- if let Ok(ref mut req) = self.request {
- req.credentials = Some(RequestCredentials::Omit);
- }
- self
- }
-
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
pub fn build(self) -> crate::Result {
@@ -466,8 +386,6 @@ where
url,
headers,
body: Some(body.into()),
- cors: true,
- credentials: None,
})
}
}
diff --git a/src/wasm/component/response.rs b/src/wasm/component/response.rs
index 7cb47392c..8c9b6d2ed 100644
--- a/src/wasm/component/response.rs
+++ b/src/wasm/component/response.rs
@@ -2,10 +2,9 @@ use std::{fmt, io::Read as _};
use bytes::Bytes;
use http::{HeaderMap, StatusCode, Version};
-use url::Url;
-
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
+use url::Url;
/// A Response to a submitted `Request`.
pub struct Response {
@@ -53,7 +52,7 @@ impl Response {
///
/// - The server didn't send a `content-length` header.
/// - The response is compressed and automatically decoded (thus changing
- /// the actual decoded length).
+ /// the actual decoded length).
pub fn content_length(&self) -> Option {
self.headers()
.get(http::header::CONTENT_LENGTH)?
@@ -84,11 +83,11 @@ impl Response {
serde_json::from_slice(&full).map_err(crate::error::decode)
}
- /// Get the response text.
+ /// Get the response as text
pub async fn text(self) -> crate::Result {
self.bytes()
.await
- .map(|s| String::from_utf8(s.to_vec()).map_err(crate::error::decode))?
+ .map(|s| String::from_utf8_lossy(&s).to_string())
}
/// Get the response as bytes
@@ -116,20 +115,20 @@ impl Response {
/// then be used to stream the body.
#[cfg(feature = "stream")]
pub fn bytes_stream(&mut self) -> crate::Result {
- let body = self
+ let response_body = self
.http
.body()
.consume()
.map_err(|_| crate::error::decode("failed to consume response body"))?;
- let stream = body
+ let stream = response_body
.stream()
.map_err(|_| crate::error::decode("failed to stream response body"));
- self.incoming_body = Some(body);
+ // Dropping the incoming body when the stream is present will trap as the
+ // stream is a child resource of the incoming body.
+ self.incoming_body = Some(response_body);
stream
}
- // util methods
-
/// Turn a response into an error if the server returned an error.
pub fn error_for_status(self) -> crate::Result {
let status = self.status();
@@ -154,7 +153,7 @@ impl Response {
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
- //.field("url", self.url())
+ .field("url", self.url())
.field("status", &self.status())
.field("headers", self.headers())
.finish()