//! Unified error type for imks. //! //! Consolidates all submodule-specific error enums into a single `ImksError`. //! Public APIs return `ImksResult`, private code may still use local enums //! for internal dispatch and convert via `From` / `.map_err()` when crossing //! module boundaries. use std::string::FromUtf8Error; use thiserror::Error; /// Unified error enum for the entire imks crate. #[derive(Debug, Error)] pub enum ImksError { // Protocol layer (engine) #[error("invalid engine packet type: {0}")] InvalidEnginePacketType(u8), #[error("invalid engine packet type char: {0}")] InvalidEnginePacketTypeChar(char), #[error("empty engine packet")] EmptyEnginePacket, #[error("invalid base64: {0}")] InvalidBase64(#[from] base64::DecodeError), #[error("invalid utf8 in packet: {0}")] InvalidPacketUtf8(#[from] FromUtf8Error), #[error("engine serialization error: {0}")] EngineSerialization(String), // Transport upgrade #[error("session not found for upgrade")] UpgradeSessionNotFound, #[error("session already closed, cannot upgrade")] UpgradeSessionClosed, #[error("invalid session state for upgrade")] UpgradeInvalidState, // Socket.IO layer #[error("invalid socket packet type: {0}")] InvalidSocketPacketType(u8), #[error("invalid socket packet type char: {0}")] InvalidSocketPacketTypeChar(char), #[error("empty socket packet")] EmptySocketPacket, #[error("invalid socket packet format: {0}")] InvalidSocketPacketFormat(String), #[error("missing namespace in socket packet")] MissingNamespace, #[error("invalid attachment count in binary event")] InvalidAttachmentCount, // Socket namespace #[error("namespace error: {0}")] Namespace(String), #[error("socket not found: {0}")] SocketNotFound(String), #[error("failed to send packet to socket: channel full")] SocketSendFull, // Adapter layer #[error("adapter redis error: {0}")] AdapterRedis(String), #[error("adapter nats error: {0}")] AdapterNats(String), #[error("adapter message bus error: {0}")] AdapterMessageBus(String), #[error("adapter serialization error: {0}")] AdapterSerialization(String), #[error("adapter room error: {0}")] AdapterRoom(String), // Message bus #[error("message bus connection closed")] MessageBusConnectionClosed, #[error("message bus channel not found: {0}")] MessageBusChannelNotFound(String), // Session store #[error("session not found: {0}")] SessionNotFound(String), #[error("session expired: {0}")] SessionExpired(String), #[error("session store redis error: {0}")] SessionRedis(String), #[error("session serialization error: {0}")] SessionSerialization(String), // Database #[error("database error: {0}")] Database(#[from] sqlx::Error), // gRPC #[error("gRPC error: {0}")] GrpcStatus(#[from] tonic::Status), #[error("gRPC transport error: {0}")] GrpcTransport(#[from] tonic::transport::Error), // Serialization #[error("JSON error: {0}")] Json(#[from] serde_json::Error), // Transport #[error("webtransport error: {0}")] WebTransport(String), #[error("IO error: {0}")] Io(#[from] std::io::Error), // Auth #[error("auth error: {0}")] Auth(String), #[error("token expired")] TokenExpired, // General #[error("not found: {0}")] NotFound(String), #[error("invalid input: {0}")] InvalidInput(String), #[error("internal error: {0}")] Internal(String), } /// Convenience alias used across all public APIs. pub type ImksResult = Result; // Conversions from submodule error types (for gradual migration). impl From for ImksError { fn from(e: crate::engine::packet::PacketError) -> Self { use crate::engine::packet::PacketError::*; match e { InvalidType(v) => ImksError::InvalidEnginePacketType(v), InvalidTypeChar(c) => ImksError::InvalidEnginePacketTypeChar(c), Empty => ImksError::EmptyEnginePacket, InvalidBase64(e) => ImksError::InvalidBase64(e), InvalidUtf8(e) => ImksError::InvalidPacketUtf8(e), Serialization(s) => ImksError::EngineSerialization(s), } } } impl From for ImksError { fn from(e: crate::engine::upgrade::UpgradeError) -> Self { use crate::engine::upgrade::UpgradeError::*; match e { SessionNotFound => ImksError::UpgradeSessionNotFound, SessionClosed => ImksError::UpgradeSessionClosed, InvalidState => ImksError::UpgradeInvalidState, } } } impl From for ImksError { fn from(e: crate::socket::packet::PacketError) -> Self { use crate::socket::packet::PacketError::*; match e { InvalidType(v) => ImksError::InvalidSocketPacketType(v), InvalidTypeChar(c) => ImksError::InvalidSocketPacketTypeChar(c), Empty => ImksError::EmptySocketPacket, InvalidFormat(s) => ImksError::InvalidSocketPacketFormat(s), Json(e) => ImksError::Json(e), MissingNamespace => ImksError::MissingNamespace, InvalidAttachmentCount => ImksError::InvalidAttachmentCount, } } } impl From for ImksError { fn from(e: crate::socket::adapter::AdapterError) -> Self { use crate::socket::adapter::AdapterError::*; match e { Redis(s) => ImksError::AdapterRedis(s), Nats(s) => ImksError::AdapterNats(s), MessageBus(s) => ImksError::AdapterMessageBus(s), Serialization(s) => ImksError::AdapterSerialization(s), Room(s) => ImksError::AdapterRoom(s), } } } impl From for ImksError { fn from(e: crate::socket::message_bus::MessageBusError) -> Self { use crate::socket::message_bus::MessageBusError::*; match e { Redis(s) => ImksError::AdapterRedis(s), Nats(s) => ImksError::AdapterNats(s), ConnectionClosed => ImksError::MessageBusConnectionClosed, ChannelNotFound(s) => ImksError::MessageBusChannelNotFound(s), Serialization(s) => ImksError::AdapterSerialization(s), } } } impl From for ImksError { fn from(e: crate::socket::session_store::SessionError) -> Self { use crate::socket::session_store::SessionError::*; match e { Redis(s) => ImksError::SessionRedis(s), NotFound(s) => ImksError::SessionNotFound(s), Serialization(s) => ImksError::SessionSerialization(s), Expired(s) => ImksError::SessionExpired(s), } } } impl From> for ImksError { fn from(_: tokio::sync::mpsc::error::TrySendError) -> Self { ImksError::SocketSendFull } } #[cfg(test)] mod tests { use super::*; #[test] fn test_imks_error_display() { let err = ImksError::NotFound("message 01909abc".into()); assert_eq!(err.to_string(), "not found: message 01909abc"); } #[test] fn test_imks_error_from_base64() { let b64_err = base64::DecodeError::InvalidByte(0, b'!'); let err: ImksError = b64_err.into(); assert!(matches!(err, ImksError::InvalidBase64(_))); } #[test] fn test_imks_error_from_sqlx() { // sqlx::Error doesn't impl PartialEq, so just check the variant let db_err = sqlx::Error::PoolClosed; let err: ImksError = db_err.into(); assert!(matches!(err, ImksError::Database(_))); } #[test] fn test_imks_error_from_serde_json() { let json_err = serde_json::from_str::("not json").unwrap_err(); let err: ImksError = json_err.into(); assert!(matches!(err, ImksError::Json(_))); } #[test] #[allow(clippy::unnecessary_literal_unwrap)] fn test_imks_result_ok() { let result: ImksResult = Ok(42); assert_eq!(result.unwrap(), 42); } #[test] fn test_imks_result_err() { let result: ImksResult = Err(ImksError::TokenExpired); assert!(result.is_err()); } }