pub mod recovery;
use std::fmt;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Protocol error: {code} - {message}")]
Protocol {
code: ErrorCode,
message: String,
data: Option<serde_json::Value>,
},
#[error("Transport error: {0}")]
Transport(#[from] TransportError),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Validation error: {0}")]
Validation(String),
#[error("Authentication error: {0}")]
Authentication(String),
#[error("Request timed out after {0}ms")]
Timeout(u64),
#[error("Capability not supported: {0}")]
UnsupportedCapability(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Invalid state: {0}")]
InvalidState(String),
#[error("Operation cancelled")]
Cancelled,
#[error("Rate limit exceeded")]
RateLimited,
#[error("Circuit breaker is open")]
CircuitBreakerOpen,
#[error(transparent)]
Other(#[from] anyhow::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ErrorCode(pub i32);
impl ErrorCode {
pub const PARSE_ERROR: Self = Self(-32700);
pub const INVALID_REQUEST: Self = Self(-32600);
pub const METHOD_NOT_FOUND: Self = Self(-32601);
pub const INVALID_PARAMS: Self = Self(-32602);
pub const INTERNAL_ERROR: Self = Self(-32603);
pub const REQUEST_TIMEOUT: Self = Self(-32001);
pub const UNSUPPORTED_CAPABILITY: Self = Self(-32002);
pub const AUTHENTICATION_REQUIRED: Self = Self(-32003);
pub const PERMISSION_DENIED: Self = Self(-32004);
pub const RATE_LIMITED: Self = Self(-32005);
pub const CIRCUIT_BREAKER_OPEN: Self = Self(-32006);
pub const fn other(code: i32) -> Self {
Self(code)
}
pub fn as_i32(&self) -> i32 {
self.0
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::hash::Hash for ErrorCode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[derive(Error, Debug)]
pub enum TransportError {
#[error("IO error: {0}")]
Io(String),
#[error("Connection closed")]
ConnectionClosed,
#[error("Invalid message format: {0}")]
InvalidMessage(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Request error: {0}")]
Request(String),
#[error("Send error: {0}")]
Send(String),
#[cfg(feature = "websocket")]
#[error("WebSocket error: {0}")]
WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
#[cfg(feature = "http")]
#[error("HTTP error: {0}")]
Http(String),
}
impl From<std::io::Error> for TransportError {
fn from(err: std::io::Error) -> Self {
Self::Io(err.to_string())
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Transport(TransportError::Io(err.to_string()))
}
}
impl Error {
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal(message.into())
}
pub fn protocol(code: ErrorCode, message: impl Into<String>) -> Self {
Self::Protocol {
code,
message: message.into(),
data: None,
}
}
pub fn error_code(&self) -> Option<ErrorCode> {
match self {
Self::Protocol { code, .. } => Some(*code),
Self::Timeout(_) => Some(ErrorCode::REQUEST_TIMEOUT),
Self::Authentication(_) => Some(ErrorCode::AUTHENTICATION_REQUIRED),
Self::RateLimited => Some(ErrorCode::RATE_LIMITED),
Self::CircuitBreakerOpen => Some(ErrorCode::CIRCUIT_BREAKER_OPEN),
_ => None,
}
}
pub fn validation(message: impl Into<String>) -> Self {
Self::Validation(message.into())
}
pub fn parse(message: impl Into<String>) -> Self {
Self::Protocol {
code: ErrorCode::PARSE_ERROR,
message: message.into(),
data: None,
}
}
pub fn authentication(message: impl Into<String>) -> Self {
Self::Authentication(message.into())
}
pub fn timeout(duration_ms: u64) -> Self {
Self::Timeout(duration_ms)
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::NotFound(message.into())
}
pub fn unsupported_capability(capability: impl Into<String>) -> Self {
Self::UnsupportedCapability(capability.into())
}
pub fn from_jsonrpc_error(error: crate::types::jsonrpc::JSONRPCError) -> Self {
Self::Protocol {
code: ErrorCode(error.code),
message: error.message,
data: error.data,
}
}
pub fn protocol_msg(message: impl Into<String>) -> Self {
Self::Protocol {
code: ErrorCode::INTERNAL_ERROR,
message: message.into(),
data: None,
}
}
pub fn is_error_code(&self, code: ErrorCode) -> bool {
matches!(self.error_code(), Some(c) if c == code)
}
pub fn capability(message: impl Into<String>) -> Self {
Self::UnsupportedCapability(message.into())
}
pub fn invalid_state(message: impl Into<String>) -> Self {
Self::InvalidState(message.into())
}
pub fn cancelled() -> Self {
Self::Cancelled
}
pub fn invalid_params(message: impl Into<String>) -> Self {
Self::Protocol {
code: ErrorCode::INVALID_PARAMS,
message: message.into(),
data: None,
}
}
pub fn method_not_found(method: impl Into<String>) -> Self {
Self::Protocol {
code: ErrorCode::METHOD_NOT_FOUND,
message: format!("Method not found: {}", method.into()),
data: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = Error::internal("test error");
assert!(matches!(err, Error::Internal(_)));
let err = Error::protocol(ErrorCode::INVALID_REQUEST, "bad request");
assert!(matches!(err, Error::Protocol { .. }));
}
#[test]
fn test_error_codes() {
assert_eq!(ErrorCode::PARSE_ERROR.as_i32(), -32700);
assert_eq!(ErrorCode::RATE_LIMITED.as_i32(), -32005);
assert_eq!(ErrorCode::CIRCUIT_BREAKER_OPEN.as_i32(), -32006);
}
}