use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PatternsError {
#[error("file not found: {}", path.display())]
FileNotFound { path: PathBuf },
#[error("function '{function}' not found in {}", file.display())]
FunctionNotFound { function: String, file: PathBuf },
#[error("class '{class_name}' not found in {}", file.display())]
ClassNotFound { class_name: String, file: PathBuf },
#[error("parse error in {}: {message}", file.display())]
ParseError { file: PathBuf, message: String },
#[error("file too large: {} ({bytes} bytes, max {max_bytes} bytes)", path.display())]
FileTooLarge {
path: PathBuf,
bytes: u64,
max_bytes: u64,
},
#[error("directory scan limit exceeded: {count} files found, max {max_files}")]
TooManyFiles { count: u32, max_files: u32 },
#[error("analysis depth limit exceeded: depth {depth}, max {max_depth}")]
DepthLimitExceeded { depth: u32, max_depth: u32 },
#[error("analysis timed out after {timeout_secs}s")]
Timeout { timeout_secs: u64 },
#[error("invalid parameter: {message}")]
InvalidParameter { message: String },
#[error("path traversal blocked: {} attempts to escape project root", path.display())]
PathTraversal { path: PathBuf },
#[error("path is not a directory: {}", path.display())]
NotADirectory { path: PathBuf },
#[error("unsupported language: {language}")]
UnsupportedLanguage { language: String },
#[error("no constraints found matching criteria")]
NoConstraintsFound,
#[error("resource issues found: {leaks} leaks, {double_closes} double-closes, {use_after_closes} use-after-close")]
IssuesFound {
leaks: u32,
double_closes: u32,
use_after_closes: u32,
},
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
pub type PatternsResult<T> = Result<T, PatternsError>;
impl PatternsError {
pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
Self::FileNotFound { path: path.into() }
}
pub fn function_not_found(function: impl Into<String>, file: impl Into<PathBuf>) -> Self {
Self::FunctionNotFound {
function: function.into(),
file: file.into(),
}
}
pub fn class_not_found(class_name: impl Into<String>, file: impl Into<PathBuf>) -> Self {
Self::ClassNotFound {
class_name: class_name.into(),
file: file.into(),
}
}
pub fn parse_error(file: impl Into<PathBuf>, message: impl Into<String>) -> Self {
Self::ParseError {
file: file.into(),
message: message.into(),
}
}
pub fn invalid_parameter(message: impl Into<String>) -> Self {
Self::InvalidParameter {
message: message.into(),
}
}
pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
Self::PathTraversal { path: path.into() }
}
pub fn file_too_large(path: impl Into<PathBuf>, bytes: u64, max_bytes: u64) -> Self {
Self::FileTooLarge {
path: path.into(),
bytes,
max_bytes,
}
}
pub fn depth_exceeded(depth: u32, max_depth: u32) -> Self {
Self::DepthLimitExceeded { depth, max_depth }
}
pub fn too_many_files(count: u32, max_files: u32) -> Self {
Self::TooManyFiles { count, max_files }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_file_not_found_message() {
let err = PatternsError::file_not_found("/path/to/file.py");
let msg = err.to_string();
assert!(msg.contains("file not found"));
assert!(msg.contains("file.py"));
}
#[test]
fn test_error_function_not_found_message() {
let err = PatternsError::function_not_found("my_func", "/path/to/file.py");
let msg = err.to_string();
assert!(msg.contains("my_func"));
assert!(msg.contains("not found"));
}
#[test]
fn test_error_path_traversal_message() {
let err = PatternsError::path_traversal("../etc/passwd");
let msg = err.to_string();
assert!(msg.contains("path traversal"));
assert!(msg.contains("etc/passwd"));
}
#[test]
fn test_error_file_too_large_message() {
let err = PatternsError::file_too_large("/big/file.py", 20_000_000, 10_000_000);
let msg = err.to_string();
assert!(msg.contains("too large"));
assert!(msg.contains("20000000"));
}
#[test]
fn test_error_depth_exceeded_message() {
let err = PatternsError::depth_exceeded(150, 100);
let msg = err.to_string();
assert!(msg.contains("depth"));
assert!(msg.contains("150"));
assert!(msg.contains("100"));
}
#[test]
fn test_error_too_many_files_message() {
let err = PatternsError::too_many_files(1500, 1000);
let msg = err.to_string();
assert!(msg.contains("1500"));
assert!(msg.contains("1000"));
}
#[test]
fn test_error_parse_error_message() {
let err = PatternsError::parse_error("/file.py", "unexpected token");
let msg = err.to_string();
assert!(msg.contains("parse error"));
assert!(msg.contains("unexpected token"));
}
#[test]
fn test_error_invalid_parameter_message() {
let err = PatternsError::invalid_parameter("value must be positive");
let msg = err.to_string();
assert!(msg.contains("invalid parameter"));
assert!(msg.contains("positive"));
}
}