maia_wasm/ui/request.rs
1//! HTTP requests.
2//!
3//! This module implements some convenience functions that use the [Fetch
4//! API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make and
5//! process asynchronous HTTP requests.
6
7use serde::Serialize;
8use wasm_bindgen::JsValue;
9use wasm_bindgen_futures::JsFuture;
10use web_sys::{Request, RequestInit, Response};
11
12/// Constructs a JSON HTTP request.
13///
14/// Given a serializable value in the `json` parameter, this function serializes
15/// it to JSON with [`serde_json`] and creates a [`Request`] with that JSON as
16/// body. The URL and HTTP method of the request are given in the `url` and
17/// `method` arguments.
18pub fn json_request<T: Serialize>(url: &str, json: &T, method: &str) -> Result<Request, JsValue> {
19 let opts = RequestInit::new();
20 opts.set_method(method);
21 let json = serde_json::to_string(json)
22 .map_err(|_| format!("unable to format JSON for {method} request"))?;
23 opts.set_body(&json.into());
24 let request = Request::new_with_str_and_init(url, &opts)?;
25 request.headers().set("Content-Type", "application/json")?;
26 Ok(request)
27}
28
29/// Converts the text of a [`Response`] to a Rust [`String`].
30///
31/// This function awaits for the text of a `Response` and tries to convert it to
32/// a Rust string, which is returned.
33pub async fn response_to_string(response: &Response) -> Result<String, JsValue> {
34 Ok(JsFuture::from(response.text()?)
35 .await?
36 .as_string()
37 .ok_or("unable to convert fetch text to string")?)
38}
39
40/// Converts the text of a JSON [`Response`] to a Rust value.
41///
42/// For a deserializable Rust type `T`, this function awaits for the text of a
43/// `Response` and tries to convert it to a Rust value of type `T` by using
44/// [`serde_json`] to deserialize the JSON in the text of the response. If the
45/// deserialization is successful, the Rust value is returned.
46pub async fn response_to_json<T>(response: &Response) -> Result<T, JsValue>
47where
48 for<'a> T: serde::Deserialize<'a>,
49{
50 let json = serde_json::from_str(&response_to_string(response).await?)
51 .map_err(|_| format!("unable to parse {} JSON", std::any::type_name::<T>()))?;
52 Ok(json)
53}
54
55/// Request error.
56///
57/// This enum represents the errors that can happen with a request.
58pub enum RequestError {
59 /// The request failed on the server.
60 ///
61 /// The server has returned an HTTP error response that states that the
62 /// request failed server-side. This variant contains the error returned by
63 /// the server.
64 RequestFailed(maia_json::Error),
65 /// Other error.
66 ///
67 /// Other error has happened. The error is contained as a [`JsValue`].
68 OtherError(JsValue),
69}
70
71impl From<JsValue> for RequestError {
72 fn from(value: JsValue) -> RequestError {
73 RequestError::OtherError(value)
74 }
75}
76
77/// Ignore [`RequestError::RequestFailed`] errors.
78///
79/// This function transforms a `Result<T, RequestError>` by ignoring
80/// `RequestError::RequestFailed` in the following way. If the input `Result` is
81/// `Ok(x)`, then `Okay(Some(x))` is returned. If the input `Result` is
82/// `Err(RequestError::RequestFailed(_))`, then `Ok(None)` is returned, thus
83/// discarding the Error. If the input is `Result` is
84/// `Err(RequestError::OtherError(jsvalue))`, then `Err(jsvalue)` is returned,
85/// thus propagating the error.
86///
87/// This utility function is useful because request errors are logged by the
88/// function that does the request, so often we want to do nothing more to
89/// handle this error, but we want to handle other errors by failing a promise.
90pub fn ignore_request_failed<T>(x: Result<T, RequestError>) -> Result<Option<T>, JsValue> {
91 match x {
92 Ok(y) => Ok(Some(y)),
93 Err(RequestError::RequestFailed(_)) => Ok(None),
94 Err(RequestError::OtherError(err)) => Err(err),
95 }
96}