Files
huskies/server/src/service/ws/error.rs
T

99 lines
3.3 KiB
Rust

//! 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<Error> = 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();
}
}
}