4d7cf67
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use export::data::{AppResponseData, NoData};
use thiserror::Error;
pub type PaymentResult<T> = Result<T, PaymentError>;
#[derive(Debug, Error)]
pub enum PaymentError {
#[error("bad request: {0}")]
BadRequest(String),
#[error("not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("gateway error: {0}")]
Gateway(String),
#[error("webhook verification failed: {0}")]
WebhookVerification(String),
#[error("not implemented: {0}")]
NotImplemented(&'static str),
#[error("assertion failed: {0}")]
Assertion(String),
#[error("database error: {0}")]
Sqlx(#[from] db::conn::SqlxError),
#[error("uuid: {0}")]
Uuid(#[from] uuid::Error),
#[error("internal: {0}")]
Internal(String),
}
impl PaymentError {
fn status(&self) -> StatusCode {
match self {
PaymentError::BadRequest(_) => StatusCode::BAD_REQUEST,
PaymentError::NotFound(_) => StatusCode::NOT_FOUND,
PaymentError::Conflict(_) => StatusCode::CONFLICT,
PaymentError::Gateway(_) | PaymentError::WebhookVerification(_) => {
StatusCode::BAD_GATEWAY
}
PaymentError::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED,
PaymentError::Assertion(_)
| PaymentError::Internal(_)
| PaymentError::Sqlx(_)
| PaymentError::Uuid(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn is_user_facing(&self) -> bool {
matches!(
self,
PaymentError::BadRequest(_)
| PaymentError::NotFound(_)
| PaymentError::Conflict(_)
| PaymentError::NotImplemented(_)
)
}
pub fn error_kind(&self) -> &'static str {
match self {
PaymentError::BadRequest(_) => "bad_request",
PaymentError::NotFound(_) => "not_found",
PaymentError::Conflict(_) => "conflict",
PaymentError::Gateway(_) => "gateway",
PaymentError::WebhookVerification(_) => "webhook_verification",
PaymentError::NotImplemented(_) => "not_implemented",
PaymentError::Assertion(_) => "assertion",
PaymentError::Internal(_) => "internal_other",
PaymentError::Sqlx(_) => "internal_db",
PaymentError::Uuid(_) => "internal_uuid",
}
}
}
impl IntoResponse for PaymentError {
fn into_response(self) -> Response {
let status = self.status();
let msg = if self.is_user_facing() {
self.to_string()
} else {
tracing::error!(error = %self, "payment internal error");
"internal error".to_string()
};
AppResponseData::<NoData>::err_plain(status.as_u16() as i32, msg).into_response()
}
}