use std::path::PathBuf;
use std::time::Duration;
use crate::io::process::ProcessError;
use crate::lsp::LspError;
use crate::project::ProjectError;
#[derive(Debug, thiserror::Error)]
pub enum ClangdSessionError {
#[error("LSP error: {0}")]
Lsp(#[from] LspError),
#[error("Process error: {0}")]
Process(#[from] ProcessError),
#[error("Project error: {0}")]
Project(#[from] ProjectError),
#[error("Configuration error: {0}")]
Config(#[from] ClangdConfigError),
#[error("Invalid working directory: {path}")]
InvalidWorkingDirectory {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Missing compile_commands.json in build directory: {build_dir}")]
MissingCompileCommands { build_dir: PathBuf },
#[error("Session already started")]
AlreadyStarted,
#[error("Session not started")]
NotStarted,
#[error("Invalid session state: current={current}, expected={expected}")]
InvalidState { current: String, expected: String },
#[error("Build directory detection failed for project: {project_root}")]
BuildDirectoryDetection {
project_root: PathBuf,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("No build directory found in project: {project_root}")]
NoBuildDirectoryFound { project_root: PathBuf },
#[error(
"Multiple build directories found in project: {project_root}, directories: {build_dirs:?}"
)]
MultipleBuildDirectories {
project_root: PathBuf,
build_dirs: Vec<PathBuf>,
},
#[error("Clangd executable not found or invalid: {clangd_path}")]
InvalidClangdExecutable { clangd_path: String },
#[error("Session operation timeout: {operation} took longer than {timeout:?}")]
OperationTimeout {
operation: String,
timeout: Duration,
},
#[error("Session startup failed: {reason}")]
StartupFailed { reason: String },
#[error("Session shutdown failed: {reason}")]
ShutdownFailed { reason: String },
#[error("Unexpected session failure: {reason}")]
UnexpectedFailure { reason: String },
}
#[derive(Debug, thiserror::Error)]
pub enum ClangdConfigError {
#[error("Missing required field: {field}")]
MissingField { field: String },
#[error("Invalid path: {path} - {reason}")]
InvalidPath { path: String, reason: String },
#[error("Invalid timeout: {timeout:?} - {reason}")]
InvalidTimeout { timeout: Duration, reason: String },
#[error("Invalid clangd arguments: {args:?} - {reason}")]
InvalidArguments { args: Vec<String>, reason: String },
#[error("Invalid LSP configuration: {reason}")]
InvalidLspConfig { reason: String },
#[error("Invalid resource configuration: {reason}")]
InvalidResourceConfig { reason: String },
#[error("Path validation failed: {path}")]
PathValidation {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Build directory validation failed: {build_dir}")]
BuildDirectoryValidation {
build_dir: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Working directory validation failed: {working_dir}")]
WorkingDirectoryValidation {
working_dir: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Clangd executable validation failed: {clangd_path}")]
ClangdExecutableValidation {
clangd_path: String,
#[source]
source: std::io::Error,
},
}
impl ClangdSessionError {
pub fn startup_failed(reason: impl Into<String>) -> Self {
Self::StartupFailed {
reason: reason.into(),
}
}
pub fn shutdown_failed(reason: impl Into<String>) -> Self {
Self::ShutdownFailed {
reason: reason.into(),
}
}
pub fn unexpected_failure(reason: impl Into<String>) -> Self {
Self::UnexpectedFailure {
reason: reason.into(),
}
}
pub fn invalid_state(current: impl Into<String>, expected: impl Into<String>) -> Self {
Self::InvalidState {
current: current.into(),
expected: expected.into(),
}
}
pub fn operation_timeout(operation: impl Into<String>, timeout: Duration) -> Self {
Self::OperationTimeout {
operation: operation.into(),
timeout,
}
}
}
impl ClangdConfigError {
pub fn missing_field(field: impl Into<String>) -> Self {
Self::MissingField {
field: field.into(),
}
}
pub fn invalid_path(path: impl Into<String>, reason: impl Into<String>) -> Self {
Self::InvalidPath {
path: path.into(),
reason: reason.into(),
}
}
pub fn invalid_timeout(timeout: Duration, reason: impl Into<String>) -> Self {
Self::InvalidTimeout {
timeout,
reason: reason.into(),
}
}
pub fn invalid_arguments(args: Vec<String>, reason: impl Into<String>) -> Self {
Self::InvalidArguments {
args,
reason: reason.into(),
}
}
pub fn invalid_lsp_config(reason: impl Into<String>) -> Self {
Self::InvalidLspConfig {
reason: reason.into(),
}
}
pub fn invalid_resource_config(reason: impl Into<String>) -> Self {
Self::InvalidResourceConfig {
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation_helpers() {
let startup_error = ClangdSessionError::startup_failed("test reason");
assert!(matches!(
startup_error,
ClangdSessionError::StartupFailed { .. }
));
let config_error = ClangdConfigError::missing_field("test_field");
assert!(matches!(
config_error,
ClangdConfigError::MissingField { .. }
));
}
#[test]
fn test_error_conversion() {
let config_error = ClangdConfigError::missing_field("test");
let session_error: ClangdSessionError = config_error.into();
assert!(matches!(session_error, ClangdSessionError::Config(_)));
}
}