recipes/frontend/src/ron_request.rs

156 lines
4.1 KiB
Rust

/// This module provides a simple API for making HTTP requests to the server.
/// For requests with a body (POST, PUT, PATCH, etc.), it uses the RON format.
/// The RON data structures should come from the `web_api` module.
/// For requests with parameters (GET), it uses the HTML form format.
use common::web_api;
use gloo::net::http::{Request, RequestBuilder};
use serde::{Serialize, de::DeserializeOwned};
use thiserror::Error;
use crate::toast::{self, Level};
#[derive(Error, Debug)]
pub enum Error {
#[error("Gloo error: {0}")]
Gloo(#[from] gloo::net::Error),
#[error("RON Spanned error: {0}")]
RonSpanned(#[from] ron::error::SpannedError),
#[error("RON Error: {0}")]
Ron(#[from] ron::error::Error),
#[error("HTTP error: {0}")]
Http(String),
#[error("Unknown error: {0}")]
Other(String),
}
type Result<T> = std::result::Result<T, Error>;
const CONTENT_TYPE: &str = "Content-Type"; // TODO: take it from the http crate.
async fn req_with_body<T, U>(url: &str, body: U, method_fn: fn(&str) -> RequestBuilder) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
let request_builder = method_fn(url).header(
CONTENT_TYPE,
common::consts::MIME_TYPE_RON.to_str().unwrap(),
);
send_req(request_builder.body(web_api::to_string(body)?)?).await
}
async fn req_with_params<T, U>(
url: &str,
params: U,
method_fn: fn(&str) -> RequestBuilder,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
let mut url = url.to_string();
url.push('?');
serde_html_form::ser::push_to_string(&mut url, params).unwrap();
let request_builder = method_fn(&url);
send_req(request_builder.build()?).await
}
async fn req<T>(url: &str, method_fn: fn(&str) -> RequestBuilder) -> Result<T>
where
T: DeserializeOwned,
{
let request_builder = method_fn(url);
send_req(request_builder.build()?).await
}
async fn send_req<T>(request: Request) -> Result<T>
where
T: DeserializeOwned,
{
match request.send().await {
Err(error) => {
toast::show_message_level(Level::Error, &format!("Internal server error: {}", error));
Err(Error::Gloo(error))
}
Ok(response) => {
if !response.ok() {
toast::show_message_level(
Level::Error,
&format!("HTTP error: {}", response.status_text()),
);
Err(Error::Http(response.status_text()))
} else {
let mut r = response.binary().await?;
// An empty response is considered to be an unit value.
if r.is_empty() {
r = b"()".to_vec();
}
Ok(ron::de::from_bytes::<T>(&r)?)
}
}
}
}
/// Sends a request to the server with the given API name and body.
/// # Example
/// ```rust
/// use common::web_api;
/// let body = web_api::SetLang { lang : "en".to_string() };
/// request::put::<(), _>("lang", body).await;
/// ```
pub async fn put<T, U>(url: &str, body: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_body(url, body, Request::put).await
}
pub async fn patch<T, U>(url: &str, body: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_body(url, body, Request::patch).await
}
pub async fn post<T, U>(url: &str, body: Option<U>) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
match body {
Some(body) => req_with_body(url, body, Request::post).await,
None => req(url, Request::post).await,
}
}
pub async fn delete<T, U>(url: &str, body: Option<U>) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
match body {
Some(body) => req_with_body(url, body, Request::delete).await,
None => req(url, Request::delete).await,
}
}
pub async fn get<T>(url: &str) -> Result<T>
where
T: DeserializeOwned,
{
req(url, Request::get).await
}
pub async fn get_with_params<T, U>(url: &str, params: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_params(url, params, Request::get).await
}