use axum::{http::StatusCode, Json}; use serde_json::json; pub type Result = std::result::Result; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("IO error: {0}")] IO(#[from] std::io::Error), #[error("Config file error: {0}")] Toml(#[from] toml::de::Error), #[error("Config error: {0}")] Config(String), #[error("Axum error: {0}")] Axum(#[from] axum::Error), #[error("Http error: {0}")] Http(#[from] axum::http::Error), #[error("Header error: {0}")] Header(#[from] axum::http::header::InvalidHeaderValue), #[error("Json error: {0}")] Json(#[from] serde_json::Error), #[error("JSON web token error: {0}")] Jwt(#[from] jsonwebtoken::errors::Error), #[error("Token error: {0}")] Token(#[from] axum_extra::headers::authorization::InvalidBearerToken), #[error("Database error: {0}")] Sqlx(#[from] sqlx::Error), #[error("Migration error: {0}")] Migration(#[from] sqlx::migrate::MigrateError), #[error("Failed to hash password: {0}")] PasswordHash(#[source] argon2::password_hash::Error), #[error("User not found")] UserNotFound, #[error("User with that email already exists")] EmailExists, #[error("Invalid email: {0}")] EmailInvalid(#[from] email_address::Error), #[error("Invalid email or password")] Authorization(#[from] AuthError), #[error("{0}")] Other(String), } impl From for Error { fn from(value: argon2::password_hash::Error) -> Self { match value { argon2::password_hash::Error::Password => Self::Authorization(AuthError::LoginInvalid), _ => Self::PasswordHash(value), } } } impl axum::response::IntoResponse for Error { fn into_response(self) -> axum::response::Response { // TODO: implement [rfc7807](https://www.rfc-editor.org/rfc/rfc7807.html) let status = match &self { Self::UserNotFound => StatusCode::NOT_FOUND, Self::EmailExists => StatusCode::CONFLICT, Self::EmailInvalid(_) => StatusCode::UNPROCESSABLE_ENTITY, Self::Authorization(_) => StatusCode::UNAUTHORIZED, _ => StatusCode::INTERNAL_SERVER_ERROR, }; ( status, Json(json!({ "status": status.to_string(), "detail": self.to_string(), })), ) .into_response() } } #[derive(thiserror::Error, Debug)] pub enum AuthError { #[error("Invalid email or password")] LoginInvalid, #[error("Authorization token not found")] JwtNotFound, #[error("The user belonging to this token no longer exists")] UserNotFound, #[error("Invalid authorization token")] JwtValidation(#[from] jsonwebtoken::errors::Error), #[error("Jwk not found")] JwkNotFound, } impl axum::response::IntoResponse for AuthError { fn into_response(self) -> axum::response::Response { StatusCode::UNAUTHORIZED.into_response() } }