use axum::extract::{FromRequest, Request};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use mail4agent_api::MailError;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::identity::SessionError;
pub enum ApiError {
Mail(MailError),
Session(SessionError),
}
impl From<MailError> for ApiError {
fn from(err: MailError) -> Self {
Self::Mail(err)
}
}
impl From<SessionError> for ApiError {
fn from(err: SessionError) -> Self {
Self::Session(err)
}
}
#[derive(Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum SessionErrorBody {
MissingConnectInfo { reason: String },
AttestationFailed { reason: String },
SessionRegistrationRefused { reason: String },
}
impl From<&SessionError> for SessionErrorBody {
fn from(err: &SessionError) -> Self {
match err {
SessionError::MissingConnectInfo => Self::MissingConnectInfo { reason: err.to_string() },
SessionError::Attest(_) => Self::AttestationFailed { reason: err.to_string() },
SessionError::Mailbox(_) => Self::SessionRegistrationRefused { reason: err.to_string() },
}
}
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
match self {
Self::Mail(err) => {
let status = match &err {
MailError::PermissionDenied { .. } => StatusCode::FORBIDDEN,
MailError::NotAddressedToYou { .. } => StatusCode::FORBIDDEN,
MailError::UnknownParticipant { .. }
| MailError::UnknownRoom { .. }
| MailError::UnknownSession { .. }
| MailError::UnknownMessage { .. } => StatusCode::NOT_FOUND,
MailError::SessionAccountMismatch { .. } => StatusCode::CONFLICT,
MailError::Malformed { .. } | MailError::TooLarge { .. } => StatusCode::BAD_REQUEST,
MailError::StoreUnavailable { .. } => StatusCode::SERVICE_UNAVAILABLE,
};
(status, Json(err)).into_response()
}
Self::Session(err) => {
let status = match &err {
SessionError::MissingConnectInfo => StatusCode::INTERNAL_SERVER_ERROR,
SessionError::Attest(_) => StatusCode::UNAUTHORIZED,
SessionError::Mailbox(_) => StatusCode::BAD_REQUEST,
};
tracing::warn!(error = %err, "session resolution refused a request");
let body = SessionErrorBody::from(&err);
(status, Json(body)).into_response()
}
}
}
}
pub struct ApiJson<T>(pub T);
impl<T, S> FromRequest<S> for ApiJson<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
type Rejection = ApiError;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
match Json::<T>::from_request(req, state).await {
Ok(Json(value)) => Ok(Self(value)),
Err(rejection) => Err(ApiError::Mail(MailError::Malformed {
field: "body".to_string(),
reason: rejection.body_text(),
})),
}
}
}