use chrono::{DateTime, Duration, Utc};
pub type Result<T> = core::result::Result<T, CoreError>;
#[derive(thiserror::Error, Debug)]
pub enum CoreError {
#[error("bad request: {0}")]
BadRequest(String),
#[error(transparent)]
Validation(#[from] ValidationError),
#[error(transparent)]
Unauthenticated(#[from] AuthError),
#[error(transparent)]
Forbidden(#[from] ForbiddenReason),
#[error(transparent)]
NotFound(#[from] ResourceKind),
#[error(transparent)]
Conflict(#[from] ConflictReason),
#[error("unsupported media type: {0}")]
UnsupportedMediaType(String),
#[error("rate limit exceeded: {limit} requests allowed per {window:?}")]
RateLimitExceeded { limit: u32, window: Duration },
#[error(transparent)]
Internal(#[from] InternalError),
}
#[derive(Debug, Clone)]
pub enum CredentialField {
Username,
Email,
Password,
EmailOrPassword,
Token,
ApiKey,
ObjectId,
}
#[derive(Debug, Clone)]
pub enum TokenErrorType {
Token,
AccessToken,
RefreshToken,
SessionToken,
PasswordResetToken,
EmailVerificationToken,
}
#[derive(thiserror::Error, Debug)]
pub enum AuthError {
#[error("{token_type:?} expired at {expired_at}")]
TokenExpired {
token_type: TokenErrorType,
expired_at: DateTime<Utc>,
},
#[error("{token_type:?} token has an invalid signature")]
TokenInvalidSignature { token_type: TokenErrorType },
#[error("{token_type:?} token has an invalid audience")]
TokenInvalidAudience { token_type: TokenErrorType },
#[error("{token_type:?} token has an invalid issuer")]
TokenInvalidIssuer { token_type: TokenErrorType },
#[error("{token_type:?} token uses an invalid or missing algorithm")]
TokenInvalidAlgorithm { token_type: TokenErrorType },
#[error("{token_type:?} token is malformed")]
TokenMalformed { token_type: TokenErrorType },
#[error("{token_type:?} has already been used")]
TokenReplay { token_type: TokenErrorType },
#[error("{token_type:?} is invalid")]
TokenInvalid { token_type: TokenErrorType },
#[error("invalid credentials for {field:?}")]
InvalidCredentials { field: CredentialField },
#[error("account is not verified")]
AccountNotVerified,
#[error("jwt is not configured")]
JwtNotConfigured,
}
#[derive(thiserror::Error, Debug)]
pub enum ForbiddenReason {
#[error("insufficient permissions to perform this action")]
InsufficientPermissions,
#[error("account has been suspended")]
AccountSuspended,
}
#[derive(thiserror::Error, Debug)]
pub enum ResourceKind {
#[error("user not found (id: {id:?}, email: {email:?})")]
User {
id: Option<String>,
email: Option<String>,
},
#[error("role not found (id: {id:?})")]
Role { id: Option<String> },
#[error("{token_type:?} not found")]
Token { token_type: TokenErrorType },
}
#[derive(thiserror::Error, Debug)]
pub enum ConflictReason {
#[error("{field:?} already exists")]
AlreadyExists { field: CredentialField },
}
#[derive(thiserror::Error, Debug)]
pub enum ValidationError {
#[error("malformed {field:?}")]
Malformed { field: CredentialField },
#[error("missing required field: {field:?}")]
Missing { field: CredentialField },
#[error("invalid value for '{0}'")]
Invalid(String),
}
#[derive(thiserror::Error, Debug)]
pub enum InternalError {
#[error("database error: {0}")]
Database(String),
#[error("hashing failure")]
Hashing,
#[error("failed to create {token_type:?}")]
TokenCreation { token_type: TokenErrorType },
#[error("failed to deliver email to {to}")]
EmailDelivery { to: String },
#[error("TLS configuration error at {path}")]
Tls { path: String },
#[error("I/O error: {0}")]
Io(String),
#[error("serialization error: {0}")]
Serialization(String),
#[error("cache error: {0}")]
Cache(String),
#[error("session error: {0}")]
Session(String),
#[error("JWT error: {0}")]
Jwt(String),
#[error("missing app data: {0}")]
MissingConfiguration(String),
#[error("missing configuration field: {field} - {reason}")]
MissingField { field: String, reason: String },
#[error("invalid config")]
InvalidConfig(Vec<CoreError>), }
impl From<validator::ValidationErrors> for ValidationError {
fn from(e: validator::ValidationErrors) -> Self {
ValidationError::Invalid(e.to_string())
}
}
impl From<validator::ValidationError> for ValidationError {
fn from(e: validator::ValidationError) -> Self {
ValidationError::Invalid(e.to_string())
}
}
impl From<std::io::Error> for InternalError {
fn from(e: std::io::Error) -> Self {
InternalError::Io(e.to_string())
}
}
impl From<std::env::VarError> for InternalError {
fn from(e: std::env::VarError) -> Self {
InternalError::Io(e.to_string())
}
}
impl From<sqlx::Error> for InternalError {
fn from(e: sqlx::Error) -> Self {
InternalError::Database(e.to_string())
}
}
impl From<sqlx::migrate::MigrateError> for InternalError {
fn from(e: sqlx::migrate::MigrateError) -> Self {
InternalError::Database(e.to_string())
}
}
impl From<mongodb::error::Error> for InternalError {
fn from(e: mongodb::error::Error) -> Self {
InternalError::Database(e.to_string())
}
}
impl From<mongodb::bson::ser::Error> for InternalError {
fn from(e: mongodb::bson::ser::Error) -> Self {
InternalError::Database(e.to_string())
}
}
impl From<redis::RedisError> for InternalError {
fn from(e: redis::RedisError) -> Self {
InternalError::Cache(e.to_string())
}
}
impl From<memcache::MemcacheError> for InternalError {
fn from(e: memcache::MemcacheError) -> Self {
InternalError::Cache(e.to_string())
}
}
impl From<serde_json::Error> for InternalError {
fn from(e: serde_json::Error) -> Self {
InternalError::Serialization(e.to_string())
}
}
impl From<serde_yaml::Error> for InternalError {
fn from(e: serde_yaml::Error) -> Self {
InternalError::Serialization(e.to_string())
}
}
impl From<config::ConfigError> for InternalError {
fn from(e: config::ConfigError) -> Self {
InternalError::Serialization(e.to_string())
}
}
impl From<jsonwebtoken::errors::Error> for InternalError {
fn from(e: jsonwebtoken::errors::Error) -> Self {
InternalError::Jwt(e.to_string())
}
}
impl From<std::io::Error> for CoreError {
fn from(e: std::io::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<std::env::VarError> for CoreError {
fn from(e: std::env::VarError) -> Self {
CoreError::Internal(e.into())
}
}
impl From<sqlx::Error> for CoreError {
fn from(e: sqlx::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<sqlx::migrate::MigrateError> for CoreError {
fn from(e: sqlx::migrate::MigrateError) -> Self {
CoreError::Internal(e.into())
}
}
impl From<mongodb::error::Error> for CoreError {
fn from(e: mongodb::error::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<mongodb::bson::ser::Error> for CoreError {
fn from(e: mongodb::bson::ser::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<redis::RedisError> for CoreError {
fn from(e: redis::RedisError) -> Self {
CoreError::Internal(e.into())
}
}
impl From<memcache::MemcacheError> for CoreError {
fn from(e: memcache::MemcacheError) -> Self {
CoreError::Internal(e.into())
}
}
impl From<serde_json::Error> for CoreError {
fn from(e: serde_json::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<serde_yaml::Error> for CoreError {
fn from(e: serde_yaml::Error) -> Self {
CoreError::Internal(e.into())
}
}
impl From<config::ConfigError> for CoreError {
fn from(e: config::ConfigError) -> Self {
CoreError::Internal(e.into())
}
}
impl From<jsonwebtoken::errors::Error> for CoreError {
fn from(_: jsonwebtoken::errors::Error) -> Self {
CoreError::Unauthenticated(AuthError::TokenInvalid {
token_type: TokenErrorType::AccessToken,
})
}
}
impl From<validator::ValidationErrors> for CoreError {
fn from(e: validator::ValidationErrors) -> Self {
CoreError::Validation(e.into())
}
}
impl From<validator::ValidationError> for CoreError {
fn from(e: validator::ValidationError) -> Self {
CoreError::Validation(e.into())
}
}
impl From<CoreError> for std::io::Error {
fn from(err: CoreError) -> Self {
std::io::Error::other(err.to_string())
}
}