/// 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 = std::result::Result; const CONTENT_TYPE: &str = "Content-Type"; // TODO: take it from the http crate. async fn req_with_body(url: &str, body: U, method_fn: fn(&str) -> RequestBuilder) -> Result 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( url: &str, params: U, method_fn: fn(&str) -> RequestBuilder, ) -> Result 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(url: &str, method_fn: fn(&str) -> RequestBuilder) -> Result where T: DeserializeOwned, { let request_builder = method_fn(url); send_req(request_builder.build()?).await } async fn send_req(request: Request) -> Result 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::(&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(url: &str, body: U) -> Result where T: DeserializeOwned, U: Serialize, { req_with_body(url, body, Request::put).await } pub async fn patch(url: &str, body: U) -> Result where T: DeserializeOwned, U: Serialize, { req_with_body(url, body, Request::patch).await } pub async fn post(url: &str, body: Option) -> Result 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(url: &str, body: Option) -> Result 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(url: &str) -> Result where T: DeserializeOwned, { req(url, Request::get).await } pub async fn get_with_params(url: &str, params: U) -> Result where T: DeserializeOwned, U: Serialize, { req_with_params(url, params, Request::get).await }