Cleaning
This commit is contained in:
parent
198bff6e4a
commit
6f014ef238
19 changed files with 118 additions and 81 deletions
|
|
@ -355,11 +355,11 @@ fn url_rewriting(mut req: Request) -> Request {
|
|||
}
|
||||
|
||||
/// The language associated to the current HTTP request is defined in the current order:
|
||||
/// - Extraction from the url: like in `/fr/recipe/view/42`
|
||||
/// - Get from the user database record.
|
||||
/// - Get from the cookie.
|
||||
/// - Get from the HTTP header `accept-language`.
|
||||
/// - Set as `translation::DEFAULT_LANGUAGE_CODE`.
|
||||
/// 1. Extraction from the url: like in `/fr/recipe/view/42`
|
||||
/// 2. Get from the user database record.
|
||||
/// 3. Get from the cookie.
|
||||
/// 4. Get from the HTTP header `accept-language`.
|
||||
/// 5. Set as [translation::DEFAULT_LANGUAGE_CODE].
|
||||
async fn context(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
State(connection): State<db::Connection>,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ pub struct Config {
|
|||
#[serde(default = "port_default")]
|
||||
pub port: u16,
|
||||
|
||||
/// The email address used when sending validation email.
|
||||
#[serde(default = "email_address_default")]
|
||||
pub email_address: String,
|
||||
|
||||
#[serde(default = "smtp_relay_address_default")]
|
||||
pub smtp_relay_address: String,
|
||||
|
||||
|
|
@ -44,11 +48,14 @@ pub struct Config {
|
|||
fn port_default() -> u16 {
|
||||
8082
|
||||
}
|
||||
|
||||
fn smtp_relay_address_default() -> String {
|
||||
"mail.something.com".to_string()
|
||||
}
|
||||
|
||||
fn email_address_default() -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn smtp_login_default() -> String {
|
||||
"login".to_string()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ pub const COOKIE_LANG_NAME: &str = "lang";
|
|||
/// (cookie authentication, password reset, validation token).
|
||||
pub const TOKEN_SIZE: usize = 32;
|
||||
|
||||
// TODO: Move it in conf.ron.
|
||||
pub const EMAIL_ADDRESS: &str = "recipes@gburri.org";
|
||||
|
||||
/// When sending a validation email,
|
||||
/// the server has this duration to wait for a response from the SMTP server.
|
||||
pub const SEND_EMAIL_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use lettre::{
|
|||
AsyncTransport, Message, Tokio1Executor,
|
||||
transport::smtp::{AsyncSmtpTransport, authentication::Credentials},
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use crate::consts;
|
||||
|
||||
|
|
@ -22,7 +21,13 @@ pub enum Error {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
pub trait EmailServiceTrait: Send + Sync {
|
||||
async fn send_email(&self, email: &str, title: &str, message: &str) -> Result<(), Error>;
|
||||
async fn send_email(
|
||||
&self,
|
||||
email_sender: &str,
|
||||
email_receiver: &str,
|
||||
title: &str,
|
||||
message: &str,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct EmailService {
|
||||
|
|
@ -49,11 +54,17 @@ impl EmailService {
|
|||
impl EmailServiceTrait for EmailService {
|
||||
/// A function to send an email using the given SMTP address.
|
||||
/// It may timeout if the SMTP server is not reachable, see [consts::SEND_EMAIL_TIMEOUT].
|
||||
async fn send_email(&self, email: &str, title: &str, message: &str) -> Result<(), Error> {
|
||||
async fn send_email(
|
||||
&self,
|
||||
email_sender: &str,
|
||||
email_receiver: &str,
|
||||
title: &str,
|
||||
message: &str,
|
||||
) -> Result<(), Error> {
|
||||
let email = Message::builder()
|
||||
.message_id(None)
|
||||
.from(consts::EMAIL_ADDRESS.parse()?)
|
||||
.to(email.parse()?)
|
||||
.from(email_sender.parse()?)
|
||||
.to(email_receiver.parse()?)
|
||||
.subject(title)
|
||||
.body(message.to_string())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use argon2::{
|
|||
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||||
};
|
||||
|
||||
const MAX_LENGTH_PASSWORD: usize = 255;
|
||||
|
||||
fn get_argon2<'k>() -> Argon2<'k> {
|
||||
// Note: It's not neccessary to have only one Argon2 object, creating a new one
|
||||
// Note: It's not necessary to have only one Argon2 object, creating a new one
|
||||
// when we need it is lightweight.
|
||||
Argon2::new(
|
||||
argon2::Algorithm::Argon2id,
|
||||
|
|
@ -22,6 +24,14 @@ fn get_argon2<'k>() -> Argon2<'k> {
|
|||
}
|
||||
|
||||
pub fn hash(password: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
if password.len() > MAX_LENGTH_PASSWORD {
|
||||
return Err(format!("Password max length is {}", MAX_LENGTH_PASSWORD).into());
|
||||
}
|
||||
|
||||
if password.is_empty() {
|
||||
return Err("Password can't be empty".into());
|
||||
}
|
||||
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = get_argon2();
|
||||
argon2
|
||||
|
|
|
|||
|
|
@ -44,7 +44,14 @@ impl Log {
|
|||
P: AsRef<Path>,
|
||||
{
|
||||
if !directory.as_ref().exists() {
|
||||
fs::DirBuilder::new().create(&directory).unwrap();
|
||||
fs::DirBuilder::new()
|
||||
.create(&directory)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Unable to create directory: {}",
|
||||
directory.as_ref().to_string_lossy()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let file_appender = RollingFileAppender::builder()
|
||||
|
|
@ -78,7 +85,7 @@ impl Log {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_stdout_only() -> Self {
|
||||
pub fn new_to_stdout_only() -> Self {
|
||||
let layer_stdout = tracing_subscriber::fmt::layer()
|
||||
.with_writer(std::io::stdout.with_max_level(TRACING_LEVEL))
|
||||
.with_thread_ids(TRACING_DISPLAY_THREAD)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! An Axum extractor for HTTP body containing RON data (Rusty Object Notation).
|
||||
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
extract::{FromRequest, Request},
|
||||
|
|
|
|||
|
|
@ -18,13 +18,15 @@ pub struct RonError {
|
|||
|
||||
impl axum::response::IntoResponse for RonError {
|
||||
fn into_response(self) -> Response {
|
||||
let ron_as_str = ron_api::to_string(&self);
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
[(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
|
||||
ron_as_str,
|
||||
)
|
||||
.into_response()
|
||||
match ron_api::to_string(&self) {
|
||||
Ok(ron_as_str) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
[(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
|
||||
ron_as_str,
|
||||
)
|
||||
.into_response(),
|
||||
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +46,7 @@ pub fn ron_error(status: StatusCode, message: &str) -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn ron_error_not_autorized() -> ErrorResponse {
|
||||
pub fn ron_error_not_authorized() -> ErrorResponse {
|
||||
ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
consts::NOT_AUTHORIZED_MESSAGE,
|
||||
|
|
@ -58,16 +60,19 @@ where
|
|||
ron_response(StatusCode::OK, ron)
|
||||
}
|
||||
|
||||
pub fn ron_response<T>(status: StatusCode, ron: T) -> impl IntoResponse
|
||||
pub fn ron_response<T>(status: StatusCode, ron: T) -> Response
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let ron_as_str = ron_api::to_string(&ron);
|
||||
(
|
||||
status,
|
||||
[(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
|
||||
ron_as_str,
|
||||
)
|
||||
match ron_api::to_string(&ron) {
|
||||
Ok(ron_as_str) => (
|
||||
status,
|
||||
[(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
|
||||
ron_as_str,
|
||||
)
|
||||
.into_response(),
|
||||
Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_body<T>(body: Bytes) -> Result<T, RonError>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use axum::{
|
|||
response::{Html, IntoResponse},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{
|
||||
app::{Context, Result},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use axum::response::Result;
|
||||
|
||||
use crate::{data::db, data::model, ron_utils::ron_error_not_autorized};
|
||||
use crate::{data::db, data::model, ron_utils::ron_error_not_authorized};
|
||||
|
||||
pub async fn check_user_rights_recipe(
|
||||
connection: &db::Connection,
|
||||
|
|
@ -9,7 +9,7 @@ pub async fn check_user_rights_recipe(
|
|||
) -> Result<()> {
|
||||
match user {
|
||||
Some(user) if connection.can_edit_recipe(user.id, recipe_id).await? => Ok(()),
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ pub async fn check_user_rights_recipe_group(
|
|||
) -> Result<()> {
|
||||
match user {
|
||||
Some(user) if connection.can_edit_recipe_group(user.id, group_id).await? => Ok(()),
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ pub async fn check_user_rights_recipe_groups(
|
|||
{
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ pub async fn check_user_rights_recipe_step(
|
|||
) -> Result<()> {
|
||||
match user {
|
||||
Some(user) if connection.can_edit_recipe_step(user.id, step_id).await? => Ok(()),
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ pub async fn check_user_rights_recipe_steps(
|
|||
{
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ pub async fn check_user_rights_recipe_ingredient(
|
|||
{
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ pub async fn check_user_rights_recipe_ingredients(
|
|||
{
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +116,6 @@ pub async fn check_user_rights_shopping_list_entry(
|
|||
{
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(ron_error_not_autorized()),
|
||||
_ => Err(ron_error_not_authorized()),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use tracing::{error, warn};
|
|||
|
||||
use crate::{
|
||||
app::{AppState, Context, Result},
|
||||
config::Config,
|
||||
consts,
|
||||
data::db,
|
||||
email,
|
||||
|
|
@ -88,6 +89,7 @@ enum SignUpError {
|
|||
#[debug_handler(state = AppState)]
|
||||
pub async fn sign_up_post(
|
||||
Host(host): Host,
|
||||
State(config): State<Config>,
|
||||
State(connection): State<db::Connection>,
|
||||
State(email_service): State<Arc<dyn email::EmailServiceTrait>>,
|
||||
Extension(context): Extension<Context>,
|
||||
|
|
@ -171,6 +173,7 @@ pub async fn sign_up_post(
|
|||
let email = form_data.email.clone();
|
||||
match email_service
|
||||
.send_email(
|
||||
&config.email_address,
|
||||
&email,
|
||||
context.tr.t(Sentence::SignUpEmailTitle),
|
||||
&context.tr.tp(
|
||||
|
|
@ -385,7 +388,6 @@ pub async fn sign_in_post(
|
|||
#[debug_handler]
|
||||
pub async fn sign_out(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(context): Extension<Context>,
|
||||
req: Request<Body>,
|
||||
) -> Result<(CookieJar, Redirect)> {
|
||||
let mut jar = CookieJar::from_headers(req.headers());
|
||||
|
|
@ -450,6 +452,7 @@ enum AskResetPasswordError {
|
|||
#[debug_handler(state = AppState)]
|
||||
pub async fn ask_reset_password_post(
|
||||
Host(host): Host,
|
||||
State(config): State<Config>,
|
||||
State(connection): State<db::Connection>,
|
||||
State(email_service): State<Arc<dyn email::EmailServiceTrait>>,
|
||||
Extension(context): Extension<Context>,
|
||||
|
|
@ -522,6 +525,7 @@ pub async fn ask_reset_password_post(
|
|||
let url = utils::get_url_from_host(&host);
|
||||
match email_service
|
||||
.send_email(
|
||||
&config.email_address,
|
||||
&form_data.email,
|
||||
context.tr.t(Sentence::AskResetEmailTitle),
|
||||
&context.tr.tp(
|
||||
|
|
@ -755,6 +759,7 @@ enum ProfileUpdateError {
|
|||
#[debug_handler(state = AppState)]
|
||||
pub async fn edit_user_post(
|
||||
Host(host): Host,
|
||||
State(config): State<Config>,
|
||||
State(connection): State<db::Connection>,
|
||||
State(email_service): State<Arc<dyn email::EmailServiceTrait>>,
|
||||
Extension(context): Extension<Context>,
|
||||
|
|
@ -859,6 +864,7 @@ pub async fn edit_user_post(
|
|||
let email = form_data.email.clone();
|
||||
match email_service
|
||||
.send_email(
|
||||
&config.email_address,
|
||||
&email,
|
||||
context.tr.t(Sentence::ProfileFollowEmailTitle),
|
||||
&context.tr.tp(
|
||||
|
|
|
|||
|
|
@ -19,12 +19,15 @@ pub struct Tr {
|
|||
}
|
||||
|
||||
impl Tr {
|
||||
/// Create a new translation object.
|
||||
/// See [available_codes].
|
||||
pub fn new(code: &str) -> Self {
|
||||
Self {
|
||||
lang: get_language_translation(code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate the given sentence according to the current language.
|
||||
pub fn t<T>(&self, sentence: T) -> &'static str
|
||||
where
|
||||
T: Borrow<Sentence>,
|
||||
|
|
@ -32,11 +35,13 @@ impl Tr {
|
|||
self.lang.get(sentence)
|
||||
}
|
||||
|
||||
/// Translate the given sentence id according to the current language.
|
||||
pub fn t_from_id(&self, sentence_id: i64) -> &'static str {
|
||||
self.lang.get_from_id(sentence_id)
|
||||
}
|
||||
|
||||
/// Translate a sentence with parameters.
|
||||
/// Placeholders "{}" are replaced in the same order as the given parameters.
|
||||
pub fn tp(&self, sentence: Sentence, params: &[Box<dyn ToString + Send>]) -> String {
|
||||
let text = self.lang.get(sentence);
|
||||
let params_as_string: Vec<String> = params.iter().map(|p| p.to_string()).collect();
|
||||
|
|
@ -118,20 +123,6 @@ impl Tr {
|
|||
}
|
||||
}
|
||||
|
||||
// #[macro_export]
|
||||
// macro_rules! t {
|
||||
// ($self:expr, $str:expr) => {
|
||||
// $self.t($str)
|
||||
// };
|
||||
// ($self:expr, $str:expr, $( $x:expr ),+ ) => {
|
||||
// {
|
||||
// let mut result = $self.t($str);
|
||||
// $( result = result.replacen("{}", &$x.to_string(), 1); )+
|
||||
// result
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StoredLanguage {
|
||||
code: String,
|
||||
|
|
@ -151,7 +142,7 @@ struct Language {
|
|||
const UNABLE_TO_FIND_TRANSLATION_MESSAGE: &str = "Unable to find translation";
|
||||
|
||||
impl Language {
|
||||
pub fn from_stored_language(stored_language: StoredLanguage) -> Self {
|
||||
fn from_stored_language(stored_language: StoredLanguage) -> Self {
|
||||
Self {
|
||||
code: stored_language.code,
|
||||
territory: stored_language.territory,
|
||||
|
|
@ -166,15 +157,14 @@ impl Language {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get<T>(&'static self, sentence: T) -> &'static str
|
||||
fn get<T>(&'static self, sentence: T) -> &'static str
|
||||
where
|
||||
T: Borrow<Sentence>,
|
||||
{
|
||||
let sentence_cloned: Sentence = sentence.borrow().clone();
|
||||
self.get_from_id(sentence_cloned as i64)
|
||||
self.get_from_id(*sentence.borrow() as i64)
|
||||
}
|
||||
|
||||
pub fn get_from_id(&'static self, sentence_id: i64) -> &'static str {
|
||||
fn get_from_id(&'static self, sentence_id: i64) -> &'static str {
|
||||
let text: &str = match self.translation.get(sentence_id as usize) {
|
||||
None => UNABLE_TO_FIND_TRANSLATION_MESSAGE,
|
||||
Some(text) => text,
|
||||
|
|
@ -187,6 +177,7 @@ impl Language {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns all available languages as a tuple (code, name).
|
||||
pub fn available_languages() -> Vec<(&'static str, &'static str)> {
|
||||
TRANSLATIONS
|
||||
.iter()
|
||||
|
|
@ -194,6 +185,7 @@ pub fn available_languages() -> Vec<(&'static str, &'static str)> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all available codes.
|
||||
pub fn available_codes() -> Vec<&'static str> {
|
||||
TRANSLATIONS.iter().map(|tr| tr.code.as_ref()).collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,13 @@ pub fn get_ip_and_user_agent(headers: &HeaderMap, remote_address: SocketAddr) ->
|
|||
}
|
||||
|
||||
pub fn get_url_from_host(host: &str) -> String {
|
||||
let port: Option<u16> = 'p: {
|
||||
let port: Option<u16> = {
|
||||
let split_port: Vec<&str> = host.split(':').collect();
|
||||
if split_port.len() == 2 {
|
||||
if let Ok(p) = split_port[1].parse::<u16>() {
|
||||
break 'p Some(p);
|
||||
}
|
||||
split_port[1].parse::<u16>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
};
|
||||
format!(
|
||||
"http{}://{}",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{# Needed by the frontend toast module. #}
|
||||
<div id="toasts">
|
||||
<div class="toast">
|
||||
<img class="icon" width="24" height="24" alt="icon" src="">
|
||||
<img class="icon" width="24" height="24" alt="icon" src="/static/success.svg">
|
||||
<div class="content user-message"></div>
|
||||
<span class="close button">✖</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -119,18 +119,19 @@ async fn sign_up() -> Result<(), Box<dyn Error>> {
|
|||
let mut mock_email_service = utils::mock_email::MockEmailService::new();
|
||||
mock_email_service
|
||||
.expect_send_email()
|
||||
.withf_st(move |email, _title, message| {
|
||||
.withf(|_email_sender, email_receiver, _title, _message| {
|
||||
email_receiver == "president@spaceball.planet"
|
||||
})
|
||||
.times(1)
|
||||
.returning_st(move |_email_sender, _email_receiver, _title, message| {
|
||||
sscanf!(
|
||||
message,
|
||||
"Follow this link to confirm your inscription, http://127.0.0.1:8000{}",
|
||||
*validation_url_clone.borrow_mut()
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", message);
|
||||
email == "president@spaceball.planet"
|
||||
})
|
||||
.times(1)
|
||||
.returning(|_email, _title, _message| Ok(()));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let state = utils::common_state_with_email_service(Arc::new(mock_email_service)).await?;
|
||||
let server = TestServer::new(app::make_service(state))?;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ mock! {
|
|||
pub EmailService {}
|
||||
#[async_trait]
|
||||
impl email::EmailServiceTrait for EmailService {
|
||||
async fn send_email(&self, email: &str, title: &str, message: &str)
|
||||
async fn send_email(&self, email_sender: &str, email_receiver: &str, title: &str, message: &str)
|
||||
-> Result<(), email::Error>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,10 +240,9 @@ pub struct ShoppingListItem {
|
|||
|
||||
/*** Misc ***/
|
||||
|
||||
pub fn to_string<T>(ron: T) -> String
|
||||
pub fn to_string<T>(ron: T) -> Result<String, ron::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// TODO: handle'unwrap'.
|
||||
to_string_pretty(&ron, PrettyConfig::new()).unwrap()
|
||||
to_string_pretty(&ron, PrettyConfig::new())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use serde::Deserialize;
|
|||
use strum::EnumCount;
|
||||
|
||||
#[repr(i64)]
|
||||
#[derive(Debug, Clone, EnumCount, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, EnumCount, Deserialize)]
|
||||
pub enum Sentence {
|
||||
MainTitle = 0,
|
||||
CreateNewRecipe,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ pub enum Error {
|
|||
Gloo(#[from] gloo::net::Error),
|
||||
|
||||
#[error("RON Spanned error: {0}")]
|
||||
Ron(#[from] ron::error::SpannedError),
|
||||
RonSpanned(#[from] ron::error::SpannedError),
|
||||
|
||||
#[error("RON Error: {0}")]
|
||||
Ron(#[from] ron::error::Error),
|
||||
|
||||
#[error("HTTP error: {0}")]
|
||||
Http(String),
|
||||
|
|
@ -40,7 +43,7 @@ where
|
|||
{
|
||||
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
|
||||
send_req(request_builder.body(ron_api::to_string(body)?)?).await
|
||||
}
|
||||
|
||||
async fn req_with_params<T, U>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue