use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use export::data::{AppResponseData, NoData}; use thiserror::Error; pub type PaymentResult = Result; #[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::::err_plain(status.as_u16() as i32, msg).into_response() } }