//! Typed error enum for the WebSocket service layer. //! //! Every distinct failure mode the WS layer can produce is represented here. //! The HTTP/WS adapter maps these to close codes or error frames. /// Errors produced by the WebSocket service layer. /// /// Each variant maps to a distinct failure mode; the transport adapter /// translates these into WS close codes or error-frame messages. #[derive(Debug)] #[allow(dead_code)] pub enum Error { /// Client sent a message that could not be parsed as a valid `WsRequest`. /// Maps to an `error` frame with the parse failure detail. InvalidMessage(String), /// The chat subsystem returned an error (e.g. LLM provider failure). /// Maps to an `error` frame with the provider message. Chat(String), /// A permission response referenced an unknown `request_id`. /// Silently ignored in practice (no frame sent), but tracked for observability. UnknownPermissionRequest(String), /// Failed to load initial state (pipeline, onboarding, wizard). /// Maps to an `error` frame. Init(String), /// The send channel to the client is closed (client disconnected). /// Triggers connection teardown — no frame is sent. ClientGone, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidMessage(msg) => write!(f, "Invalid request: {msg}"), Self::Chat(msg) => write!(f, "Chat error: {msg}"), Self::UnknownPermissionRequest(id) => { write!(f, "Unknown permission request: {id}") } Self::Init(msg) => write!(f, "Initialisation error: {msg}"), Self::ClientGone => write!(f, "Client disconnected"), } } } #[cfg(test)] mod tests { use super::*; #[test] fn display_invalid_message() { let err = Error::InvalidMessage("bad json".to_string()); assert_eq!(err.to_string(), "Invalid request: bad json"); } #[test] fn display_chat_error() { let err = Error::Chat("timeout".to_string()); assert_eq!(err.to_string(), "Chat error: timeout"); } #[test] fn display_unknown_permission_request() { let err = Error::UnknownPermissionRequest("req-99".to_string()); assert_eq!(err.to_string(), "Unknown permission request: req-99"); } #[test] fn display_init_error() { let err = Error::Init("CRDT not ready".to_string()); assert_eq!(err.to_string(), "Initialisation error: CRDT not ready"); } #[test] fn display_client_gone() { let err = Error::ClientGone; assert_eq!(err.to_string(), "Client disconnected"); } #[test] fn error_is_debug() { let err = Error::InvalidMessage("test".to_string()); let debug = format!("{err:?}"); assert!(debug.contains("InvalidMessage")); } #[test] fn all_variants_display_without_panic() { let variants: Vec = vec![ Error::InvalidMessage("a".to_string()), Error::Chat("b".to_string()), Error::UnknownPermissionRequest("c".to_string()), Error::Init("d".to_string()), Error::ClientGone, ]; for v in &variants { let _ = v.to_string(); } } }