1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4use wasm_bindgen::prelude::*;
5use wasm_bindgen_futures::JsFuture;
6use web_sys::{Request as WebRequest, RequestInit, Response as WebResponse};
7
8pub use web_sys::RequestMode;
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Debug)]
14pub enum Error {
15 JsError(JsValue),
17 HttpError(u16, String),
19 JsonError(String),
21}
22
23impl From<JsValue> for Error {
24 fn from(value: JsValue) -> Self {
25 Error::JsError(value)
26 }
27}
28
29impl From<serde_json::Error> for Error {
30 fn from(err: serde_json::Error) -> Self {
31 Error::JsonError(err.to_string())
32 }
33}
34
35#[derive(Debug, Clone, Copy)]
37pub enum Method {
38 GET,
39 POST,
40 PUT,
41 DELETE,
42 PATCH,
43 HEAD,
44 OPTIONS,
45}
46
47impl Method {
48 fn as_str(&self) -> &'static str {
49 match self {
50 Method::GET => "GET",
51 Method::POST => "POST",
52 Method::PUT => "PUT",
53 Method::DELETE => "DELETE",
54 Method::PATCH => "PATCH",
55 Method::HEAD => "HEAD",
56 Method::OPTIONS => "OPTIONS",
57 }
58 }
59}
60
61pub struct RequestBuilder {
63 url: String,
64 method: Method,
65 headers: HashMap<String, String>,
66 body: Option<String>,
67 mode: RequestMode,
68}
69
70impl RequestBuilder {
71 fn new(method: Method, url: impl Into<String>) -> Self {
72 Self {
73 url: url.into(),
74 method,
75 headers: HashMap::new(),
76 body: None,
77 mode: RequestMode::Cors,
78 }
79 }
80
81 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
83 self.headers.insert(key.into(), value.into());
84 self
85 }
86
87 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
89 self.headers.extend(headers);
90 self
91 }
92
93 pub fn mode(mut self, mode: RequestMode) -> Self {
95 self.mode = mode;
96 self
97 }
98
99 pub fn body(mut self, body: impl Into<String>) -> Self {
101 self.body = Some(body.into());
102 self
103 }
104
105 pub fn json<T: Serialize>(mut self, json: &T) -> Result<Self> {
107 let body = serde_json::to_string(json)?;
108 self.body = Some(body);
109 self.headers
110 .insert("Content-Type".to_string(), "application/json".to_string());
111 Ok(self)
112 }
113
114 pub async fn send(self) -> Result<Response> {
116 let opts = RequestInit::new();
117 opts.set_method(self.method.as_str());
118 opts.set_mode(self.mode);
119
120 if let Some(body) = &self.body {
121 opts.set_body(&JsValue::from_str(body));
122 }
123
124 let request = WebRequest::new_with_str_and_init(&self.url, &opts)?;
125 let headers = request.headers();
126
127 for (key, value) in &self.headers {
128 headers.set(key, value)?;
129 }
130
131 let window = web_sys::window()
132 .ok_or_else(|| Error::JsError(JsValue::from_str("Failed to get window")))?;
133
134 let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
135 let web_response: WebResponse = resp_value
136 .dyn_into()
137 .map_err(|_| Error::JsError(JsValue::from_str("Response conversion failed")))?;
138
139 Ok(Response::from_web_response(web_response))
140 }
141}
142
143pub struct Response {
145 inner: WebResponse,
146}
147
148impl Response {
149 fn from_web_response(response: WebResponse) -> Self {
150 Self { inner: response }
151 }
152
153 pub fn status(&self) -> u16 {
155 self.inner.status()
156 }
157
158 pub fn ok(&self) -> bool {
160 self.inner.ok()
161 }
162
163 pub fn header(&self, name: &str) -> Result<Option<String>> {
165 Ok(self.inner.headers().get(name)?)
166 }
167
168 pub async fn text(&self) -> Result<String> {
170 let promise = self.inner.text().map_err(Error::JsError)?;
171 let text = JsFuture::from(promise).await?;
172
173 text.as_string()
174 .ok_or_else(|| Error::JsError(JsValue::from_str("Failed to convert to string")))
175 }
176
177 pub async fn json<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
179 let text = self.text().await?;
180 Ok(serde_json::from_str(&text)?)
181 }
182
183 pub async fn json_value(&self) -> Result<Value> {
185 self.json().await
186 }
187
188 pub async fn bytes(&self) -> Result<Vec<u8>> {
190 let promise = self.inner.array_buffer().map_err(Error::JsError)?;
191 let array_buffer = JsFuture::from(promise).await?;
192 let uint8_array = js_sys::Uint8Array::new(&array_buffer);
193 Ok(uint8_array.to_vec())
194 }
195
196 pub fn error_for_status(self) -> Result<Self> {
198 if self.ok() {
199 Ok(self)
200 } else {
201 let status = self.status();
202 let text = format!("HTTP Error {}", status);
203 Err(Error::HttpError(status, text))
204 }
205 }
206}
207
208pub struct Client;
210
211impl Client {
212 pub fn get(&self, url: impl Into<String>) -> RequestBuilder {
214 RequestBuilder::new(Method::GET, url)
215 }
216
217 pub fn post(&self, url: impl Into<String>) -> RequestBuilder {
219 RequestBuilder::new(Method::POST, url)
220 }
221
222 pub fn put(&self, url: impl Into<String>) -> RequestBuilder {
224 RequestBuilder::new(Method::PUT, url)
225 }
226
227 pub fn delete(&self, url: impl Into<String>) -> RequestBuilder {
229 RequestBuilder::new(Method::DELETE, url)
230 }
231
232 pub fn patch(&self, url: impl Into<String>) -> RequestBuilder {
234 RequestBuilder::new(Method::PATCH, url)
235 }
236
237 pub fn head(&self, url: impl Into<String>) -> RequestBuilder {
239 RequestBuilder::new(Method::HEAD, url)
240 }
241}
242
243pub async fn get(url: impl Into<String>) -> Result<Response> {
245 Client.get(url).send().await
246}
247
248pub async fn post_json<T: Serialize>(url: impl Into<String>, json: &T) -> Result<Response> {
250 Client.post(url).json(json)?.send().await
251}