use std::error::Error; use std::fmt::{self, Display, Formatter}; use actix_web::http::StatusCode; use actix_web::{HttpResponse, ResponseError}; use cache::CacheError; use cache::redis::RedisError; #[derive(Debug)] pub enum SessionError { Serialize { key: String, source: serde_json::Error, }, Deserialize { key: String, source: serde_json::Error, }, Purged, Unauthorized, UpdateConflict { key: String, }, Config(String), Store(CacheError), Backend(RedisError), } impl SessionError { pub fn config(message: impl Into) -> Self { Self::Config(message.into()) } pub fn serialize(key: impl Into, source: serde_json::Error) -> Self { Self::Serialize { key: key.into(), source, } } pub fn deserialize(key: impl Into, source: serde_json::Error) -> Self { Self::Deserialize { key: key.into(), source, } } pub fn conflict(key: impl Into) -> Self { Self::UpdateConflict { key: key.into() } } pub fn is_retryable(&self) -> bool { matches!(self, Self::Store(source) if source.is_retryable()) } } impl Display for SessionError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Self::Serialize { key, source } => { write!(f, "failed to serialize session field `{key}`: {source}") } Self::Deserialize { key, source } => { write!(f, "failed to deserialize session field `{key}`: {source}") } Self::Purged => f.write_str("session record has been purged"), Self::Unauthorized => f.write_str("authentication required"), Self::UpdateConflict { key } => { write!( f, "session field `{key}` changed before its update was committed" ) } Self::Config(message) => write!(f, "invalid session configuration: {message}"), Self::Store(source) => write!(f, "session store error: {source}"), Self::Backend(source) => write!(f, "session backend error: {source}"), } } } impl Error for SessionError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::Serialize { source, .. } | Self::Deserialize { source, .. } => Some(source), Self::Store(source) => Some(source), Self::Backend(source) => Some(source), Self::Purged | Self::Unauthorized | Self::UpdateConflict { .. } | Self::Config(_) => { None } } } } impl ResponseError for SessionError { fn status_code(&self) -> StatusCode { match self { Self::Unauthorized | Self::Purged => StatusCode::UNAUTHORIZED, _ => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> HttpResponse { let status = self.status_code(); let error_code = match self { Self::Unauthorized => "unauthenticated", Self::Purged => "session_revoked", _ => "internal", }; let message = if status.is_server_error() { "internal server error".to_owned() } else { self.to_string() }; HttpResponse::build(status).json(serde_json::json!({ "code": status.as_u16(), "message": message, "data": null, "error": null, "error_code": error_code, "request_id": null, })) } } impl From for SessionError { fn from(value: CacheError) -> Self { Self::Store(value) } } impl From for SessionError { fn from(value: RedisError) -> Self { Self::Backend(value) } }