use std::fs;
use crate::check::Check;
use crate::project::Project;
use crate::types::{CheckGroup, CheckLayer, CheckResult, CheckStatus};
pub struct ErrorModuleCheck;
impl Check for ErrorModuleCheck {
fn id(&self) -> &str {
"p4-error-module"
}
fn group(&self) -> CheckGroup {
CheckGroup::P4
}
fn layer(&self) -> CheckLayer {
CheckLayer::Project
}
fn covers(&self) -> &'static [&'static str] {
&["p4-should-structured-enum"]
}
fn applicable(&self, project: &Project) -> bool {
project.path.is_dir() && project.language.is_some()
}
fn run(&self, project: &Project) -> anyhow::Result<CheckResult> {
let src_dir = project.path.join("src");
let direct_candidates = ["error.rs", "errors.rs", "error.py", "errors.py"];
for candidate in &direct_candidates {
if src_dir.join(candidate).exists() {
return Ok(CheckResult {
id: self.id().to_string(),
label: "Dedicated error module exists".into(),
group: self.group(),
layer: self.layer(),
status: CheckStatus::Pass,
});
}
}
if src_dir.is_dir() {
if let Ok(entries) = fs::read_dir(&src_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
for name in &["error.rs", "errors.rs", "error.py", "errors.py"] {
if path.join(name).exists() {
return Ok(CheckResult {
id: self.id().to_string(),
label: "Dedicated error module exists".into(),
group: self.group(),
layer: self.layer(),
status: CheckStatus::Pass,
});
}
}
}
}
}
}
Ok(CheckResult {
id: self.id().to_string(),
label: "Dedicated error module exists".into(),
group: self.group(),
layer: self.layer(),
status: CheckStatus::Warn(
"No dedicated error module found (expected src/error.rs or src/errors.rs)".into(),
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn temp_dir(suffix: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!(
"anc-errmod-{suffix}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system time after UNIX epoch")
.as_nanos(),
));
fs::create_dir_all(&dir).expect("create test dir");
dir
}
#[test]
fn applicable_when_language_detected() {
let dir = temp_dir("applicable");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let project = Project::discover(&dir).expect("discover test project");
assert!(ErrorModuleCheck.applicable(&project));
}
#[test]
fn not_applicable_without_language() {
let dir = temp_dir("no-lang");
let project = Project::discover(&dir).expect("discover test project");
assert!(!ErrorModuleCheck.applicable(&project));
}
#[test]
fn pass_with_error_rs() {
let dir = temp_dir("error-rs");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let src = dir.join("src");
fs::create_dir_all(&src).expect("create src dir");
fs::write(src.join("error.rs"), "pub enum AppError {}").expect("write error.rs");
let project = Project::discover(&dir).expect("discover test project");
let result = ErrorModuleCheck.run(&project).expect("run check");
assert_eq!(result.status, CheckStatus::Pass);
}
#[test]
fn pass_with_errors_rs() {
let dir = temp_dir("errors-rs");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let src = dir.join("src");
fs::create_dir_all(&src).expect("create src dir");
fs::write(src.join("errors.rs"), "pub enum AppError {}").expect("write errors.rs");
let project = Project::discover(&dir).expect("discover test project");
let result = ErrorModuleCheck.run(&project).expect("run check");
assert_eq!(result.status, CheckStatus::Pass);
}
#[test]
fn pass_with_nested_error_module() {
let dir = temp_dir("nested");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let submod = dir.join("src").join("core");
fs::create_dir_all(&submod).expect("create submod dir");
fs::write(submod.join("error.rs"), "pub enum CoreError {}").expect("write error.rs");
let project = Project::discover(&dir).expect("discover test project");
let result = ErrorModuleCheck.run(&project).expect("run check");
assert_eq!(result.status, CheckStatus::Pass);
}
#[test]
fn warn_when_no_error_module() {
let dir = temp_dir("no-error");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let src = dir.join("src");
fs::create_dir_all(&src).expect("create src dir");
fs::write(src.join("main.rs"), "fn main() {}").expect("write main.rs");
let project = Project::discover(&dir).expect("discover test project");
let result = ErrorModuleCheck.run(&project).expect("run check");
assert!(matches!(result.status, CheckStatus::Warn(_)));
}
#[test]
fn warn_when_no_src_dir() {
let dir = temp_dir("no-src");
fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\n",
)
.expect("write Cargo.toml");
let project = Project::discover(&dir).expect("discover test project");
let result = ErrorModuleCheck.run(&project).expect("run check");
assert!(matches!(result.status, CheckStatus::Warn(_)));
}
#[test]
fn metadata_is_correct() {
let check = ErrorModuleCheck;
assert_eq!(check.id(), "p4-error-module");
assert_eq!(check.group(), CheckGroup::P4);
assert_eq!(check.layer(), CheckLayer::Project);
}
}