use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RemainingError {
#[error("file not found: {}", path.display())]
FileNotFound { path: PathBuf },
#[error("symbol '{}' not found in {}", symbol, file.display())]
SymbolNotFound { symbol: String, file: PathBuf },
#[error("parse error in {}: {message}", file.display())]
ParseError { file: PathBuf, message: String },
#[error("invalid argument: {message}")]
InvalidArgument { message: String },
#[error("file too large: {} ({bytes} bytes)", path.display())]
FileTooLarge { path: PathBuf, bytes: u64 },
#[error("path traversal blocked: {}", path.display())]
PathTraversal { path: PathBuf },
#[error("unsupported language: {language}")]
UnsupportedLanguage { language: String },
#[error("analysis error: {message}")]
AnalysisError { message: String },
#[error("{count} findings detected")]
FindingsDetected { count: u32 },
#[error("analysis timed out after {seconds}s")]
Timeout { seconds: u64 },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
impl RemainingError {
pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
Self::FileNotFound { path: path.into() }
}
pub fn symbol_not_found(symbol: impl Into<String>, file: impl Into<PathBuf>) -> Self {
Self::SymbolNotFound {
symbol: symbol.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_argument(message: impl Into<String>) -> Self {
Self::InvalidArgument {
message: message.into(),
}
}
pub fn file_too_large(path: impl Into<PathBuf>, bytes: u64) -> Self {
Self::FileTooLarge {
path: path.into(),
bytes,
}
}
pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
Self::PathTraversal { path: path.into() }
}
pub fn unsupported_language(language: impl Into<String>) -> Self {
Self::UnsupportedLanguage {
language: language.into(),
}
}
pub fn analysis_error(message: impl Into<String>) -> Self {
Self::AnalysisError {
message: message.into(),
}
}
pub fn findings_detected(count: u32) -> Self {
Self::FindingsDetected { count }
}
pub fn timeout(seconds: u64) -> Self {
Self::Timeout { seconds }
}
pub fn exit_code(&self) -> i32 {
match self {
Self::FindingsDetected { .. } => 2, _ => 1, }
}
}
pub type RemainingResult<T> = Result<T, RemainingError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_not_found_error() {
let err = RemainingError::file_not_found("/path/to/file.py");
assert!(err.to_string().contains("file not found"));
assert!(err.to_string().contains("file.py"));
}
#[test]
fn test_symbol_not_found_error() {
let err = RemainingError::symbol_not_found("my_function", "/path/to/file.py");
assert!(err.to_string().contains("my_function"));
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_exit_codes() {
assert_eq!(RemainingError::file_not_found("/foo").exit_code(), 1);
assert_eq!(RemainingError::findings_detected(5).exit_code(), 2);
}
}