werewolves/api/src/error.rs

194 lines
6.9 KiB
Rust
Raw Normal View History

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<Self, Self::Err> {
// panic!("ServerError::FromStr({s})")
// }
// }
// impl From<ServerFnError> 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<DatabaseError> 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<serde_json::Error> 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<sqlx::Error> 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<argon2::password_hash::Error> for DatabaseError {
fn from(err: argon2::password_hash::Error) -> Self {
Self::PasswordHashError(err.to_string())
}
}