#[cfg(feature = "git")]
use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ConfigError {
#[error("Options '{option1}' and '{option2}' cannot be used simultaneously.")]
Conflict {
option1: String,
option2: String,
},
#[error("Invalid value for option '{option}': {reason}")]
InvalidValue {
option: String,
reason: String,
},
#[error("Option '{option}' requires option '{required}' to be specified.")]
MissingDependency {
option: String,
required: String,
},
#[error("Failed to determine or create git cache directory: {0}")]
CacheDir(String),
#[error("Invalid {name} regex: '{pattern}': {source}")]
InvalidRegex {
name: String,
pattern: String,
#[source]
source: regex::Error,
},
#[error("Invalid size format: '{0}'")]
InvalidSizeFormat(String),
}
#[cfg(feature = "git")]
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum GitError {
#[error("Failed to clone repository from '{url}': {source}")]
CloneFailed {
url: String,
#[source]
source: git2::Error,
},
#[error("Failed to fetch from remote '{remote}': {source}")]
FetchFailed {
remote: String,
#[source]
source: git2::Error,
},
#[error("Failed to update local repository: {0}")]
UpdateFailed(String),
#[error("Could not find remote branch or tag named '{name}' after fetch.")]
RefNotFound {
name: String,
},
#[error("Failed to download from GitHub API for '{url}': {source}")]
ApiDownloadFailed {
url: String,
#[source]
source: anyhow::Error,
},
#[error("Subdirectory '{path}' not found in repository '{repo}'.")]
SubdirectoryNotFound {
path: String,
repo: String,
},
#[error("Cached repository at '{path}' is corrupted or invalid.")]
CorruptedCache {
path: PathBuf,
},
#[error("Could not determine remote's default branch: {0}")]
DefaultBranchResolution(String),
#[error(transparent)]
Generic(#[from] anyhow::Error),
}
#[cfg(feature = "clipboard")]
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ClipboardError {
#[error("Failed to initialize clipboard: {0}")]
Initialization(String),
#[error("Failed to set clipboard content: {0}")]
SetContent(String),
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("I/O error accessing path '{path}': {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error(transparent)]
Config(#[from] ConfigError),
#[cfg(feature = "git")]
#[error(transparent)]
Git(#[from] GitError),
#[cfg(feature = "clipboard")]
#[error(transparent)]
Clipboard(#[from] ClipboardError),
#[error("Operation cancelled by user (Ctrl+C)")]
Interrupted,
#[error(transparent)]
Generic(#[from] anyhow::Error),
#[error("No files found matching the specified criteria.")]
NoFilesFound,
}
pub fn io_error_with_path<P: AsRef<std::path::Path>>(source: std::io::Error, path: P) -> Error {
Error::Io {
path: path.as_ref().display().to_string(),
source,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{io, path::PathBuf};
#[test]
fn test_io_error_with_path_helper() {
let path = PathBuf::from("some/test/path.txt"); let source_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
let app_error = io_error_with_path(source_error, &path);
if let Error::Io {
path: error_path,
source,
} = app_error
{
assert!(error_path.contains("some/test/path.txt")); assert_eq!(source.kind(), io::ErrorKind::NotFound);
assert!(source.to_string().contains("File not found"));
} else {
panic!("Expected Error::Io");
}
let path_str = "another/path";
let source_error_perm = io::Error::new(io::ErrorKind::PermissionDenied, "Access denied");
let app_error_perm = io_error_with_path(source_error_perm, path_str); if let Error::Io {
path: error_path,
source,
} = app_error_perm
{
assert!(error_path.contains("another/path")); assert_eq!(source.kind(), io::ErrorKind::PermissionDenied);
assert!(source.to_string().contains("Access denied"));
} else {
panic!("Expected Error::Io");
}
}
}