mod headers;
mod query;
use crate::{js_to_error, Error};
use js_sys::{ArrayBuffer, Uint8Array};
use std::fmt;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
use serde::de::DeserializeOwned;
pub use headers::Headers;
pub use query::QueryParams;
pub use web_sys::{
AbortSignal, FormData, ObserverCallback, ReadableStream, ReferrerPolicy, RequestCache,
RequestCredentials, RequestMode, RequestRedirect, ResponseType,
};
#[allow(
missing_docs,
missing_debug_implementations,
clippy::upper_case_acronyms
)]
#[derive(Clone, Copy, Debug)]
pub enum Method {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Method::GET => "GET",
Method::HEAD => "HEAD",
Method::POST => "POST",
Method::PUT => "PUT",
Method::DELETE => "DELETE",
Method::CONNECT => "CONNECT",
Method::OPTIONS => "OPTIONS",
Method::TRACE => "TRACE",
Method::PATCH => "PATCH",
};
write!(f, "{}", s)
}
}
pub struct Request {
options: web_sys::RequestInit,
headers: Headers,
query: QueryParams,
url: String,
}
impl Request {
pub fn new(url: &str) -> Self {
Self {
options: web_sys::RequestInit::new(),
headers: Headers::new(),
query: QueryParams::new(),
url: url.into(),
}
}
pub fn body(mut self, body: impl Into<JsValue>) -> Self {
self.options.body(Some(&body.into()));
self
}
pub fn cache(mut self, cache: RequestCache) -> Self {
self.options.cache(cache);
self
}
pub fn credentials(mut self, credentials: RequestCredentials) -> Self {
self.options.credentials(credentials);
self
}
pub fn headers(mut self, headers: Headers) -> Self {
self.headers = headers;
self
}
pub fn header(self, key: &str, value: &str) -> Self {
self.headers.set(key, value);
self
}
pub fn query<'a, T, V>(self, params: T) -> Self
where
T: IntoIterator<Item = (&'a str, V)>,
V: AsRef<str>,
{
for (name, value) in params {
self.query.append(name, value.as_ref());
}
self
}
pub fn integrity(mut self, integrity: &str) -> Self {
self.options.integrity(integrity);
self
}
#[cfg(feature = "json")]
pub fn json<T: serde::Serialize + ?Sized>(self, value: &T) -> Result<Self, Error> {
let json = serde_json::to_string(value)?;
Result::Ok(self.header("Content-Type", "application/json").body(json))
}
pub fn method(mut self, method: Method) -> Self {
self.options.method(&method.to_string());
self
}
pub fn mode(mut self, mode: RequestMode) -> Self {
self.options.mode(mode);
self
}
pub fn observe(mut self, observe: &ObserverCallback) -> Self {
self.options.observe(observe);
self
}
pub fn redirect(mut self, redirect: RequestRedirect) -> Self {
self.options.redirect(redirect);
self
}
pub fn referrer(mut self, referrer: &str) -> Self {
self.options.referrer(referrer);
self
}
pub fn referrer_policy(mut self, referrer_policy: ReferrerPolicy) -> Self {
self.options.referrer_policy(referrer_policy);
self
}
pub fn abort_signal(mut self, signal: Option<&AbortSignal>) -> Self {
self.options.signal(signal);
self
}
fn build_url(&self) -> Result<web_sys::Url, Error> {
let request = web_sys::Request::new_with_str(&self.url).map_err(js_to_error)?;
let url = web_sys::Url::new(&request.url()).map_err(js_to_error)?;
let combined_query = match url.search().as_str() {
"" => self.query.to_string(),
_ => format!("{}&{}", url.search(), self.query),
};
url.set_search(&combined_query);
Ok(url)
}
pub async fn send(mut self) -> Result<Response, Error> {
let url = String::from(&self.build_url()?.to_string());
self.options.headers(&self.headers.into_raw());
let request =
web_sys::Request::new_with_str_and_init(&url, &self.options).map_err(js_to_error)?;
let promise = gloo_utils::window().fetch_with_request(&request);
let response = JsFuture::from(promise).await.map_err(js_to_error)?;
match response.dyn_into::<web_sys::Response>() {
Ok(response) => Ok(Response {
response: response.unchecked_into(),
}),
Err(e) => panic!("fetch returned {:?}, not `Response` - this is a bug", e),
}
}
pub fn get(url: &str) -> Self {
Self::new(url).method(Method::GET)
}
pub fn post(url: &str) -> Self {
Self::new(url).method(Method::POST)
}
pub fn put(url: &str) -> Self {
Self::new(url).method(Method::PUT)
}
pub fn delete(url: &str) -> Self {
Self::new(url).method(Method::DELETE)
}
pub fn patch(url: &str) -> Self {
Self::new(url).method(Method::PATCH)
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Request").field("url", &self.url).finish()
}
}
pub struct Response {
response: web_sys::Response,
}
impl Response {
pub fn from_raw(raw: web_sys::Response) -> Self {
Self { response: raw }
}
pub fn type_(&self) -> ResponseType {
self.response.type_()
}
pub fn url(&self) -> String {
self.response.url()
}
pub fn redirected(&self) -> bool {
self.response.redirected()
}
pub fn status(&self) -> u16 {
self.response.status()
}
pub fn ok(&self) -> bool {
self.response.ok()
}
pub fn status_text(&self) -> String {
self.response.status_text()
}
pub fn headers(&self) -> Headers {
Headers::from_raw(self.response.headers())
}
pub fn body_used(&self) -> bool {
self.response.body_used()
}
pub fn body(&self) -> Option<ReadableStream> {
self.response.body()
}
pub fn as_raw(&self) -> &web_sys::Response {
&self.response
}
pub async fn form_data(&self) -> Result<FormData, Error> {
let promise = self.response.form_data().map_err(js_to_error)?;
let val = JsFuture::from(promise).await.map_err(js_to_error)?;
Ok(FormData::from(val))
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(&self) -> Result<T, Error> {
let promise = self.response.json().map_err(js_to_error)?;
let json = JsFuture::from(promise).await.map_err(js_to_error)?;
Ok(json.into_serde()?)
}
pub async fn text(&self) -> Result<String, Error> {
let promise = self.response.text().unwrap();
let val = JsFuture::from(promise).await.map_err(js_to_error)?;
let string = js_sys::JsString::from(val);
Ok(String::from(&string))
}
pub async fn binary(&self) -> Result<Vec<u8>, Error> {
let promise = self.response.array_buffer().map_err(js_to_error)?;
let array_buffer: ArrayBuffer = JsFuture::from(promise)
.await
.map_err(js_to_error)?
.unchecked_into();
let typed_buff: Uint8Array = Uint8Array::new(&array_buffer);
let mut body = vec![0; typed_buff.length() as usize];
typed_buff.copy_to(&mut body);
Ok(body)
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Response")
.field("url", &self.url())
.field("redirected", &self.redirected())
.field("status", &self.status())
.field("headers", &self.headers())
.field("body_used", &self.body_used())
.finish()
}
}