Introduce unstable, incomplete WASM support

This commit is contained in:
Sean McArthur
2019-09-24 14:50:30 -07:00
parent 6413a4349e
commit 932defd879
10 changed files with 543 additions and 78 deletions

3
src/wasm/body.rs Normal file
View File

@@ -0,0 +1,3 @@
/// dox
#[derive(Debug)]
pub struct Body(());

162
src/wasm/client.rs Normal file
View File

@@ -0,0 +1,162 @@
use std::future::Future;
use http::Method;
use wasm_bindgen::UnwrapThrowExt as _;
use crate::IntoUrl;
use super::{Request, RequestBuilder, Response};
/// dox
#[derive(Clone, Debug)]
pub struct Client(());
/// dox
#[derive(Debug)]
pub struct ClientBuilder(());
impl Client {
/// dox
pub fn new() -> Self {
Client::builder().build().unwrap_throw()
}
/// dox
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)
}
pub(super) fn execute_request(&self, req: Request) -> impl Future<Output = crate::Result<Response>> {
fetch(req)
}
}
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());
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());
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 = web_sys::window()
.expect("window should exist")
.fetch_with_request(&js_req);
let js_resp = super::promise::<web_sys::Response>(p)
.await
.map_err(crate::error::request)?;
// Convert from the js Response
let mut resp = http::Response::builder();
resp.status(js_resp.status());
// TODO: translate js_resp.headers()
/*
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");
}
*/
resp.body(js_resp)
.map(Response::new)
.map_err(crate::error::request)
}
// ===== impl ClientBuilder =====
impl ClientBuilder {
/// dox
pub fn new() -> Self {
ClientBuilder(())
}
/// dox
pub fn build(self) -> Result<Client, crate::Error> {
Ok(Client(()))
}
}

29
src/wasm/mod.rs Normal file
View File

@@ -0,0 +1,29 @@
use wasm_bindgen::JsCast;
mod body;
mod client;
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;
async fn promise<T>(promise: js_sys::Promise) -> Result<T, crate::error::BoxError>
where
T: JsCast,
{
use wasm_bindgen_futures::futures_0_3::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()
})
}

144
src/wasm/request.rs Normal file
View File

@@ -0,0 +1,144 @@
use std::fmt;
use http::{Method, HeaderMap};
use url::Url;
use super::{Body, Client, Response};
/// A request which can be executed with `Client::execute()`.
pub struct Request {
method: Method,
url: Url,
headers: HeaderMap,
body: Option<Body>,
}
/// A builder to construct the properties of a `Request`.
pub struct RequestBuilder {
client: Client,
request: crate::Result<Request>,
}
impl Request {
pub(super) fn new(method: Method, url: Url) -> Self {
Request {
method,
url,
headers: HeaderMap::new(),
body: 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
}
}
impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { client, request }
}
/// 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
}
/// 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
}
}
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)
}

73
src/wasm/response.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::fmt;
use http::{HeaderMap, StatusCode};
/// A Response to a submitted `Request`.
pub struct Response {
http: http::Response<web_sys::Response>,
}
impl Response {
pub(super) fn new(
res: http::Response<web_sys::Response>,
//url: Url,
) -> Response {
Response {
http: res,
}
}
/// 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()
}
/* 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()
}
*/
// pub async fn json()
/// 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"))
}
}
}
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()
}
}