recipes/frontend/src/request.rs

115 lines
2.9 KiB
Rust

use common::ron_api;
use gloo::net::http::{Request, RequestBuilder};
use serde::{de::DeserializeOwned, Serialize};
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}")]
Ron(#[from] ron::error::SpannedError),
#[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";
const CONTENT_TYPE_RON: &str = "application/ron";
async fn req_with_body<T, U>(
api_name: &str,
body: U,
method_fn: fn(&str) -> RequestBuilder,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
let url = format!("/ron-api/{}", api_name);
let request_builder = method_fn(&url).header(CONTENT_TYPE, CONTENT_TYPE_RON);
send_req(request_builder.body(ron_api::to_string(body))?).await
}
async fn req_with_params<T, U>(
api_name: &str,
params: U,
method_fn: fn(&str) -> RequestBuilder,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
let mut url = format!("/ron-api/{}?", api_name);
serde_html_form::ser::push_to_string(&mut url, params).unwrap();
let request_builder = method_fn(&url).header(CONTENT_TYPE, CONTENT_TYPE_RON);
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::Info, &format!("Internal server error: {}", error));
Err(Error::Gloo(error))
}
Ok(response) => {
if !response.ok() {
toast::show_message(
Level::Info,
&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)?)
}
}
}
}
pub async fn put<T, U>(api_name: &str, body: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_body(api_name, body, Request::put).await
}
pub async fn post<T, U>(api_name: &str, body: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_body(api_name, body, Request::post).await
}
pub async fn delete<T, U>(api_name: &str, body: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_body(api_name, body, Request::delete).await
}
pub async fn get<T, U>(api_name: &str, params: U) -> Result<T>
where
T: DeserializeOwned,
U: Serialize,
{
req_with_params(api_name, params, Request::get).await
}