use actix_web::http::StatusCode;
use actix_web::HttpResponse;
#[derive(Debug, thiserror::Error)]
pub enum PaymentError {
#[error("The payment middleware must be executed after the Auth middleware.")]
ServerMisconfigured,
#[error("The X-BSV-Payment header is not valid JSON.")]
MalformedPayment,
#[error("The X-BSV-Payment-Derivation-Prefix header is not valid.")]
InvalidDerivationPrefix,
#[error("{0}")]
PaymentFailed(String),
#[error("An internal error occurred while determining the payment required for this request.")]
PaymentInternal,
}
impl actix_web::error::ResponseError for PaymentError {
fn status_code(&self) -> StatusCode {
match self {
Self::ServerMisconfigured => StatusCode::INTERNAL_SERVER_ERROR,
Self::MalformedPayment => StatusCode::BAD_REQUEST,
Self::InvalidDerivationPrefix => StatusCode::BAD_REQUEST,
Self::PaymentFailed(_) => StatusCode::BAD_REQUEST,
Self::PaymentInternal => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let code = match self {
Self::ServerMisconfigured => "ERR_SERVER_MISCONFIGURED",
Self::MalformedPayment => "ERR_MALFORMED_PAYMENT",
Self::InvalidDerivationPrefix => "ERR_INVALID_DERIVATION_PREFIX",
Self::PaymentFailed(_) => "ERR_PAYMENT_FAILED",
Self::PaymentInternal => "ERR_PAYMENT_INTERNAL",
};
HttpResponse::build(self.status_code()).json(serde_json::json!({
"status": "error",
"code": code,
"description": self.to_string()
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::body::to_bytes;
use actix_web::error::ResponseError;
async fn error_body_json(err: &PaymentError) -> serde_json::Value {
let response = err.error_response();
let body = to_bytes(response.into_body()).await.unwrap();
serde_json::from_slice(&body).unwrap()
}
#[actix_rt::test]
async fn test_server_misconfigured_status() {
let err = PaymentError::ServerMisconfigured;
assert_eq!(err.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[actix_rt::test]
async fn test_server_misconfigured_body() {
let err = PaymentError::ServerMisconfigured;
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_SERVER_MISCONFIGURED");
assert_eq!(
body["description"],
"The payment middleware must be executed after the Auth middleware."
);
}
#[actix_rt::test]
async fn test_malformed_payment_status() {
let err = PaymentError::MalformedPayment;
assert_eq!(err.status_code(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_malformed_payment_body() {
let err = PaymentError::MalformedPayment;
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_MALFORMED_PAYMENT");
assert_eq!(
body["description"],
"The X-BSV-Payment header is not valid JSON."
);
}
#[actix_rt::test]
async fn test_invalid_derivation_prefix_status() {
let err = PaymentError::InvalidDerivationPrefix;
assert_eq!(err.status_code(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_invalid_derivation_prefix_body() {
let err = PaymentError::InvalidDerivationPrefix;
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_INVALID_DERIVATION_PREFIX");
assert_eq!(
body["description"],
"The X-BSV-Payment-Derivation-Prefix header is not valid."
);
}
#[actix_rt::test]
async fn test_payment_failed_status() {
let err = PaymentError::PaymentFailed("Custom msg".into());
assert_eq!(err.status_code(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_payment_failed_body_custom_message() {
let err = PaymentError::PaymentFailed("Custom msg".into());
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_PAYMENT_FAILED");
assert_eq!(body["description"], "Custom msg");
}
#[actix_rt::test]
async fn test_payment_failed_body_empty_message() {
let err = PaymentError::PaymentFailed("".into());
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_PAYMENT_FAILED");
assert_eq!(body["description"], "");
}
#[actix_rt::test]
async fn test_payment_internal_status() {
let err = PaymentError::PaymentInternal;
assert_eq!(err.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[actix_rt::test]
async fn test_payment_internal_body() {
let err = PaymentError::PaymentInternal;
let body = error_body_json(&err).await;
assert_eq!(body["status"], "error");
assert_eq!(body["code"], "ERR_PAYMENT_INTERNAL");
assert_eq!(
body["description"],
"An internal error occurred while determining the payment required for this request."
);
}
#[test]
fn test_no_payment_required_variant() {
let err = PaymentError::ServerMisconfigured;
match err {
PaymentError::ServerMisconfigured => {}
PaymentError::MalformedPayment => {}
PaymentError::InvalidDerivationPrefix => {}
PaymentError::PaymentFailed(_) => {}
PaymentError::PaymentInternal => {}
}
}
#[actix_rt::test]
async fn test_all_errors_have_three_fields() {
let errors: Vec<PaymentError> = vec![
PaymentError::ServerMisconfigured,
PaymentError::MalformedPayment,
PaymentError::InvalidDerivationPrefix,
PaymentError::PaymentFailed("test".into()),
PaymentError::PaymentInternal,
];
for err in &errors {
let body = error_body_json(err).await;
let obj = body.as_object().unwrap();
assert_eq!(obj.len(), 3, "Error {:?} should have exactly 3 fields", err);
assert!(obj.contains_key("status"));
assert!(obj.contains_key("code"));
assert!(obj.contains_key("description"));
}
}
}