From 595576d9a781fe2f34f778831e12f9830151a6f1 Mon Sep 17 00:00:00 2001 From: RA <70325462+RAprogramm@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:07:15 +0700 Subject: [PATCH] test: enforce AppResult alias usage --- src/convert.rs | 12 +++---- src/convert/actix.rs | 2 +- src/convert/validator.rs | 4 +-- src/lib.rs | 2 +- src/prelude.rs | 2 +- tests/enforce_app_result_alias.rs | 53 +++++++++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/enforce_app_result_alias.rs diff --git a/src/convert.rs b/src/convert.rs index eb0d6d3..cd9258b 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -46,9 +46,9 @@ //! ```rust //! use std::io::{self, ErrorKind}; //! -//! use masterror::{AppError, AppErrorKind}; +//! use masterror::{AppError, AppErrorKind, AppResult}; //! -//! fn open() -> Result<(), AppError> { +//! fn open() -> AppResult<()> { //! let _ = io::Error::new(ErrorKind::Other, "boom"); //! Err(io::Error::from(ErrorKind::Other).into()) //! } @@ -61,9 +61,9 @@ //! feature): //! //! ```rust -//! use masterror::{AppError, AppErrorKind}; +//! use masterror::{AppError, AppErrorKind, AppResult}; //! -//! fn validate(x: i32) -> Result<(), AppError> { +//! fn validate(x: i32) -> AppResult<()> { //! if x < 0 { //! return Err(String::from("must be non-negative").into()); //! } @@ -149,9 +149,9 @@ impl From for AppError { /// Prefer structured validation for complex DTOs, but this covers simple cases. /// /// ```rust -/// use masterror::{AppError, AppErrorKind}; +/// use masterror::{AppError, AppErrorKind, AppResult}; /// -/// fn check(name: &str) -> Result<(), AppError> { +/// fn check(name: &str) -> AppResult<()> { /// if name.is_empty() { /// return Err(String::from("name must not be empty").into()); /// } diff --git a/src/convert/actix.rs b/src/convert/actix.rs index a7868aa..eb1c7e1 100644 --- a/src/convert/actix.rs +++ b/src/convert/actix.rs @@ -5,7 +5,7 @@ //! //! ## What it does //! - Implements `actix_web::ResponseError` for [`AppError`]. -//! - This lets you `return Result<_, AppError>` from Actix handlers. +//! - This lets you `return AppResult<_>` from Actix handlers. //! - On error, Actix automatically builds an `HttpResponse` with the right //! status code and JSON body (when the `serde_json` feature is enabled). //! - Provides stable mapping from [`AppErrorKind`] to diff --git a/src/convert/validator.rs b/src/convert/validator.rs index 0022f0e..e5f7f47 100644 --- a/src/convert/validator.rs +++ b/src/convert/validator.rs @@ -19,7 +19,7 @@ //! ## Example //! //! ```rust,ignore -//! use masterror::{AppError, AppErrorKind}; +//! use masterror::{AppError, AppErrorKind, AppResult}; //! use validator::{Validate, ValidationError}; //! //! #[derive(Validate)] @@ -28,7 +28,7 @@ //! name: String, //! } //! -//! fn validate_payload(p: Payload) -> Result<(), AppError> { +//! fn validate_payload(p: Payload) -> AppResult<()> { //! p.validate()?; //! Ok(()) //! } diff --git a/src/lib.rs b/src/lib.rs index 93eadf2..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 alias `Result` +//! - [`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/src/prelude.rs b/src/prelude.rs index 4824b6d..a1d85da 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -35,7 +35,7 @@ pub use crate::AppCode; pub use crate::AppError; /// High-level taxonomy of application errors (stable categories). pub use crate::AppErrorKind; -/// Convenience alias `Result` used in handlers/services. +/// Convenience alias for returning [`AppError`] from handlers/services. pub use crate::AppResult; /// Stable wire-level error payload for HTTP APIs. pub use crate::ErrorResponse; 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(", ") + ); + } +}