#[cfg(feature = "cli")]
use miette::Diagnostic;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
#[cfg_attr(feature = "cli", derive(Diagnostic))]
pub enum ConfigError {
#[error("Config file not found: {path}")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::file_not_found)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("Check that the config file path is correct and the file exists"))
)]
FileNotFound {
path: PathBuf,
source: std::io::Error,
},
#[error("Config file is not a regular file: {path}")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::not_a_file)))]
NotAFile {
path: PathBuf,
},
#[error("Config file too large: {path} ({size} bytes exceeds {max_size} bytes)")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::file_too_large)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("Config files should be under 1MB. Check for accidental binary data."))
)]
FileTooLarge {
path: PathBuf,
size: u64,
max_size: u64,
},
#[error("Invalid config file path: {path}")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::invalid_path)))]
InvalidPath {
path: PathBuf,
source: std::io::Error,
},
#[error("Invalid config file type: {path}")]
#[cfg_attr(
feature = "cli",
diagnostic(help("Config file must be a regular file, not a symlink, FIFO, or socket"))
)]
InvalidFileType {
path: PathBuf,
message: String,
},
#[error("Failed to parse config file {path}: {source}")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::parse_error)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("Check that the YAML syntax is valid and all values are correct"))
)]
ParseError {
path: PathBuf,
source: Box<figment::Error>,
},
#[error("Invalid max_depth: {value} (must be between 0 and {max})")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::invalid_max_depth)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("Directory depth must be between 0 (unlimited) and 100"))
)]
InvalidMaxDepth {
value: usize,
max: usize,
},
#[error("Invalid max_files: {value} (must be between 1 and {max})")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::invalid_max_files)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("File limit prevents memory exhaustion. Maximum allowed is 10,000,000"))
)]
InvalidMaxFiles {
value: usize,
max: usize,
},
#[error("Invalid max_clipboard_mb: {value} (must be between 0 and {max})")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::invalid_max_clipboard)))]
#[cfg_attr(
feature = "cli",
diagnostic(help("Clipboard limit prevents OOM. Maximum allowed is 1000MB"))
)]
InvalidMaxClipboardMb {
value: usize,
max: usize,
},
#[error("Invalid {pattern_type} pattern: {pattern}")]
#[cfg_attr(feature = "cli", diagnostic(code(luff::config::invalid_pattern)))]
InvalidPattern {
pattern: String,
pattern_type: String,
help: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_error_display() {
let err = ConfigError::InvalidMaxFiles {
value: 20_000_000,
max: 10_000_000,
};
let error_string = err.to_string();
assert!(error_string.contains("20000000"));
assert!(error_string.contains("10000000"));
assert!(error_string.contains("max_files"));
}
#[test]
fn test_config_file_error() {
let path = PathBuf::from("/nonexistent/config.yaml");
let err = ConfigError::FileNotFound {
path,
source: std::io::Error::new(std::io::ErrorKind::NotFound, "not found"),
};
assert!(err.to_string().contains("Config file not found"));
assert!(err.to_string().contains("/nonexistent/config.yaml"));
}
#[test]
fn test_invalid_pattern_fields() {
let err = ConfigError::InvalidPattern {
pattern: "*.png".to_string(),
pattern_type: "extension".to_string(),
help: "no globs allowed".to_string(),
};
match err {
ConfigError::InvalidPattern {
pattern,
pattern_type,
help,
} => {
assert_eq!(pattern, "*.png");
assert_eq!(pattern_type, "extension");
assert_eq!(help, "no globs allowed");
}
_ => panic!("Unexpected error variant"),
}
}
#[test]
fn test_invalid_max_depth() {
let err = ConfigError::InvalidMaxDepth {
value: 200,
max: 100,
};
let msg = err.to_string();
assert!(msg.contains("200"));
assert!(msg.contains("100"));
}
#[test]
fn test_invalid_max_clipboard_mb() {
let err = ConfigError::InvalidMaxClipboardMb {
value: 2000,
max: 1000,
};
let msg = err.to_string();
assert!(msg.contains("2000"));
assert!(msg.contains("1000"));
}
#[test]
fn test_file_too_large() {
let err = ConfigError::FileTooLarge {
path: PathBuf::from("/tmp/huge.yaml"),
size: 2_000_000,
max_size: 1_000_000,
};
let msg = err.to_string();
assert!(msg.contains("/tmp/huge.yaml"));
assert!(msg.contains("2000000"));
}
#[test]
fn test_invalid_file_type() {
let err = ConfigError::InvalidFileType {
path: PathBuf::from("/tmp/fifo"),
message: "is a FIFO".to_string(),
};
assert!(err.to_string().contains("/tmp/fifo"));
}
#[test]
fn test_not_a_file() {
let err = ConfigError::NotAFile {
path: PathBuf::from("/tmp/dir"),
};
assert!(err.to_string().contains("not a regular file"));
}
}