mod charge;
pub use charge::ChargeMethod;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
Expired,
InvalidAmount,
InvalidRecipient,
TransactionFailed,
NotFound,
InvalidCredential,
NetworkError,
ChainIdMismatch,
CredentialMismatch,
}
impl ErrorCode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Expired => "expired",
Self::InvalidAmount => "invalid_amount",
Self::InvalidRecipient => "invalid_recipient",
Self::TransactionFailed => "transaction_failed",
Self::NotFound => "not_found",
Self::InvalidCredential => "invalid_credential",
Self::NetworkError => "network_error",
Self::ChainIdMismatch => "chain_id_mismatch",
Self::CredentialMismatch => "credential_mismatch",
}
}
pub fn spec_code(&self) -> &'static str {
match self {
Self::Expired => "payment_expired",
Self::InvalidAmount => "payment_insufficient",
Self::InvalidRecipient => "payment_verification_failed",
Self::TransactionFailed => "payment_verification_failed",
Self::NotFound => "payment_verification_failed",
Self::InvalidCredential => "malformed_proof",
Self::NetworkError => "payment_verification_failed",
Self::ChainIdMismatch => "payment_method_unsupported",
Self::CredentialMismatch => "malformed_proof",
}
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct VerificationError {
pub message: String,
pub code: Option<ErrorCode>,
pub retryable: bool,
}
impl VerificationError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
code: None,
retryable: false,
}
}
pub fn with_code(message: impl Into<String>, code: ErrorCode) -> Self {
Self {
message: message.into(),
code: Some(code),
retryable: false,
}
}
pub fn retryable(mut self) -> Self {
self.retryable = true;
self
}
pub fn expired(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::Expired)
}
pub fn invalid_amount(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::InvalidAmount)
}
pub fn invalid_recipient(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::InvalidRecipient)
}
pub fn transaction_failed(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::TransactionFailed)
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::NotFound)
}
pub fn chain_id_mismatch(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::ChainIdMismatch)
}
pub fn credential_mismatch(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::CredentialMismatch)
}
pub fn network_error(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::NetworkError).retryable()
}
pub fn pending(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::NotFound).retryable()
}
}
impl fmt::Display for VerificationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref code) = self.code {
write!(f, "[{}] {}", code, self.message)
} else {
write!(f, "{}", self.message)
}
}
}
impl std::error::Error for VerificationError {}
impl From<String> for VerificationError {
fn from(message: String) -> Self {
Self::new(message)
}
}
impl From<&str> for VerificationError {
fn from(message: &str) -> Self {
Self::new(message)
}
}
use crate::error::{MppError, PaymentError, PaymentErrorDetails};
impl From<VerificationError> for MppError {
fn from(err: VerificationError) -> Self {
match err.code {
Some(ErrorCode::Expired) => MppError::PaymentExpired(None),
Some(ErrorCode::InvalidCredential) => MppError::MalformedCredential(Some(err.message)),
Some(ErrorCode::CredentialMismatch)
| Some(ErrorCode::InvalidAmount)
| Some(ErrorCode::InvalidRecipient)
| Some(ErrorCode::TransactionFailed)
| Some(ErrorCode::ChainIdMismatch)
| Some(ErrorCode::NotFound)
| Some(ErrorCode::NetworkError)
| None => MppError::VerificationFailed(Some(err.message)),
}
}
}
impl PaymentError for VerificationError {
fn to_problem_details(&self, challenge_id: Option<&str>) -> PaymentErrorDetails {
MppError::from(self.clone()).to_problem_details(challenge_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verification_error_display() {
let err = VerificationError::new("Payment failed");
assert_eq!(err.to_string(), "Payment failed");
let err_with_code = VerificationError::with_code("Request expired", ErrorCode::Expired);
assert_eq!(err_with_code.to_string(), "[expired] Request expired");
}
#[test]
fn test_verification_error_constructors() {
let err = VerificationError::expired("Challenge expired");
assert_eq!(err.code, Some(ErrorCode::Expired));
assert!(!err.retryable);
let err = VerificationError::invalid_amount("Amount mismatch").retryable();
assert_eq!(err.code, Some(ErrorCode::InvalidAmount));
assert!(err.retryable);
}
#[test]
fn test_error_code_spec_codes() {
assert_eq!(ErrorCode::Expired.spec_code(), "payment_expired");
assert_eq!(ErrorCode::InvalidAmount.spec_code(), "payment_insufficient");
assert_eq!(ErrorCode::InvalidCredential.spec_code(), "malformed_proof");
assert_eq!(
ErrorCode::ChainIdMismatch.spec_code(),
"payment_method_unsupported"
);
assert_eq!(
ErrorCode::TransactionFailed.spec_code(),
"payment_verification_failed"
);
}
}