chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

320
vendor/reqwest/src/wasm/body.rs vendored Normal file
View File

@@ -0,0 +1,320 @@
#[cfg(feature = "multipart")]
use super::multipart::Form;
/// dox
use bytes::Bytes;
use js_sys::Uint8Array;
use std::{borrow::Cow, fmt};
use wasm_bindgen::JsValue;
/// The body of a `Request`.
///
/// In most cases, this is not needed directly, as the
/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
/// passing many things (like a string or vector of bytes).
///
/// [builder]: ./struct.RequestBuilder.html#method.body
pub struct Body {
inner: Inner,
}
enum Inner {
Single(Single),
/// MultipartForm holds a multipart/form-data body.
#[cfg(feature = "multipart")]
MultipartForm(Form),
}
#[derive(Clone)]
pub(crate) enum Single {
Bytes(Bytes),
Text(Cow<'static, str>),
}
impl Single {
fn as_bytes(&self) -> &[u8] {
match self {
Single::Bytes(bytes) => bytes.as_ref(),
Single::Text(text) => text.as_bytes(),
}
}
pub(crate) fn to_js_value(&self) -> JsValue {
match self {
Single::Bytes(bytes) => {
let body_bytes: &[u8] = bytes.as_ref();
let body_uint8_array: Uint8Array = body_bytes.into();
let js_value: &JsValue = body_uint8_array.as_ref();
js_value.to_owned()
}
Single::Text(text) => JsValue::from_str(text),
}
}
fn is_empty(&self) -> bool {
match self {
Single::Bytes(bytes) => bytes.is_empty(),
Single::Text(text) => text.is_empty(),
}
}
}
impl Body {
/// Returns a reference to the internal data of the `Body`.
///
/// `None` is returned, if the underlying data is a multipart form.
#[inline]
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.inner {
Inner::Single(single) => Some(single.as_bytes()),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
match &self.inner {
Inner::Single(single) => Ok(single.to_js_value()),
#[cfg(feature = "multipart")]
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")]
pub(crate) fn as_single(&self) -> Option<&Single> {
match &self.inner {
Inner::Single(single) => Some(single),
Inner::MultipartForm(_) => None,
}
}
#[inline]
#[cfg(feature = "multipart")]
pub(crate) fn from_form(f: Form) -> Body {
Self {
inner: Inner::MultipartForm(f),
}
}
/// into_part turns a regular body into the body of a multipart/form-data part.
#[cfg(feature = "multipart")]
pub(crate) fn into_part(self) -> Body {
match self.inner {
Inner::Single(single) => Self {
inner: Inner::Single(single),
},
Inner::MultipartForm(form) => Self {
inner: Inner::MultipartForm(form),
},
}
}
pub(crate) fn is_empty(&self) -> bool {
match &self.inner {
Inner::Single(single) => single.is_empty(),
#[cfg(feature = "multipart")]
Inner::MultipartForm(form) => form.is_empty(),
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
match &self.inner {
Inner::Single(single) => Some(Self {
inner: Inner::Single(single.clone()),
}),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
}
impl From<Bytes> for Body {
#[inline]
fn from(bytes: Bytes) -> Body {
Body {
inner: Inner::Single(Single::Bytes(bytes)),
}
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(vec: Vec<u8>) -> Body {
Body {
inner: Inner::Single(Single::Bytes(vec.into())),
}
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::from_static(s))),
}
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body").finish()
}
}
impl Default for Body {
fn default() -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::new())),
}
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use crate::Body;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: String);
}
#[wasm_bindgen_test]
async fn test_body() {
let body = Body::from("TEST");
assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
}
#[wasm_bindgen_test]
async fn test_body_js_static_str() {
let body_value = "TEST";
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 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_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);
}
}

473
vendor/reqwest/src/wasm/client.rs vendored Normal file
View File

@@ -0,0 +1,473 @@
use http::header::USER_AGENT;
use http::{HeaderMap, HeaderValue, Method};
use js_sys::{Promise, JSON};
use std::convert::TryInto;
use std::{fmt, future::Future, sync::Arc};
use url::Url;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use super::{AbortGuard, Request, RequestBuilder, Response};
use crate::IntoUrl;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = fetch)]
fn fetch_with_request(input: &web_sys::Request) -> Promise;
}
fn js_fetch(req: &web_sys::Request) -> Promise {
use wasm_bindgen::{JsCast, JsValue};
let global = js_sys::global();
if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope"))
{
global
.unchecked_into::<web_sys::ServiceWorkerGlobalScope>()
.fetch_with_request(req)
} else {
// browser
fetch_with_request(req)
}
}
/// An HTTP Client for WebAssembly.
///
/// Uses the browser's Fetch API to make requests. The `Client` holds
/// configuration that applies to all requests. To configure a `Client`,
/// use `Client::builder()`.
#[derive(Clone)]
pub struct Client {
config: Arc<Config>,
}
/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
pub struct ClientBuilder {
config: Config,
}
impl Client {
/// Constructs a new `Client`.
pub fn new() -> Self {
Client::builder().build().unwrap_throw()
}
/// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
/// Convenience method to make a `GET` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url)
}
/// Convenience method to make a `POST` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url)
}
/// Convenience method to make a `PUT` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url)
}
/// Convenience method to make a `PATCH` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url)
}
/// Convenience method to make a `DELETE` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url)
}
/// Convenience method to make a `HEAD` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url)
}
/// Start building a `Request` with the `Method` and `Url`.
///
/// Returns a `RequestBuilder`, which will allow setting headers and
/// request body before sending.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = url.into_url().map(move |url| Request::new(method, url));
RequestBuilder::new(self.clone(), req)
}
/// Executes a `Request`.
///
/// A `Request` can be built manually with `Request::new()` or obtained
/// from a RequestBuilder with `RequestBuilder::build()`.
///
/// You should prefer to use the `RequestBuilder` and
/// `RequestBuilder::send()`.
///
/// # Errors
///
/// This method fails if there was an error while sending request,
/// redirect loop was detected or redirect limit was exhausted.
pub fn execute(
&self,
request: Request,
) -> impl Future<Output = Result<Response, crate::Error>> {
self.execute_request(request)
}
// merge request headers with Client default_headers, prior to external http fetch
fn merge_headers(&self, req: &mut Request) {
use http::header::Entry;
let headers: &mut HeaderMap = req.headers_mut();
// insert default headers in the request headers
// without overwriting already appended headers.
for (key, value) in self.config.headers.iter() {
if let Entry::Vacant(entry) = headers.entry(key) {
entry.insert(value.clone());
}
}
}
pub(super) fn execute_request(
&self,
mut req: Request,
) -> impl Future<Output = crate::Result<Response>> {
self.merge_headers(&mut req);
fetch(req)
}
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("Client");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("ClientBuilder");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
async fn fetch(req: Request) -> crate::Result<Response> {
// Build the js Request
let mut init = web_sys::RequestInit::new();
init.method(req.method().as_str());
// convert HeaderMap to Headers
let js_headers = web_sys::Headers::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
for (name, value) in req.headers() {
js_headers
.append(
name.as_str(),
value.to_str().map_err(crate::error::builder)?,
)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
}
init.headers(&js_headers.into());
// When req.cors is true, do nothing because the default mode is 'cors'
if !req.cors {
init.mode(web_sys::RequestMode::NoCors);
}
if let Some(creds) = req.credentials {
init.credentials(creds);
}
if let Some(cache) = req.cache {
init.set_cache(cache);
}
if let Some(body) = req.body() {
if !body.is_empty() {
init.body(Some(body.to_js_value()?.as_ref()));
}
}
let mut abort = AbortGuard::new()?;
if let Some(timeout) = req.timeout() {
abort.timeout(*timeout);
}
init.signal(Some(&abort.signal()));
let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
// Await the fetch() promise
let p = js_fetch(&js_req);
let js_resp = super::promise::<web_sys::Response>(p)
.await
.map_err(|error| {
if error.to_string() == "JsValue(\"reqwest::errors::TimedOut\")" {
crate::error::TimedOut.into()
} else {
error
}
})
.map_err(crate::error::request)?;
// Convert from the js Response
let mut resp = http::Response::builder().status(js_resp.status());
let url = Url::parse(&js_resp.url()).expect_throw("url parse");
let js_headers = js_resp.headers();
let js_iter = js_sys::try_iter(&js_headers)
.expect_throw("headers try_iter")
.expect_throw("headers have an iterator");
for item in js_iter {
let item = item.expect_throw("headers iterator doesn't throw");
let serialized_headers: String = JSON::stringify(&item)
.expect_throw("serialized headers")
.into();
let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers)
.expect_throw("deserializable serialized headers");
resp = resp.header(&name, &value);
}
resp.body(js_resp)
.map(|resp| Response::new(resp, url, abort))
.map_err(crate::error::request)
}
// ===== impl ClientBuilder =====
impl ClientBuilder {
/// Constructs a new `ClientBuilder`.
///
/// This is the same as `Client::builder()`.
pub fn new() -> Self {
ClientBuilder {
config: Config::default(),
}
}
/// Returns a 'Client' that uses this ClientBuilder configuration
pub fn build(mut self) -> Result<Client, crate::Error> {
if let Some(err) = self.config.error {
return Err(err);
}
let config = std::mem::take(&mut self.config);
Ok(Client {
config: Arc::new(config),
})
}
/// Sets the `User-Agent` header to be used by this client.
pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
where
V: TryInto<HeaderValue>,
V::Error: Into<http::Error>,
{
match value.try_into() {
Ok(value) => {
self.config.headers.insert(USER_AGENT, value);
}
Err(e) => {
self.config.error = Some(crate::error::builder(e.into()));
}
}
self
}
/// Sets the default headers for every request
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
for (key, value) in headers.iter() {
self.config.headers.insert(key, value.clone());
}
self
}
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct Config {
headers: HeaderMap,
error: Option<crate::Error>,
}
impl Default for Config {
fn default() -> Config {
Config {
headers: HeaderMap::new(),
error: None,
}
}
}
impl Config {
fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
f.field("default_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 default_headers() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// merge headers as if client were about to issue fetch
client.merge_headers(&mut req);
let test_headers = req.headers();
assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type");
assert!(test_headers.get("x-custom").is_some(), "custom header");
assert!(test_headers.get("accept").is_none(), "no accept header");
}
#[wasm_bindgen_test]
async fn default_headers_clone() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.header(CONTENT_TYPE, "text/plain")
.build()
.expect("request");
client.merge_headers(&mut req);
let headers1 = req.headers();
// confirm that request headers override defaults
assert_eq!(
headers1.get(CONTENT_TYPE).unwrap(),
"text/plain",
"request headers override defaults"
);
// confirm that request headers don't change client defaults
let mut req2 = client
.get("https://www.example.com/x")
.build()
.expect("req 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(CONTENT_TYPE).unwrap(),
"application/json",
"request headers don't change client defaults"
);
}
#[wasm_bindgen_test]
fn user_agent_header() {
use crate::header::USER_AGENT;
let client = crate::Client::builder()
.user_agent("FooBar/1.2.3")
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// Merge the client headers with the request's one.
client.merge_headers(&mut req);
let headers1 = req.headers();
// Confirm that we have the `User-Agent` header set
assert_eq!(
headers1.get(USER_AGENT).unwrap(),
"FooBar/1.2.3",
"The user-agent header was not set: {req:#?}"
);
// Now we try to overwrite the `User-Agent` value
let mut req2 = client
.get("https://www.example.com")
.header(USER_AGENT, "Another-User-Agent/42")
.build()
.expect("request 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(USER_AGENT).expect("headers2 user agent"),
"Another-User-Agent/42",
"Was not able to overwrite the User-Agent value on the request-builder"
);
}
}

85
vendor/reqwest/src/wasm/mod.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
use std::convert::TryInto;
use std::time::Duration;
use js_sys::Function;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{AbortController, AbortSignal};
mod body;
mod client;
/// TODO
#[cfg(feature = "multipart")]
pub mod multipart;
mod request;
mod response;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "setTimeout")]
fn set_timeout(handler: &Function, timeout: i32) -> JsValue;
#[wasm_bindgen(js_name = "clearTimeout")]
fn clear_timeout(handle: JsValue) -> JsValue;
}
async fn promise<T>(promise: js_sys::Promise) -> Result<T, crate::error::BoxError>
where
T: JsCast,
{
use wasm_bindgen_futures::JsFuture;
let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
js_val
.dyn_into::<T>()
.map_err(|_js_val| "promise resolved to unexpected type".into())
}
/// A guard that cancels a fetch request when dropped.
struct AbortGuard {
ctrl: AbortController,
timeout: Option<(JsValue, Closure<dyn FnMut()>)>,
}
impl AbortGuard {
fn new() -> crate::Result<Self> {
Ok(AbortGuard {
ctrl: AbortController::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?,
timeout: None,
})
}
fn signal(&self) -> AbortSignal {
self.ctrl.signal()
}
fn timeout(&mut self, timeout: Duration) {
let ctrl = self.ctrl.clone();
let abort =
Closure::once(move || ctrl.abort_with_reason(&"reqwest::errors::TimedOut".into()));
let timeout = set_timeout(
abort.as_ref().unchecked_ref::<js_sys::Function>(),
timeout.as_millis().try_into().expect("timeout"),
);
if let Some((id, _)) = self.timeout.replace((timeout, abort)) {
clear_timeout(id);
}
}
}
impl Drop for AbortGuard {
fn drop(&mut self) {
self.ctrl.abort();
if let Some((id, _)) = self.timeout.take() {
clear_timeout(id);
}
}
}

419
vendor/reqwest/src/wasm/multipart.rs vendored Normal file
View File

@@ -0,0 +1,419 @@
//! multipart/form-data
use std::borrow::Cow;
use std::fmt;
use http::HeaderMap;
use mime_guess::Mime;
use web_sys::FormData;
use super::Body;
/// An async multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
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<P> {
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
}
pub(crate) struct PartMetadata {
mime: Option<Mime>,
file_name: Option<Cow<'static, str>>,
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<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
pub(crate) fn to_form_data(&self) -> crate::Result<FormData> {
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<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
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<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
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<T: Into<Body>>(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<Part> {
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<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
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<F>(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<web_sys::Blob> {
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<P: PartProps> FormParts<P> {
pub(crate) fn new() -> Self {
FormParts { fields: Vec::new() }
}
/// Adds a customized Part.
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
where
T: Into<Cow<'static, str>>,
{
self.fields.push((name.into(), part));
self
}
}
impl<P: fmt::Debug> FormParts<P> {
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<T>(mut self, filename: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.file_name = Some(filename.into());
self
}
pub(crate) fn headers<T>(mut self, headers: T) -> Self
where
T: Into<HeaderMap>,
{
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::<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);
// 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::<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);
}
}

621
vendor/reqwest/src/wasm/request.rs vendored Normal file
View File

@@ -0,0 +1,621 @@
use std::convert::TryFrom;
use std::fmt;
use std::time::Duration;
use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
use web_sys::{RequestCache, RequestCredentials};
use super::{Body, Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
/// A request which can be executed with `Client::execute()`.
pub struct Request {
method: Method,
url: Url,
headers: HeaderMap,
body: Option<Body>,
timeout: Option<Duration>,
pub(super) cors: bool,
pub(super) credentials: Option<RequestCredentials>,
pub(super) cache: Option<RequestCache>,
}
/// A builder to construct the properties of a `Request`.
pub struct RequestBuilder {
client: Client,
request: crate::Result<Request>,
}
impl Request {
/// Constructs a new request.
#[inline]
pub fn new(method: Method, url: Url) -> Self {
Request {
method,
url,
headers: HeaderMap::new(),
body: None,
timeout: None,
cors: true,
credentials: None,
cache: None,
}
}
/// Get the method.
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Get a mutable reference to the method.
#[inline]
pub fn method_mut(&mut self) -> &mut Method {
&mut self.method
}
/// Get the url.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/// Get a mutable reference to the url.
#[inline]
pub fn url_mut(&mut self) -> &mut Url {
&mut self.url
}
/// Get the headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Get the body.
#[inline]
pub fn body(&self) -> Option<&Body> {
self.body.as_ref()
}
/// Get a mutable reference to the body.
#[inline]
pub fn body_mut(&mut self) -> &mut Option<Body> {
&mut self.body
}
/// Get the timeout.
#[inline]
pub fn timeout(&self) -> Option<&Duration> {
self.timeout.as_ref()
}
/// Get a mutable reference to the timeout.
#[inline]
pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
&mut self.timeout
}
/// Attempts to clone the `Request`.
///
/// None is returned if a body is which can not be cloned.
pub fn try_clone(&self) -> Option<Request> {
let body = match self.body.as_ref() {
Some(body) => Some(body.try_clone()?),
None => None,
};
Some(Self {
method: self.method.clone(),
url: self.url.clone(),
headers: self.headers.clone(),
body,
timeout: self.timeout,
cors: self.cors,
credentials: self.credentials,
cache: self.cache,
})
}
}
impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { client, request }
}
/// Assemble a builder starting from an existing `Client` and a `Request`.
pub fn from_parts(client: crate::Client, request: crate::Request) -> crate::RequestBuilder {
crate::RequestBuilder {
client,
request: crate::Result::Ok(request),
}
}
/// Modify the query string of the URL.
///
/// Modifies the URL of this request, adding the parameters provided.
/// This method appends and does not overwrite. This means that it can
/// be called multiple times and that existing query parameters are not
/// overwritten if the same key is used. The key will simply show up
/// twice in the query string.
/// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
///
/// # Note
/// This method does not support serializing a single key-value
/// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
/// as `.query(&[("key", "val")])`. It's also possible to serialize structs
/// and maps into a key-value pair.
///
/// # Errors
/// This method will fail if the object you provide cannot be serialized
/// into a query string.
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
let url = req.url_mut();
let mut pairs = url.query_pairs_mut();
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
if let Err(err) = query.serialize(serializer) {
error = Some(crate::error::builder(err));
}
}
if let Ok(ref mut req) = self.request {
if let Some("") = req.url().query() {
req.url_mut().set_query(None);
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Send a form body.
///
/// Sets the body to the url encoded serialization of the passed value,
/// and also sets the `Content-Type: application/x-www-form-urlencoded`
/// header.
///
/// # Errors
///
/// This method fails if the passed value cannot be serialized into
/// url encoded format
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_urlencoded::to_string(form) {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Set the request json
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) {
Ok(body) => {
req.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Enable HTTP basic authentication.
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
{
let header_value = crate::util::basic_auth(username, password);
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
let header_value = format!("Bearer {token}");
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Set the request body.
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.body = Some(body.into());
}
self
}
/// Enables a request timeout.
pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.timeout_mut() = Some(timeout);
}
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<K, V>(mut self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let mut error = None;
if let Ok(ref mut req) = self.request {
match <HeaderName as TryFrom<K>>::try_from(key) {
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(value) => {
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
};
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Add a set of Headers to the existing ones on this Request.
///
/// The headers will be merged in to any already set.
pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
crate::util::replace_headers(req.headers_mut(), headers);
}
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
}
/// Set fetch cache mode to 'default'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'default'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_default(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Default);
}
self
}
/// Set fetch cache mode to 'no-store'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-store'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_store(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoStore);
}
self
}
/// Set fetch cache mode to 'reload'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'reload'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_reload(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Reload);
}
self
}
/// Set fetch cache mode to 'no-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoCache);
}
self
}
/// Set fetch cache mode to 'force-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'force-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_force_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::ForceCache);
}
self
}
/// Set fetch cache mode to 'only-if-cached'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'only-if-cached'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_only_if_cached(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::OnlyIfCached);
}
self
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
pub fn build(self) -> crate::Result<Request> {
self.request
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
///
/// This is similar to [`RequestBuilder::build()`], but also returns the
/// embedded `Client`.
pub fn build_split(self) -> (Client, crate::Result<Request>) {
(self.client, self.request)
}
/// Constructs the Request and sends it to the target URL, returning a
/// future Response.
///
/// # Errors
///
/// This method fails if there was an error while sending request.
///
/// # Example
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # async fn run() -> Result<(), Error> {
/// let response = reqwest::Client::new()
/// .get("https://hyper.rs")
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn send(self) -> crate::Result<Response> {
let req = self.request?;
self.client.execute_request(req).await
}
/// Attempt to clone the RequestBuilder.
///
/// `None` is returned if the RequestBuilder can not be cloned.
///
/// # Examples
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # fn run() -> Result<(), Error> {
/// let client = reqwest::Client::new();
/// let builder = client.post("http://httpbin.org/post")
/// .body("from a &str!");
/// let clone = builder.try_clone();
/// assert!(clone.is_some());
/// # Ok(())
/// # }
/// ```
pub fn try_clone(&self) -> Option<RequestBuilder> {
self.request
.as_ref()
.ok()
.and_then(|req| req.try_clone())
.map(|req| RequestBuilder {
client: self.client.clone(),
request: Ok(req),
})
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
}
}
impl fmt::Debug for RequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("RequestBuilder");
match self.request {
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
Err(ref err) => builder.field("error", err).finish(),
}
}
}
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
type Error = crate::Error;
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
let (parts, body) = req.into_parts();
let Parts {
method,
uri,
headers,
..
} = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
url,
headers,
body: Some(body.into()),
timeout: None,
cors: true,
credentials: None,
cache: None,
})
}
}
impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
..
} = req;
let mut req = HttpRequest::builder()
.method(method)
.uri(url.as_str())
.body(body.unwrap_or_else(|| Body::from(Bytes::default())))
.map_err(crate::error::builder)?;
*req.headers_mut() = headers;
Ok(req)
}
}

195
vendor/reqwest/src/wasm/response.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
use std::fmt;
use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use js_sys::Uint8Array;
use url::Url;
use crate::wasm::AbortGuard;
#[cfg(feature = "stream")]
use wasm_bindgen::JsCast;
#[cfg(feature = "stream")]
use futures_util::stream::{self, StreamExt};
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
/// A Response to a submitted `Request`.
pub struct Response {
http: http::Response<web_sys::Response>,
_abort: AbortGuard,
// Boxed to save space (11 words to 1 word), and it's not accessed
// frequently internally.
url: Box<Url>,
}
impl Response {
pub(super) fn new(
res: http::Response<web_sys::Response>,
url: Url,
abort: AbortGuard,
) -> Response {
Response {
http: res,
url: Box::new(url),
_abort: abort,
}
}
/// Get the `StatusCode` of this `Response`.
#[inline]
pub fn status(&self) -> StatusCode {
self.http.status()
}
/// Get the `Headers` of this `Response`.
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.http.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.http.headers_mut()
}
/// Get the content-length of this response, if known.
///
/// Reasons it may not be known:
///
/// - The server didn't send a `content-length` header.
/// - The response is compressed and automatically decoded (thus changing
/// the actual decoded length).
pub fn content_length(&self) -> Option<u64> {
self.headers()
.get(http::header::CONTENT_LENGTH)?
.to_str()
.ok()?
.parse()
.ok()
}
/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/* It might not be possible to detect this in JS?
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.http.version()
}
*/
/// Try to deserialize the response body as JSON.
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
let full = self.bytes().await?;
serde_json::from_slice(&full).map_err(crate::error::decode)
}
/// Get the response text.
pub async fn text(self) -> crate::Result<String> {
let p = self
.http
.body()
.text()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let js_val = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
if let Some(s) = js_val.as_string() {
Ok(s)
} else {
Err(crate::error::decode("response.text isn't string"))
}
}
/// Get the response as bytes
pub async fn bytes(self) -> crate::Result<Bytes> {
let p = self
.http
.body()
.array_buffer()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let buf_js = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
let buffer = Uint8Array::new(&buf_js);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
}
/// Convert the response into a `Stream` of `Bytes` from the body.
#[cfg(feature = "stream")]
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
use futures_core::Stream;
use std::pin::Pin;
let web_response = self.http.into_body();
let abort = self._abort;
if let Some(body) = web_response.body() {
let body = wasm_streams::ReadableStream::from_raw(body.unchecked_into());
Box::pin(body.into_stream().map(move |buf_js| {
// Keep the abort guard alive as long as this stream is.
let _abort = &abort;
let buffer = Uint8Array::new(
&buf_js
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?,
);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
})) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
} else {
// If there's no body, return an empty stream.
Box::pin(stream::empty()) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
}
}
// util methods
/// Turn a response into an error if the server returned an error.
pub fn error_for_status(self) -> crate::Result<Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url, status))
} else {
Ok(self)
}
}
/// Turn a reference to a response into an error if the server returned an error.
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url.clone(), status))
} else {
Ok(self)
}
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
//.field("url", self.url())
.field("status", &self.status())
.field("headers", self.headers())
.finish()
}
}