1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::marker::PhantomData;

use ajars_core::HttpMethod;
use ajars_core::RestType;

use serde::de::DeserializeOwned;
use serde::Serialize;
use wasm_bindgen::JsCast as _;
use wasm_bindgen_futures::JsFuture;

use web_sys::window;
use web_sys::Headers;
use web_sys::Request as WebRequest;
use web_sys::RequestInit;
use web_sys::RequestMode;
use web_sys::Response as WebResponse;
use web_sys::Window;

use error::Error;

pub mod error;


/// Create a `http::Response` from one produced by the Fetch API.
async fn into_http_response<O: Serialize + DeserializeOwned>(response: WebResponse) -> Result<O, Error> {

    // let status = response.status();

    let value = JsFuture::from(response.json()
    .map_err(|err| Error::web("Failed to read JSON body", err))?).await
    .map_err(|err| Error::web("Failed to read JSON body", err))?;

    let data: O = serde_wasm_bindgen::from_value(value).expect("Should build from JSON with serde_wasm_bindgen");

    Ok(data)
}

async fn do_web_request<O: Serialize + DeserializeOwned>(client: &Window, request: WebRequest) -> Result<O, Error> {
    let response = JsFuture::from(client.fetch_with_request(&request))
        .await
        .map_err(|err| Error::web("failed to issue request", err))?;
    let response = response
        .dyn_into::<WebResponse>()
        .map_err(|err| Error::web("future did not resolve into a web-sys Response", err))?;

    into_http_response(response).await
}

#[derive(Clone)]
pub struct AjarsWebSys {
    window: Window,
    base_url: String,
}

impl AjarsWebSys {
    pub fn new<P: Into<String>>(base_url: P) -> Result<Self, Error> {
        let window = window().ok_or_else(|| Error::MissingWindow)?;
        Ok(Self { window, base_url: base_url.into() })
    }

    pub fn request<'a, I: Serialize + DeserializeOwned, O: Serialize + DeserializeOwned, REST: RestType<I, O>>(
        &'a self,
        rest: &'a REST,
    ) -> RequestBuilder<'a, I, O, REST> {
        let url = format!("{}{}", &self.base_url, rest.path());

        RequestBuilder { rest, window: &self.window, url, phantom_i: PhantomData, phantom_o: PhantomData }
    }
}

pub struct RequestBuilder<'a, I: Serialize + DeserializeOwned, O: Serialize + DeserializeOwned, REST: RestType<I, O>> {
    rest: &'a REST,
    window: &'a Window,
    url: String,
    phantom_i: PhantomData<I>,
    phantom_o: PhantomData<O>,
}

impl<'a, I: Serialize + DeserializeOwned, O: Serialize + DeserializeOwned, REST: RestType<I, O>>
    RequestBuilder<'a, I, O, REST>
{
    
    /// Sends the Request to the target URL, returning a
    /// future Response.
    pub async fn send(self, data: &I) -> Result<O, Error> {

        let headers = Headers::new().map_err(|err| Error::web("failed to create Headers object", err)).unwrap();
        headers.append("Content-Type", "application/json").expect("Should be able to add a header");

        let mut opts = RequestInit::new();
        opts.mode(RequestMode::Cors);
        opts.headers(&headers);
        
        let mut uri = self.url;

        match self.rest.method() {
            HttpMethod::DELETE => {
                opts.method("DELETE");
                uri.push_str("?");
                uri.push_str(&serde_urlencoded::to_string(data).unwrap());
            }
            HttpMethod::GET => {
                opts.method("GET");
                uri.push_str("?");
                uri.push_str(&serde_urlencoded::to_string(data).unwrap());
            }
            HttpMethod::POST => {
                opts.method("POST");
                opts.body(Some(&serde_wasm_bindgen::to_value(&data).unwrap()));
            },
            HttpMethod::PUT => {
                opts.method("PUT");
                opts.body(Some(&serde_wasm_bindgen::to_value(&data).unwrap()));
            },
        };

        let request = WebRequest::new_with_str_and_init(&uri, &opts)
        .map_err(|err| Error::web(format!("failed to create request for {}", uri.to_string()), err)).expect("WebRequest::new_with_str_and_init problem");

        do_web_request(&self.window, request).await
        
    }

}