simi 0.2.1

A framework for building wasm front-end web application in Rust
//! Help fetch and send back result to simi app
use crate::callback::CallbackArg;
use crate::error::ErrorString;
use futures::Future;
use serde::{Deserialize, Serialize};
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::{future_to_promise, JsFuture};

/// fetch builder
pub struct FetchBuilder {
    method: String,
    url: String,
    content_type: Option<String>,
    accept_type: Option<String>,
    request_init: web_sys::RequestInit,
}

impl FetchBuilder {
    /// New builder with default arguments:
    pub fn new(method: &str, url: &str) -> Self {
        Self {
            method: method.to_string(),
            content_type: None,
            accept_type: None,
            url: url.to_string(),
            request_init: web_sys::RequestInit::new(),
        }
    }

    fn init_fetch(self) -> impl Future<Item = web_sys::Response, Error = JsValue> + 'static {
        let FetchBuilder {
            method,
            url,
            content_type,
            accept_type,
            mut request_init,
        } = self;
        request_init.method(&method);
        let req = web_sys::Request::new_with_str_and_init(&url, &request_init)
            .expect("new web_sys::Request");
        if let Some(content_type) = content_type {
            req.headers()
                .set("Content-Type", &content_type)
                .expect("set Content-Type");
        }
        if let Some(accept_type) = accept_type {
            req.headers()
                .set("Accept", &accept_type)
                .expect("set Accept");
        }

        let win = web_sys::window().expect("web_sys::window()");
        JsFuture::from(win.fetch_with_request(&req)).map(|res| {
            res.dyn_into::<web_sys::Response>()
                .expect("web_sys::Response")
        })
    }

    #[cfg(feature = "fetch_json")]
    /// Set body content in JSON with provided data, method will setted to POST
    pub fn body_json<S: Serialize>(&mut self, data: &S) -> Result<(), ErrorString> {
        self.content_type = Some("application/json".to_string());

        let data = ::serde_json::to_string(data)?;
        let data = JsValue::from(data);
        self.request_init.body(Some(&data));
        Ok(())
    }

    #[cfg(feature = "fetch_json")]
    /// Fetch and parse data as JSON format
    pub fn fetch_json<R>(
        mut self,
        cb: CallbackArg<Result<R, ErrorString>>,
    ) -> Result<js_sys::Promise, ErrorString>
    where
        for<'a> R: Deserialize<'a> + 'static,
    {
        self.accept_type = Some("application/json".to_string());
        let cb2 = cb.clone();
        let rs = self
            .init_fetch()
            .and_then(|res| res.text())
            .and_then(JsFuture::from)
            .and_then(move |text| {
                match text.as_string() {
                    Some(text) => match ::serde_json::from_str(&text) {
                        Ok(r) => cb(Ok(r)),
                        Err(e) => cb(Err(ErrorString::from(e))),
                    },
                    None => cb(Err(ErrorString::from("Response value is not a text?"))),
                }
                futures::future::ok(JsValue::null())
            })
            .or_else(move |e| {
                cb2(Err(ErrorString::new(
                    e.as_string()
                        .unwrap_or("No message for the error".to_string()),
                )));
                futures::future::ok::<JsValue, JsValue>(JsValue::null())
            });
        Ok(future_to_promise(rs))
    }
}