use std::path::PathBuf;
use thiserror::Error;
use crate::types::HookEvent;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Settings(#[from] SettingsError),
#[error(transparent)]
Registry(#[from] RegistryError),
#[error(transparent)]
Hook(#[from] HookError),
}
#[derive(Debug, Error)]
pub enum SettingsError {
#[error("Settings file not found: {0}")]
NotFound(PathBuf),
#[error("Failed to read settings: {0}")]
Io(#[source] std::io::Error),
#[error("Failed to parse settings: {0}")]
Parse(String),
#[error("Failed to write settings atomically: {path} - Safety copy at: {temp_path}")]
WriteAtomic {
path: PathBuf,
temp_path: PathBuf,
},
}
#[derive(Debug, Error)]
pub enum RegistryError {
#[error("Failed to read registry: {0}")]
Io(#[source] std::io::Error),
#[error("Failed to parse registry: {0}")]
Parse(String),
#[error("Failed to write registry: {0}")]
Write(String),
}
#[derive(Debug, Error)]
pub enum HookError {
#[error("Hook already exists: {event:?} - {command}")]
AlreadyExists {
event: HookEvent,
command: String,
},
#[error("Hook not managed by claude-hooks: {event:?} - {command}")]
NotManaged {
event: HookEvent,
command: String,
},
#[error("Invalid hook handler: {0}")]
InvalidHandler(String),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_settings_error_not_found_display() {
let path = PathBuf::from("/home/user/.claude/settings.json");
let err = SettingsError::NotFound(path);
let display = format!("{}", err);
assert!(
display.contains("/home/user/.claude/settings.json"),
"Error should contain path"
);
}
#[test]
fn test_settings_error_write_atomic_display() {
let path = PathBuf::from("/home/user/.claude/settings.json");
let temp_path = PathBuf::from("/home/user/.claude/settings.json.tmp.20260203-143022");
let err = SettingsError::WriteAtomic { path, temp_path };
let display = format!("{}", err);
assert!(
display.contains("/home/user/.claude/settings.json"),
"Error should contain path"
);
assert!(
display.contains("settings.json.tmp.20260203-143022"),
"Error should contain temp path"
);
assert!(display.contains("Safety copy"), "Error should mention safety copy");
}
#[test]
fn test_hook_error_already_exists_display() {
let err = HookError::AlreadyExists {
event: HookEvent::Stop,
command: "/path/to/stop.sh".to_string(),
};
let display = format!("{}", err);
assert!(display.contains("Stop"), "Error should contain event");
assert!(
display.contains("/path/to/stop.sh"),
"Error should contain command"
);
}
#[test]
fn test_hook_error_not_managed_display() {
let err = HookError::NotManaged {
event: HookEvent::SessionStart,
command: "/path/to/start.sh".to_string(),
};
let display = format!("{}", err);
assert!(display.contains("SessionStart"), "Error should contain event");
assert!(
display.contains("/path/to/start.sh"),
"Error should contain command"
);
assert!(
display.contains("not managed"),
"Error should indicate not managed"
);
}
#[test]
fn test_registry_error_parse_display() {
let err = RegistryError::Parse("Invalid JSON at line 5".to_string());
let display = format!("{}", err);
assert!(
display.contains("Invalid JSON at line 5"),
"Error should contain parse details"
);
}
}