use leptos::prelude::{ServerFnError, ServerFnErrorErr}; use serde::{Deserialize, Serialize}; use thiserror::Error; use werewolves_proto::error::GameError; use crate::game::GameId; #[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)] pub enum ServerError { #[error("internal server error")] InternalServerError, #[error("not found")] NotFound, #[error("user already exists")] UserAlreadyExists, #[error("invalid credentials")] InvalidCredentials, #[error("token expired")] ExpiredToken, #[error("connection error")] ConnectionError, #[error("invalid request: {0}")] InvalidRequest(String), #[error("you're already in an active game: {0}")] AlreadyInActiveGame(GameId), #[error("{0}")] GameError(#[from] GameError), } impl leptos::prelude::FromServerFnError for ServerError { type Encoder = leptos::server_fn::codec::JsonEncoding; fn from_server_fn_error(value: ServerFnErrorErr) -> Self { match value { ServerFnErrorErr::ServerError(err) => { log::error!("server error: {err}; truncating to ServerError::InternalServerError"); ServerError::InternalServerError } ServerFnErrorErr::MiddlewareError(err) => { log::error!( "middleware error: {err}; truncating to ServerError::InternalServerError" ); ServerError::InternalServerError } ServerFnErrorErr::Request(err) => { const CONN_ERR: &str = "TypeError: NetworkError when attempting to fetch resource."; if err == CONN_ERR { Self::ConnectionError } else { Self::InvalidRequest(err) } } err => { let t = match &err { ServerFnErrorErr::Registration(_) => "Registration", ServerFnErrorErr::UnsupportedRequestMethod(_) => "UnsupportedRequestMethod", ServerFnErrorErr::Request(_) => "Request", ServerFnErrorErr::ServerError(_) => "ServerError", ServerFnErrorErr::MiddlewareError(_) => "MiddlewareError", ServerFnErrorErr::Deserialization(_) => "Deserialization", ServerFnErrorErr::Serialization(_) => "Serialization", ServerFnErrorErr::Args(_) => "Args", ServerFnErrorErr::MissingArg(_) => "MissingArg", ServerFnErrorErr::Response(_) => "Response", }; Self::InvalidRequest(format!("[{t}]: {err}")) } } } } // impl core::str::FromStr for ServerError { // type Err = core::convert::Infallible; // fn from_str(s: &str) -> Result { // panic!("ServerError::FromStr({s})") // } // } // impl From for ServerError { // fn from(err: ServerFnError) -> Self { // match err { // ServerFnError::ServerError(err) => { // log::error!("server error: {err}; truncating to ServerError::InternalServerError"); // ServerError::InternalServerError // } // ServerFnError::MiddlewareError(err) => { // log::error!( // "middleware error: {err}; truncating to ServerError::InternalServerError" // ); // ServerError::InternalServerError // } // ServerFnError::Request(err) => { // log::error!("[{err}]"); // Self::ConnectionError // } // err => { // let t = match &err { // ServerFnError::Registration(_) => "Registration", // ServerFnError::Request(_) => "Request", // ServerFnError::ServerError(_) => "ServerError", // ServerFnError::MiddlewareError(_) => "MiddlewareError", // ServerFnError::Deserialization(_) => "Deserialization", // ServerFnError::Serialization(_) => "Serialization", // ServerFnError::Args(_) => "Args", // ServerFnError::MissingArg(_) => "MissingArg", // ServerFnError::Response(_) => "Response", // ServerFnError::WrappedServerError(_) => "WrappedServerError", // }; // Self::InvalidRequest(format!("[{t}]: {err}")) // } // } // } // } impl From for ServerError { fn from(err: DatabaseError) -> Self { match err { DatabaseError::NotFound => ServerError::NotFound, DatabaseError::UserAlreadyExists => ServerError::UserAlreadyExists, #[allow(unreachable_patterns)] _ => { log::error!( "converting database error into ServerError::InternalServerError: {err}" ); ServerError::InternalServerError } } } } #[derive(Debug, Clone, PartialEq, Error)] pub enum DatabaseError { #[error("user already exists")] UserAlreadyExists, #[error("password hashing error: {0}")] PasswordHashError(String), #[error("sqlx error: {0}")] SqlxError(String), #[error("not found")] NotFound, #[error("(de)serialization error: {0}")] Serialization(String), } #[cfg(feature = "ssr")] impl From for DatabaseError { fn from(value: serde_json::Error) -> Self { Self::Serialization(value.to_string()) } } #[cfg(feature = "ssr")] impl axum::response::IntoResponse for ServerError { fn into_response(self) -> axum::response::Response { use axum::{Json, http::StatusCode}; use crate::cbor::Cbor; ( match self { ServerError::AlreadyInActiveGame(_) | ServerError::GameError(_) | ServerError::InvalidCredentials | ServerError::InvalidRequest(_) | ServerError::UserAlreadyExists => StatusCode::BAD_REQUEST, ServerError::NotFound => StatusCode::NOT_FOUND, ServerError::ConnectionError | ServerError::InternalServerError => { StatusCode::INTERNAL_SERVER_ERROR } ServerError::ExpiredToken => StatusCode::UNAUTHORIZED, }, Json(self), ) .into_response() } } #[cfg(feature = "ssr")] impl From for DatabaseError { fn from(err: sqlx::Error) -> Self { match err { sqlx::Error::RowNotFound => Self::NotFound, _ => Self::SqlxError(err.to_string()), } } } #[cfg(feature = "ssr")] impl From for DatabaseError { fn from(err: argon2::password_hash::Error) -> Self { Self::PasswordHashError(err.to_string()) } }