use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum ErrorCode {
InvalidHash = 1001,
InvalidKey = 1002,
InvalidSignature = 1003,
InvalidEvent = 1004,
InvalidBlock = 1005,
InvalidProof = 1006,
InvalidTimestamp = 1007,
InvalidFormat = 1008,
InvalidInput = 1009,
EventNotFound = 2001,
BlockNotFound = 2002,
NodeNotFound = 2003,
PeakNotFound = 2004,
DuplicateEvent = 3001,
DuplicateBlock = 3002,
ChainFork = 3003,
Unauthorized = 4001,
Forbidden = 4002,
StorageRead = 5001,
StorageWrite = 5002,
StorageCorruption = 5003,
StorageInit = 5004,
Serialization = 6001,
Deserialization = 6002,
Internal = 6003,
ConnectionFailed = 7001,
Timeout = 7002,
ProtocolError = 7003,
}
impl ErrorCode {
pub fn code(self) -> u16 {
self as u16
}
pub fn is_client_error(self) -> bool {
(1000..5000).contains(&self.code())
}
pub fn is_server_error(self) -> bool {
self.code() >= 5000
}
pub fn is_retryable(self) -> bool {
matches!(
self,
ErrorCode::StorageRead
| ErrorCode::StorageWrite
| ErrorCode::ConnectionFailed
| ErrorCode::Timeout
)
}
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "E{:04}", self.code())
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("[{code}] invalid input: {message}")]
InvalidInput { code: ErrorCode, message: String },
#[error("[{code}] invalid hash: {message}")]
InvalidHash {
code: ErrorCode,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("[{code}] invalid key: {message}")]
InvalidKey {
code: ErrorCode,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("[{code}] signature verification failed")]
InvalidSignature { code: ErrorCode },
#[error("[{code}] invalid event: {message}")]
InvalidEvent { code: ErrorCode, message: String },
#[error("[{code}] invalid block: {message}")]
InvalidBlock { code: ErrorCode, message: String },
#[error("[{code}] invalid proof: {message}")]
InvalidProof { code: ErrorCode, message: String },
#[error("[{code}] not found: {message}")]
NotFound { code: ErrorCode, message: String },
#[error("[{code}] duplicate: {message}")]
Duplicate { code: ErrorCode, message: String },
#[error("[{code}] storage error: {message}")]
Storage {
code: ErrorCode,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("[{code}] serialization error: {message}")]
Serialization {
code: ErrorCode,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("[{code}] internal error: {message}")]
Internal { code: ErrorCode, message: String },
}
impl Error {
pub fn code(&self) -> ErrorCode {
match self {
Error::InvalidInput { code, .. } => *code,
Error::InvalidHash { code, .. } => *code,
Error::InvalidKey { code, .. } => *code,
Error::InvalidSignature { code } => *code,
Error::InvalidEvent { code, .. } => *code,
Error::InvalidBlock { code, .. } => *code,
Error::InvalidProof { code, .. } => *code,
Error::NotFound { code, .. } => *code,
Error::Duplicate { code, .. } => *code,
Error::Storage { code, .. } => *code,
Error::Serialization { code, .. } => *code,
Error::Internal { code, .. } => *code,
}
}
pub fn is_client_error(&self) -> bool {
self.code().is_client_error()
}
pub fn is_server_error(&self) -> bool {
self.code().is_server_error()
}
pub fn is_retryable(&self) -> bool {
self.code().is_retryable()
}
}
impl Error {
pub fn invalid_input(message: impl Into<String>) -> Self {
Error::InvalidInput {
code: ErrorCode::InvalidInput,
message: message.into(),
}
}
pub fn invalid_hash(message: impl Into<String>) -> Self {
Error::InvalidHash {
code: ErrorCode::InvalidHash,
message: message.into(),
source: None,
}
}
pub fn invalid_key(message: impl Into<String>) -> Self {
Error::InvalidKey {
code: ErrorCode::InvalidKey,
message: message.into(),
source: None,
}
}
pub fn invalid_signature() -> Self {
Error::InvalidSignature {
code: ErrorCode::InvalidSignature,
}
}
pub fn invalid_event(message: impl Into<String>) -> Self {
Error::InvalidEvent {
code: ErrorCode::InvalidEvent,
message: message.into(),
}
}
pub fn invalid_block(message: impl Into<String>) -> Self {
Error::InvalidBlock {
code: ErrorCode::InvalidBlock,
message: message.into(),
}
}
pub fn invalid_proof(message: impl Into<String>) -> Self {
Error::InvalidProof {
code: ErrorCode::InvalidProof,
message: message.into(),
}
}
pub fn event_not_found(message: impl Into<String>) -> Self {
Error::NotFound {
code: ErrorCode::EventNotFound,
message: message.into(),
}
}
pub fn block_not_found(message: impl Into<String>) -> Self {
Error::NotFound {
code: ErrorCode::BlockNotFound,
message: message.into(),
}
}
pub fn not_found(message: impl Into<String>) -> Self {
Error::NotFound {
code: ErrorCode::NodeNotFound,
message: message.into(),
}
}
pub fn duplicate(message: impl Into<String>) -> Self {
Error::Duplicate {
code: ErrorCode::DuplicateEvent,
message: message.into(),
}
}
pub fn storage(message: impl Into<String>) -> Self {
Error::Storage {
code: ErrorCode::StorageRead,
message: message.into(),
source: None,
}
}
pub fn internal(message: impl Into<String>) -> Self {
Error::Internal {
code: ErrorCode::Internal,
message: message.into(),
}
}
}
impl From<bincode::Error> for Error {
fn from(e: bincode::Error) -> Self {
Error::Serialization {
code: ErrorCode::Serialization,
message: e.to_string(),
source: Some(Box::new(e)),
}
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Serialization {
code: ErrorCode::Serialization,
message: e.to_string(),
source: Some(Box::new(e)),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Storage {
code: ErrorCode::StorageRead,
message: e.to_string(),
source: Some(Box::new(e)),
}
}
}
impl From<hex::FromHexError> for Error {
fn from(e: hex::FromHexError) -> Self {
Error::InvalidHash {
code: ErrorCode::InvalidHash,
message: e.to_string(),
source: Some(Box::new(e)),
}
}
}
#[allow(unused_macros)]
macro_rules! impl_from_string {
($variant:ident, $code:expr) => {
impl From<&str> for $variant {
fn from(s: &str) -> Self {
$variant(s.to_string())
}
}
};
}
#[doc(hidden)]
pub struct InvalidHashCompat(pub String);
#[doc(hidden)]
pub struct InvalidKeyCompat(pub String);
#[doc(hidden)]
pub struct InvalidEventCompat(pub String);
#[doc(hidden)]
pub struct InvalidBlockCompat(pub String);
#[doc(hidden)]
pub struct InvalidProofCompat(pub String);
#[doc(hidden)]
pub struct StorageCompat(pub String);
#[doc(hidden)]
pub struct NotFoundCompat(pub String);
#[doc(hidden)]
pub struct DuplicateCompat(pub String);
#[doc(hidden)]
pub struct SerializationCompat(pub String);
#[doc(hidden)]
pub struct InternalCompat(pub String);
impl From<InvalidHashCompat> for Error {
fn from(c: InvalidHashCompat) -> Self {
Error::invalid_hash(c.0)
}
}
impl From<InvalidKeyCompat> for Error {
fn from(c: InvalidKeyCompat) -> Self {
Error::invalid_key(c.0)
}
}
impl From<InvalidEventCompat> for Error {
fn from(c: InvalidEventCompat) -> Self {
Error::invalid_event(c.0)
}
}
impl From<InvalidBlockCompat> for Error {
fn from(c: InvalidBlockCompat) -> Self {
Error::invalid_block(c.0)
}
}
impl From<InvalidProofCompat> for Error {
fn from(c: InvalidProofCompat) -> Self {
Error::invalid_proof(c.0)
}
}
impl From<StorageCompat> for Error {
fn from(c: StorageCompat) -> Self {
Error::storage(c.0)
}
}
impl From<NotFoundCompat> for Error {
fn from(c: NotFoundCompat) -> Self {
Error::not_found(c.0)
}
}
impl From<DuplicateCompat> for Error {
fn from(c: DuplicateCompat) -> Self {
Error::duplicate(c.0)
}
}
impl From<SerializationCompat> for Error {
fn from(c: SerializationCompat) -> Self {
Error::Serialization {
code: ErrorCode::Serialization,
message: c.0,
source: None,
}
}
}
impl From<InternalCompat> for Error {
fn from(c: InternalCompat) -> Self {
Error::internal(c.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_codes() {
assert_eq!(ErrorCode::InvalidHash.code(), 1001);
assert_eq!(ErrorCode::EventNotFound.code(), 2001);
assert_eq!(ErrorCode::StorageRead.code(), 5001);
}
#[test]
fn test_error_categorization() {
assert!(ErrorCode::InvalidHash.is_client_error());
assert!(!ErrorCode::InvalidHash.is_server_error());
assert!(ErrorCode::StorageRead.is_server_error());
assert!(!ErrorCode::StorageRead.is_client_error());
}
#[test]
fn test_retryable() {
assert!(ErrorCode::StorageRead.is_retryable());
assert!(ErrorCode::Timeout.is_retryable());
assert!(!ErrorCode::InvalidHash.is_retryable());
}
#[test]
fn test_error_display() {
let e = Error::invalid_hash("bad hex");
assert!(e.to_string().contains("E1001"));
assert!(e.to_string().contains("bad hex"));
}
#[test]
fn test_error_code_display() {
assert_eq!(ErrorCode::InvalidHash.to_string(), "E1001");
assert_eq!(ErrorCode::Internal.to_string(), "E6003");
}
#[test]
fn test_from_bincode() {
let bad_data = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; let bincode_err: bincode::Error = bincode::deserialize::<String>(&bad_data).unwrap_err();
let err: Error = bincode_err.into();
assert_eq!(err.code(), ErrorCode::Serialization);
assert!(err.is_server_error());
}
#[test]
fn test_error_constructors() {
let e = Error::invalid_event("missing field");
assert_eq!(e.code(), ErrorCode::InvalidEvent);
assert!(e.is_client_error());
let e = Error::storage("disk full");
assert_eq!(e.code(), ErrorCode::StorageRead);
assert!(e.is_server_error());
}
}