use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error("Invalid command: {0}")]
InvalidCommand(String),
#[error("Missing required argument: {0}")]
MissingArgument(String),
#[error("Invalid argument value: {0}")]
InvalidArgument(String),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("Invalid threshold value: {0}")]
InvalidThreshold(String),
#[error("Path does not exist: {}", .0.display())]
PathNotFound(PathBuf),
#[error("Invalid configuration file: {0}")]
InvalidConfigFile(String),
#[error("Validation failed: {0}")]
ValidationFailed(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum AnalysisError {
#[error("Failed to parse file: {}", .path.display())]
ParseError {
path: PathBuf,
#[source]
source: anyhow::Error,
},
#[error("Analysis failed: {0}")]
AnalysisFailed(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("CLI error: {0}")]
Cli(#[from] CliError),
#[error("Analysis error: {0}")]
Analysis(#[from] AnalysisError),
}
impl AppError {
pub fn exit_code(&self) -> i32 {
match self {
AppError::Cli(_) => 2, AppError::Analysis(_) => 1, }
}
pub fn user_message(&self) -> String {
match self {
AppError::Cli(CliError::Config(ConfigError::PathNotFound(path))) => {
format!(
"Error: Path '{}' does not exist.\n\n\
Suggestion: Check the path and try again, or run:\n\
debtmap analyze <path>",
path.display()
)
}
AppError::Cli(CliError::Config(ConfigError::InvalidThreshold(msg))) => {
format!(
"Error: {}\n\n\
Suggestion: Use --threshold-complexity <n> where n > 0\n\
See 'debtmap analyze --help' for more information.",
msg
)
}
AppError::Analysis(AnalysisError::ParseError { path, source }) => {
format!(
"Error: Failed to parse '{}':\n {}\n\n\
Suggestion: Check file syntax or exclude with --exclude",
path.display(),
source
)
}
_ => self.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_error_from_config_error() {
let config_err = ConfigError::InvalidThreshold("test".to_string());
let cli_err: CliError = config_err.into();
assert!(matches!(cli_err, CliError::Config(_)));
}
#[test]
fn test_app_error_from_cli_error() {
let cli_err = CliError::InvalidCommand("test".to_string());
let app_err: AppError = cli_err.into();
assert!(matches!(app_err, AppError::Cli(_)));
assert_eq!(app_err.exit_code(), 2);
}
#[test]
fn test_app_error_from_analysis_error() {
let analysis_err = AnalysisError::AnalysisFailed("test".to_string());
let app_err: AppError = analysis_err.into();
assert!(matches!(app_err, AppError::Analysis(_)));
assert_eq!(app_err.exit_code(), 1);
}
#[test]
fn test_path_not_found_user_message() {
let err = AppError::Cli(CliError::Config(ConfigError::PathNotFound(PathBuf::from(
"/nonexistent",
))));
let msg = err.user_message();
assert!(msg.contains("does not exist"));
assert!(msg.contains("Suggestion"));
}
#[test]
fn test_invalid_threshold_user_message() {
let err = AppError::Cli(CliError::Config(ConfigError::InvalidThreshold(
"must be > 0".to_string(),
)));
let msg = err.user_message();
assert!(msg.contains("must be > 0"));
assert!(msg.contains("--threshold-complexity"));
}
}