mod charge;
mod session;
pub use charge::ChargeMethod;
pub use session::SessionMethod;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
Expired,
InvalidAmount,
InvalidRecipient,
TransactionFailed,
NotFound,
InvalidCredential,
NetworkError,
ChainIdMismatch,
CredentialMismatch,
ChannelNotFound,
ChannelClosed,
InsufficientBalance,
InvalidPayload,
InvalidSignature,
AmountExceedsDeposit,
DeltaTooSmall,
}
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",
Self::ChannelNotFound => "channel-not-found",
Self::ChannelClosed => "channel-closed",
Self::InsufficientBalance => "insufficient-balance",
Self::InvalidPayload => "invalid-payload",
Self::InvalidSignature => "invalid-signature",
Self::AmountExceedsDeposit => "amount-exceeds-deposit",
Self::DeltaTooSmall => "delta-too-small",
}
}
pub fn spec_code(&self) -> &'static str {
match self {
Self::Expired => "payment-expired",
Self::InvalidAmount => "payment-insufficient",
Self::InvalidRecipient => "verification-failed",
Self::TransactionFailed => "verification-failed",
Self::NotFound => "verification-failed",
Self::InvalidCredential => "malformed-credential",
Self::NetworkError => "verification-failed",
Self::ChainIdMismatch => "method-unsupported",
Self::CredentialMismatch => "malformed-credential",
Self::ChannelNotFound => "verification-failed",
Self::ChannelClosed => "verification-failed",
Self::InsufficientBalance => "payment-insufficient",
Self::InvalidPayload => "malformed-credential",
Self::InvalidSignature => "verification-failed",
Self::AmountExceedsDeposit => "verification-failed",
Self::DeltaTooSmall => "verification-failed",
}
}
}
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()
}
pub fn channel_not_found(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::ChannelNotFound)
}
pub fn channel_closed(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::ChannelClosed)
}
pub fn insufficient_balance(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::InsufficientBalance)
}
pub fn invalid_payload(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::InvalidPayload)
}
pub fn invalid_signature(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::InvalidSignature)
}
pub fn amount_exceeds_deposit(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::AmountExceedsDeposit)
}
pub fn delta_too_small(message: impl Into<String>) -> Self {
Self::with_code(message, ErrorCode::DeltaTooSmall)
}
}
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::ChannelNotFound) => MppError::ChannelNotFound(Some(err.message)),
Some(ErrorCode::ChannelClosed) => MppError::ChannelClosed(Some(err.message)),
Some(ErrorCode::InsufficientBalance) => {
MppError::InsufficientBalance(Some(err.message))
}
Some(ErrorCode::InvalidPayload) => MppError::InvalidPayload(Some(err.message)),
Some(ErrorCode::InvalidSignature) => MppError::InvalidSignature(Some(err.message)),
Some(ErrorCode::AmountExceedsDeposit) => {
MppError::AmountExceedsDeposit(Some(err.message))
}
Some(ErrorCode::DeltaTooSmall) => MppError::DeltaTooSmall(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-credential"
);
assert_eq!(ErrorCode::ChainIdMismatch.spec_code(), "method-unsupported");
assert_eq!(
ErrorCode::TransactionFailed.spec_code(),
"verification-failed"
);
}
}