Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 108 additions & 106 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[package]
name = "masterror"
version = "0.27.1"
version = "0.27.2"
rust-version = "1.92"
edition = "2024"
license = "MIT"
Expand All @@ -13,7 +13,7 @@ readme = "README.md"
description = "Application error types and response mapping"
documentation = "https://docs.rs/masterror"
build = "build.rs"
categories = ["rust-patterns", "web-programming"]
categories = ["rust-patterns", "web-programming", "no-std"]
keywords = ["error", "api", "framework"]
include = [
"Cargo.toml",
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ The build script keeps the full feature snippet below in sync with

~~~toml
[dependencies]
masterror = { version = "0.27.1", default-features = false }
masterror = { version = "0.27.2", default-features = false }
# or with features:
# masterror = { version = "0.27.1", features = [
# masterror = { version = "0.27.2", features = [
# "std", "axum", "actix", "openapi",
# "serde_json", "tracing", "metrics", "backtrace",
# "colored", "sqlx", "sqlx-migrate", "reqwest",
Expand Down Expand Up @@ -640,7 +640,7 @@ Enable the `colored` feature for enhanced terminal output in local mode:

~~~toml
[dependencies]
masterror = { version = "0.27.1", features = ["colored"] }
masterror = { version = "0.27.2", features = ["colored"] }
~~~

With `colored` enabled, errors display with syntax highlighting:
Expand Down
2 changes: 1 addition & 1 deletion masterror-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[package]
name = "masterror-derive"
rust-version = "1.92"
version = "0.11.1"
version = "0.11.2"
edition = "2024"
license = "MIT"
repository = "https://github.com/RAprogramm/masterror"
Expand Down
3 changes: 3 additions & 0 deletions masterror-derive/src/display/enum_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
input::{
DisplaySpec, ErrorInput, Field, Fields, FormatArgsSpec, VariantData, placeholder_error
},
lint::lifetime_lint_allows,
template_support::{DisplayTemplate, TemplateIdentifierSpec}
};

Expand All @@ -47,7 +48,9 @@ pub fn expand_enum(input: &ErrorInput, variants: &[VariantData]) -> Result<Token
}
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics core::fmt::Display for #ident #ty_generics #where_clause {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Expand Down
3 changes: 3 additions & 0 deletions masterror-derive/src/display/struct_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use super::{
};
use crate::{
input::{DisplaySpec, ErrorInput, Field, Fields, StructData, placeholder_error},
lint::lifetime_lint_allows,
template_support::TemplateIdentifierSpec
};

Expand Down Expand Up @@ -64,7 +65,9 @@ pub fn expand_struct(input: &ErrorInput, data: &StructData) -> Result<TokenStrea
};
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics core::fmt::Display for #ident #ty_generics #where_clause {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#body
Expand Down
9 changes: 8 additions & 1 deletion masterror-derive/src/error_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::Error;

use crate::input::{ErrorData, ErrorInput, StructData, VariantData};
use crate::{
input::{ErrorData, ErrorInput, StructData, VariantData},
lint::lifetime_lint_allows
};

pub mod backtrace;
pub mod binding;
Expand Down Expand Up @@ -107,7 +110,9 @@ fn expand_struct(input: &ErrorInput, data: &StructData) -> Result<TokenStream, E
let provide_method = provide_method.unwrap_or_default();
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics std::error::Error for #ident #ty_generics #where_clause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
#body
Expand Down Expand Up @@ -142,7 +147,9 @@ fn expand_enum(input: &ErrorInput, variants: &[VariantData]) -> Result<TokenStre
let provide_method = provide_method.unwrap_or_default();
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics std::error::Error for #ident #ty_generics #where_clause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Expand Down
1 change: 1 addition & 0 deletions masterror-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod display;
mod error_trait;
mod from_impl;
mod input;
mod lint;
mod masterror_impl;
mod span;
mod template_support;
Expand Down
83 changes: 83 additions & 0 deletions masterror-derive/src/lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
//
// SPDX-License-Identifier: MIT

//! Clippy lint suppression for generated code.
//!
//! When generating trait implementations for types with lifetime parameters,
//! clippy may emit `needless_lifetimes` or `elidable_lifetime_names` warnings.
//! These warnings conflict with project-level `forbid` directives, so we
//! conditionally suppress them only when the type actually has lifetimes.

use proc_macro2::TokenStream;
use quote::quote;
use syn::Generics;

/// Generates conditional clippy lint allows for lifetime-related warnings.
///
/// Returns `#[allow(...)]` attributes only when the generics contain lifetime
/// parameters. This prevents conflicts with project-level `forbid` directives
/// while still suppressing false positives in generated code.
///
/// # Arguments
///
/// * `generics` - The generics from the type definition
///
/// # Returns
///
/// Token stream with allow attributes if lifetimes present, empty otherwise
pub fn lifetime_lint_allows(generics: &Generics) -> TokenStream {
if generics.lifetimes().next().is_some() {
quote! {
#[allow(clippy::elidable_lifetime_names, clippy::needless_lifetimes)]
}
} else {
TokenStream::new()
}
}

#[cfg(test)]
mod tests {
use syn::parse_quote;

use super::*;

#[test]
fn test_no_lifetimes_returns_empty() {
let generics: Generics = parse_quote!();
let result = lifetime_lint_allows(&generics);
assert!(result.is_empty());
}

#[test]
fn test_type_params_only_returns_empty() {
let generics: Generics = parse_quote!(<T, U>);
let result = lifetime_lint_allows(&generics);
assert!(result.is_empty());
}

#[test]
fn test_with_lifetime_returns_allows() {
let generics: Generics = parse_quote!(<'a>);
let result = lifetime_lint_allows(&generics);
let output = result.to_string();
assert!(output.contains("allow"));
assert!(output.contains("elidable_lifetime_names"));
assert!(output.contains("needless_lifetimes"));
}

#[test]
fn test_mixed_params_with_lifetime_returns_allows() {
let generics: Generics = parse_quote!(<'a, T, 'b>);
let result = lifetime_lint_allows(&generics);
let output = result.to_string();
assert!(output.contains("allow"));
}

#[test]
fn test_const_generics_only_returns_empty() {
let generics: Generics = parse_quote!(<const N: usize>);
let result = lifetime_lint_allows(&generics);
assert!(result.is_empty());
}
}
1 change: 1 addition & 0 deletions src/app_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
mod constructors;
mod context;
mod core;
mod inline_vec;
mod metadata;

pub use core::{AppError, AppResult, DisplayMode, Error, ErrorChain, MessageEditPolicy};
Expand Down
7 changes: 2 additions & 5 deletions src/app_error/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,8 @@ impl Display for Error {
let mut current: &dyn CoreError = source.as_ref();
let mut depth = 0;
while depth < 10 {
writeln!(
f,
"{}",
style::source_context(alloc::format!("Caused by: {}", current))
)?;
write!(f, " {}: ", style::source_context("Caused by"))?;
writeln!(f, "{}", style::source_context(current.to_string()))?;
if let Some(next) = current.source() {
current = next;
depth += 1;
Expand Down
Loading
Loading