use super::{fetch, FetchError, Header, Headers, Method, Response, Result};
use crate::browser::Url;
use gloo_timers::callback::Timeout;
use serde::Serialize;
use std::{borrow::Cow, cell::RefCell, convert::TryFrom, rc::Rc};
use wasm_bindgen::JsValue;
#[derive(Debug, Clone, Default)]
pub struct Request<'a> {
url: Cow<'a, str>,
headers: Headers<'a>,
method: Method,
body: Option<JsValue>,
cache: Option<web_sys::RequestCache>,
credentials: Option<web_sys::RequestCredentials>,
integrity: Option<String>,
mode: Option<web_sys::RequestMode>,
redirect: Option<web_sys::RequestRedirect>,
referrer: Option<String>,
referrer_policy: Option<web_sys::ReferrerPolicy>,
timeout: Option<u32>,
controller: RequestController,
}
impl<'a> Request<'a> {
pub fn new(url: impl Into<Cow<'a, str>>) -> Self {
Self {
url: url.into(),
..Self::default()
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn headers(mut self, headers: Headers<'a>) -> Self {
self.headers = headers;
self
}
pub fn header(mut self, header: Header<'a>) -> Self {
self.headers.set(header);
self
}
pub const fn method(mut self, method: Method) -> Self {
self.method = method;
self
}
pub fn body(mut self, body: JsValue) -> Self {
self.body = Some(body);
#[cfg(debug_assertions)]
match self.method {
Method::Get | Method::Head => {
error!("GET and HEAD requests shoudn't have a body");
}
_ => {}
}
self
}
pub fn json<T: Serialize + ?Sized>(mut self, data: &T) -> Result<Self> {
let body = serde_json::to_string(data).map_err(FetchError::SerdeError)?;
self.body = Some(body.into());
Ok(self.header(Header::content_type("application/json; charset=utf-8")))
}
pub fn text(mut self, text: impl AsRef<str>) -> Self {
self.body = Some(JsValue::from(text.as_ref()));
self.header(Header::content_type("text/plain; charset=utf-8"))
}
pub const fn mode(mut self, mode: web_sys::RequestMode) -> Self {
self.mode = Some(mode);
self
}
pub const fn credentials(mut self, credentials: web_sys::RequestCredentials) -> Self {
self.credentials = Some(credentials);
self
}
pub const fn cache(mut self, cache: web_sys::RequestCache) -> Self {
self.cache = Some(cache);
self
}
pub const fn redirect(mut self, redirect: web_sys::RequestRedirect) -> Self {
self.redirect = Some(redirect);
self
}
pub fn referrer(mut self, referrer: &impl ToString) -> Self {
self.referrer = Some(referrer.to_string());
self
}
pub const fn referrer_policy(mut self, referrer_policy: web_sys::ReferrerPolicy) -> Self {
self.referrer_policy = Some(referrer_policy);
self
}
pub fn integrity(mut self, integrity: &impl ToString) -> Self {
self.integrity = Some(integrity.to_string());
self
}
pub fn timeout(mut self, timeout: u32) -> Self {
self.timeout = Some(timeout);
self
}
pub fn controller(self) -> (Self, RequestController) {
let controller = self.controller.clone();
(self, controller)
}
pub async fn fetch(self) -> Result<Response> {
fetch(self).await
}
}
impl<'a, T: Into<Cow<'a, str>>> From<T> for Request<'a> {
fn from(s: T) -> Request<'a> {
Request::new(s)
}
}
impl<'a> From<Url> for Request<'a> {
fn from(url: Url) -> Request<'a> {
Request::new(url.to_string())
}
}
impl TryFrom<Request<'_>> for web_sys::Request {
type Error = FetchError;
fn try_from(request: Request) -> std::result::Result<Self, Self::Error> {
let mut init = web_sys::RequestInit::new();
let headers = web_sys::Headers::new().map_err(FetchError::RequestError)?;
for header in request.headers {
headers
.append(&header.name, &header.value)
.map_err(FetchError::RequestError)?;
}
init.headers(&headers);
init.method(request.method.as_str());
if let Some(body) = &request.body {
init.body(Some(body));
}
if let Some(cache) = request.cache {
init.cache(cache);
}
if let Some(credentials) = request.credentials {
init.credentials(credentials);
}
if let Some(integrity) = &request.integrity {
init.integrity(integrity.as_str());
}
if let Some(mode) = request.mode {
init.mode(mode);
}
if let Some(redirect) = request.redirect {
init.redirect(redirect);
}
if let Some(referrer) = &request.referrer {
init.referrer(referrer.as_str());
}
if let Some(referrer_policy) = request.referrer_policy {
init.referrer_policy(referrer_policy);
}
if let Some(timeout) = &request.timeout {
let abort_controller = request.controller.clone();
request.controller.timeout_handle.replace(Some(
Timeout::new(*timeout, move || abort_controller.abort()),
));
}
init.signal(Some(&request.controller.abort_controller.signal()));
web_sys::Request::new_with_str_and_init(&request.url, &init)
.map_err(FetchError::RequestError)
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
pub struct RequestController {
abort_controller: Rc<web_sys::AbortController>,
timeout_handle: Rc<RefCell<Option<Timeout>>>,
}
impl RequestController {
pub fn abort(&self) {
self.timeout_handle.replace(None);
self.abort_controller.abort();
}
pub fn disable_timeout(&self) -> std::result::Result<(), &'static str> {
match self.timeout_handle.replace(None) {
Some(_) => Ok(()),
None => Err("disable_timeout: already disabled"),
}
}
}
impl Default for RequestController {
fn default() -> Self {
Self {
abort_controller: Rc::new(
web_sys::AbortController::new().expect("fetch: create AbortController - failed"),
),
timeout_handle: Rc::new(RefCell::new(None)),
}
}
}