use std::fmt;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LspBridgeError {
#[error("LSP error: {0}")]
Lsp(#[from] LspError),
#[error("Resource limit exceeded: {0}")]
ResourceLimitExceeded(String),
#[error("Rate limit exceeded: {0}")]
RateLimitExceeded(String),
#[error("Circuit breaker open: {0}")]
CircuitBreakerOpen(String),
#[error("Input validation failed: {0}")]
ValidationFailed(String),
#[error("Security violation: {0}")]
SecurityViolation(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Error: {0}")]
Generic(String),
}
pub type Result<T> = std::result::Result<T, LspBridgeError>;
#[derive(Error, Debug)]
pub enum LspError {
#[error("Server startup failed: {message}")]
ServerStartup { message: String },
#[error("Server communication error: {message}")]
Communication { message: String },
#[error("Server crashed: {server_id}")]
ServerCrash { server_id: String },
#[error("Protocol version mismatch: expected {expected}, got {actual}")]
ProtocolMismatch { expected: String, actual: String },
#[error("Invalid server configuration: {message}")]
InvalidConfiguration { message: String },
#[error("Request timed out after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Invalid document URI: {uri}")]
InvalidUri { uri: String },
#[error("Server not found: {server_id}")]
ServerNotFound { server_id: String },
#[error("Feature '{feature}' not supported by server {server_id}")]
FeatureNotSupported { feature: String, server_id: String },
#[error("JSON-RPC error: {message}")]
JsonRpc { message: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Custom error: {message}")]
Custom { message: String },
}
impl LspError {
pub fn server_startup<S: Into<String>>(message: S) -> Self {
Self::ServerStartup {
message: message.into(),
}
}
pub fn communication<S: Into<String>>(message: S) -> Self {
Self::Communication {
message: message.into(),
}
}
pub fn server_crash<S: Into<String>>(server_id: S) -> Self {
Self::ServerCrash {
server_id: server_id.into(),
}
}
pub fn invalid_configuration<S: Into<String>>(message: S) -> Self {
Self::InvalidConfiguration {
message: message.into(),
}
}
pub fn timeout(timeout_ms: u64) -> Self {
Self::Timeout { timeout_ms }
}
pub fn invalid_uri<S: Into<String>>(uri: S) -> Self {
Self::InvalidUri { uri: uri.into() }
}
pub fn server_not_found<S: Into<String>>(server_id: S) -> Self {
Self::ServerNotFound {
server_id: server_id.into(),
}
}
pub fn feature_not_supported<S: Into<String>>(feature: S, server_id: S) -> Self {
Self::FeatureNotSupported {
feature: feature.into(),
server_id: server_id.into(),
}
}
pub fn json_rpc<S: Into<String>>(message: S) -> Self {
Self::JsonRpc {
message: message.into(),
}
}
pub fn protocol<S: Into<String>>(message: S) -> Self {
Self::Communication {
message: format!("Protocol error: {}", message.into()),
}
}
pub fn custom<S: Into<String>>(message: S) -> Self {
Self::Custom {
message: message.into(),
}
}
pub fn is_recoverable(&self) -> bool {
match self {
Self::ServerCrash { .. } => true,
Self::Communication { .. } => true,
Self::Timeout { .. } => true,
Self::ServerStartup { .. } => false,
Self::ProtocolMismatch { .. } => false,
Self::InvalidConfiguration { .. } => false,
Self::InvalidUri { .. } => false,
Self::ServerNotFound { .. } => false,
Self::FeatureNotSupported { .. } => false,
Self::JsonRpc { .. } => false,
Self::Io(_) => true,
Self::Serialization(_) => false,
Self::Custom { .. } => false,
}
}
pub fn severity(&self) -> ErrorSeverity {
match self {
Self::ServerCrash { .. } => ErrorSeverity::Critical,
Self::ServerStartup { .. } => ErrorSeverity::Critical,
Self::ProtocolMismatch { .. } => ErrorSeverity::Critical,
Self::Communication { .. } => ErrorSeverity::High,
Self::Timeout { .. } => ErrorSeverity::Medium,
Self::InvalidConfiguration { .. } => ErrorSeverity::High,
Self::InvalidUri { .. } => ErrorSeverity::Medium,
Self::ServerNotFound { .. } => ErrorSeverity::Medium,
Self::FeatureNotSupported { .. } => ErrorSeverity::Low,
Self::JsonRpc { .. } => ErrorSeverity::Medium,
Self::Io(_) => ErrorSeverity::Medium,
Self::Serialization(_) => ErrorSeverity::Medium,
Self::Custom { .. } => ErrorSeverity::Medium,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Low,
Medium,
High,
Critical,
}
impl fmt::Display for ErrorSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Low => write!(f, "LOW"),
Self::Medium => write!(f, "MEDIUM"),
Self::High => write!(f, "HIGH"),
Self::Critical => write!(f, "CRITICAL"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let error = LspError::server_startup("Test message");
assert!(matches!(error, LspError::ServerStartup { .. }));
assert!(!error.is_recoverable());
assert_eq!(error.severity(), ErrorSeverity::Critical);
}
#[test]
fn test_error_recoverable() {
let recoverable = LspError::server_crash("test-server");
let non_recoverable = LspError::invalid_configuration("bad config");
assert!(recoverable.is_recoverable());
assert!(!non_recoverable.is_recoverable());
}
#[test]
fn test_error_severity() {
let critical = LspError::server_startup("startup failed");
let medium = LspError::timeout(5000);
let low = LspError::feature_not_supported("hover", "test-server");
assert_eq!(critical.severity(), ErrorSeverity::Critical);
assert_eq!(medium.severity(), ErrorSeverity::Medium);
assert_eq!(low.severity(), ErrorSeverity::Low);
}
}