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;
async fn into_http_response<O: Serialize + DeserializeOwned>(response: WebResponse) -> Result<O, Error> {
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>
{
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
}
}