use crate::core::SolanaRecoverError;
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[derive(Error, Debug, Clone, Serialize, Deserialize)]
pub enum NftError {
#[error("Configuration error: {message}")]
Configuration { message: String },
#[error("Network error: {message}")]
Network { message: String, source_info: Option<String> },
#[error("Metadata error: {message}")]
Metadata {
message: String,
mint_address: Option<String>,
metadata_uri: Option<String>,
},
#[error("Validation error: {message}")]
Validation {
message: String,
field: Option<String>,
value: Option<String>,
},
#[error("Security error: {message}")]
Security {
message: String,
risk_level: RiskLevel,
details: Option<String>,
},
#[error("Valuation error: {message}")]
Valuation {
message: String,
mint_address: Option<String>,
method: Option<String>,
},
#[error("Cache error: {message}")]
Cache { message: String, operation: Option<String> },
#[error("Batch error: {message}")]
Batch {
message: String,
batch_id: Option<String>,
failed_items: Option<u32>,
total_items: Option<u32>,
},
#[error("Portfolio error: {message}")]
Portfolio { message: String, wallet_address: Option<String> },
#[error("Strategy error: {message}")]
Strategy {
message: String,
strategy_name: Option<String>,
context: Option<String>,
},
#[error("Feature '{feature}' is disabled. Enable with cargo feature flag.")]
FeatureDisabled { feature: String },
#[error("Operation timed out after {timeout_ms}ms: {message}")]
Timeout {
message: String,
timeout_ms: u64,
operation: Option<String>,
},
#[error("Rate limit exceeded: {message}")]
RateLimit {
message: String,
retry_after_ms: Option<u64>,
limit_type: Option<String>,
},
#[error("Resource exhausted: {message}")]
ResourceExhausted {
message: String,
resource_type: String,
current_usage: Option<u64>,
limit: Option<u64>,
},
#[error("Serialization error: {message}")]
Serialization {
message: String,
format: Option<String>,
data_type: Option<String>,
},
#[error("Authentication error: {message}")]
Authentication {
message: String,
service: Option<String>,
required_permissions: Option<Vec<String>>,
},
#[error("NFT operation failed: {message}")]
Generic {
message: String,
context: Option<String>,
error_code: Option<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub enum RiskLevel {
None,
Low,
Medium,
High,
Critical,
}
impl fmt::Display for RiskLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RiskLevel::None => write!(f, "None"),
RiskLevel::Low => write!(f, "Low"),
RiskLevel::Medium => write!(f, "Medium"),
RiskLevel::High => write!(f, "High"),
RiskLevel::Critical => write!(f, "Critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecoveryStrategy {
RetryWithBackoff { base_delay_ms: u64, max_retries: u32 },
UseAlternative,
SkipAndContinue,
Abort,
Fallback { method: String },
}
impl NftError {
pub fn risk_level(&self) -> RiskLevel {
match self {
NftError::Security { risk_level, .. } => *risk_level,
NftError::Validation { .. } => RiskLevel::Medium,
NftError::Authentication { .. } => RiskLevel::High,
NftError::Network { .. } => RiskLevel::Low,
_ => RiskLevel::None,
}
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
NftError::Network { .. } |
NftError::Timeout { .. } |
NftError::RateLimit { .. } |
NftError::ResourceExhausted { .. }
)
}
pub fn recovery_strategy(&self) -> RecoveryStrategy {
match self {
NftError::Network { .. } | NftError::Timeout { .. } => {
RecoveryStrategy::RetryWithBackoff { base_delay_ms: 1000, max_retries: 3 }
},
NftError::RateLimit { retry_after_ms, .. } => {
RecoveryStrategy::RetryWithBackoff {
base_delay_ms: retry_after_ms.unwrap_or(5000),
max_retries: 2
}
},
NftError::Metadata { .. } | NftError::Valuation { .. } => {
RecoveryStrategy::SkipAndContinue
},
NftError::Security { risk_level, .. } => {
if *risk_level >= RiskLevel::High {
RecoveryStrategy::Abort
} else {
RecoveryStrategy::SkipAndContinue
}
},
NftError::Configuration { .. } | NftError::FeatureDisabled { .. } => {
RecoveryStrategy::Abort
},
_ => RecoveryStrategy::RetryWithBackoff { base_delay_ms: 500, max_retries: 2 },
}
}
pub fn category(&self) -> &'static str {
match self {
NftError::Configuration { .. } => "configuration",
NftError::Network { .. } => "network",
NftError::Metadata { .. } => "metadata",
NftError::Validation { .. } => "validation",
NftError::Security { .. } => "security",
NftError::Valuation { .. } => "valuation",
NftError::Cache { .. } => "cache",
NftError::Batch { .. } => "batch",
NftError::Portfolio { .. } => "portfolio",
NftError::Strategy { .. } => "strategy",
NftError::FeatureDisabled { .. } => "feature_disabled",
NftError::Timeout { .. } => "timeout",
NftError::RateLimit { .. } => "rate_limit",
NftError::ResourceExhausted { .. } => "resource_exhausted",
NftError::Serialization { .. } => "serialization",
NftError::Authentication { .. } => "authentication",
NftError::Generic { .. } => "generic",
}
}
pub fn to_core_error(self) -> SolanaRecoverError {
SolanaRecoverError::NftError(format!("{}", self))
}
}
pub type NftResult<T> = Result<T, NftError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorMetrics {
pub error_count: u64,
pub error_rate: f64,
pub errors_by_category: std::collections::HashMap<String, u64>,
pub retryable_errors: u64,
pub critical_errors: u64,
pub last_error_time: Option<chrono::DateTime<chrono::Utc>>,
}
impl ErrorMetrics {
pub fn new() -> Self {
Self {
error_count: 0,
error_rate: 0.0,
errors_by_category: std::collections::HashMap::new(),
retryable_errors: 0,
critical_errors: 0,
last_error_time: None,
}
}
pub fn record_error(&mut self, error: &NftError) {
self.error_count += 1;
self.last_error_time = Some(chrono::Utc::now());
let category = error.category().to_string();
*self.errors_by_category.entry(category).or_insert(0) += 1;
if error.is_retryable() {
self.retryable_errors += 1;
}
if error.risk_level() >= RiskLevel::High {
self.critical_errors += 1;
}
}
pub fn calculate_rate(&mut self, total_operations: u64) {
self.error_rate = if total_operations > 0 {
self.error_count as f64 / total_operations as f64
} else {
0.0
};
}
}
impl Default for ErrorMetrics {
fn default() -> Self {
Self::new()
}
}