use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to read file: {path}")]
FileRead {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to parse config file: {path}")]
ConfigParse {
path: PathBuf,
#[source]
source: toml::de::Error,
},
#[error("invalid language definition '{name}': {reason}")]
InvalidLanguage { name: String, reason: String },
#[error("invalid regex pattern: {pattern}")]
InvalidRegex {
pattern: String,
#[source]
source: regex::Error,
},
#[error("invalid glob pattern: {pattern}")]
InvalidGlob {
pattern: String,
#[source]
source: globset::Error,
},
#[error("directory not found: {path}")]
DirectoryNotFound { path: PathBuf },
#[error("failed to write output")]
OutputWrite(#[from] std::io::Error),
#[error("failed to render template")]
TemplateRender(#[from] askama::Error),
#[error("failed to serialize JSON")]
JsonSerialize(#[from] serde_json::Error),
#[error("directory traversal error: {0}")]
Walk(#[from] ignore::Error),
#[error("not a git repository: {path}")]
NotGitRepo { path: PathBuf },
#[error("git command failed: {message}")]
GitError { message: String },
#[error("no snapshots found in {path}")]
NoSnapshots { path: PathBuf },
#[error("snapshot not found: {id}")]
SnapshotNotFound { id: String },
#[error("failed to parse language definitions")]
LanguageParse(#[from] toml::de::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn test_error_display_directory_not_found() {
let err = Error::DirectoryNotFound {
path: PathBuf::from("/nonexistent"),
};
assert_eq!(err.to_string(), "directory not found: /nonexistent");
}
#[test]
fn test_error_display_invalid_language() {
let err = Error::InvalidLanguage {
name: "Test".to_string(),
reason: "missing extensions".to_string(),
};
assert_eq!(
err.to_string(),
"invalid language definition 'Test': missing extensions"
);
}
#[test]
#[allow(clippy::invalid_regex)]
fn test_error_display_invalid_regex() {
let regex_err = regex::Regex::new("[invalid").unwrap_err();
let err = Error::InvalidRegex {
pattern: "[invalid".to_string(),
source: regex_err,
};
assert!(err.to_string().contains("invalid regex pattern"));
}
#[test]
fn test_error_from_io_error() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = Error::OutputWrite(io_err);
assert!(err.to_string().contains("failed to write output"));
}
#[test]
fn test_error_display_not_git_repo() {
let err = Error::NotGitRepo {
path: PathBuf::from("/some/path"),
};
assert_eq!(err.to_string(), "not a git repository: /some/path");
}
#[test]
fn test_error_display_git_error() {
let err = Error::GitError {
message: "command not found".to_string(),
};
assert_eq!(err.to_string(), "git command failed: command not found");
}
#[test]
fn test_error_display_no_snapshots() {
let err = Error::NoSnapshots {
path: PathBuf::from(".codelens/snapshots"),
};
assert_eq!(err.to_string(), "no snapshots found in .codelens/snapshots");
}
#[test]
fn test_error_display_snapshot_not_found() {
let err = Error::SnapshotNotFound {
id: "2026-04-01".to_string(),
};
assert_eq!(err.to_string(), "snapshot not found: 2026-04-01");
}
#[test]
fn test_result_type() {
fn returns_ok() -> Result<i32> {
Ok(42)
}
assert_eq!(returns_ok().unwrap(), 42);
let err: Result<i32> = Err(Error::DirectoryNotFound {
path: PathBuf::from("/test"),
});
assert!(err.is_err());
}
}