diff --git a/src/lib.rs b/src/lib.rs index 8871b71..1e0fa0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ //! - [`AppError`] — thin wrapper around a semantic error kind and optional //! message //! - [`AppErrorKind`] — stable internal taxonomy of application errors -//! - [`AppResult`] — convenience result alias (defaults to [`AppError`]) +//! - [`AppResult`] — convenience alias for returning [`AppError`] //! - [`ErrorResponse`] — stable wire-level JSON payload for HTTP APIs //! - [`AppCode`] — public, machine-readable error code for clients //! diff --git a/tests/enforce_app_result_alias.rs b/tests/enforce_app_result_alias.rs new file mode 100644 index 0000000..b07acee --- /dev/null +++ b/tests/enforce_app_result_alias.rs @@ -0,0 +1,53 @@ +use std::{ + ffi::OsStr, + fs, io, + path::{Path, PathBuf} +}; + +fn collect_rs_files(dir: &Path, files: &mut Vec) -> io::Result<()> { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + collect_rs_files(&path, files)?; + } else if path.extension() == Some(OsStr::new("rs")) { + files.push(path); + } + } + Ok(()) +} + +#[test] +fn prohibits_direct_result_app_error_usage() { + let src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src"); + let mut files = Vec::new(); + collect_rs_files(&src_dir, &mut files).expect("collect Rust sources"); + + let mut offenders = Vec::new(); + + for path in &files { + let content = fs::read_to_string(path) + .unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display())); + + for (idx, line) in content.lines().enumerate() { + if line.contains("Result<") + && line.contains("AppError") + && !line.contains("AppResult<") + { + if path.file_name() == Some(OsStr::new("app_error.rs")) + && line.contains("pub type AppResult") + { + continue; + } + offenders.push(format!("{}:{}", path.display(), idx + 1)); + } + } + } + + if !offenders.is_empty() { + panic!( + "Found direct `Result<_, AppError>` usage; replace with `AppResult<_>`: {}", + offenders.join(", ") + ); + } +}