From a82193712f605753156f6c6f22f74aaf6bf24b97 Mon Sep 17 00:00:00 2001 From: RA <70325462+RAprogramm@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:51:14 +0700 Subject: [PATCH] Enable no_std core and std feature flag --- CHANGELOG.md | 15 +++++ Cargo.lock | 2 +- Cargo.toml | 61 +++++++++-------- README.md | 16 ++--- src/app_error/constructors.rs | 2 +- src/app_error/context.rs | 5 +- src/app_error/core.rs | 56 +++++++++------- src/app_error/metadata.rs | 16 ++--- src/code/app_code.rs | 6 +- src/convert.rs | 3 + src/kind.rs | 65 +++++++++++-------- src/lib.rs | 22 ++++--- src/response/core.rs | 1 + src/response/details.rs | 3 + src/response/internal.rs | 2 +- src/response/legacy.rs | 1 + src/response/mapping.rs | 3 +- src/response/metadata.rs | 5 +- src/response/problem_json.rs | 8 ++- src/result_ext.rs | 6 +- .../fail/enum_missing_variant.stderr | 7 +- tests/ui/app_error/fail/missing_code.stderr | 4 +- tests/ui/app_error/fail/missing_kind.stderr | 2 +- tests/ui/formatter/fail/duplicate_fmt.stderr | 2 +- .../fail/implicit_after_named.stderr | 1 + .../ui/formatter/fail/unsupported_flag.stderr | 4 +- .../fail/unsupported_formatter.stderr | 4 +- .../ui/formatter/fail/uppercase_binary.stderr | 4 +- .../formatter/fail/uppercase_pointer.stderr | 4 +- tests/ui/masterror/fail/duplicate_attr.stderr | 2 +- .../masterror/fail/duplicate_telemetry.stderr | 2 +- tests/ui/masterror/fail/empty_redact.stderr | 2 +- .../fail/enum_missing_variant.stderr | 7 +- .../ui/masterror/fail/missing_category.stderr | 4 +- tests/ui/masterror/fail/missing_code.stderr | 4 +- tests/ui/masterror/fail/unknown_option.stderr | 2 +- 36 files changed, 211 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36fd3cc..74cbea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.22.0] - 2025-10-11 + +### Added +- Introduced an explicit `std` feature (enabled by default) and made the core + crate compile in `no_std + alloc` environments, including metadata builders + and error helpers. + +### Changed +- Reworked `AppError` internals to rely on `core`/`alloc` primitives and + `core::error::Error`, providing `std::error::Error` only when the `std` + feature is active. +- Replaced `thiserror` derives on `AppErrorKind` with manual `Display`/error + implementations so the taxonomy remains available without the standard + library. + ## [0.21.2] - 2025-10-10 ### Added diff --git a/Cargo.lock b/Cargo.lock index a609bf8..6b697e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1727,7 +1727,7 @@ dependencies = [ [[package]] name = "masterror" -version = "0.21.2" +version = "0.22.0" dependencies = [ "actix-web", "axum 0.8.4", diff --git a/Cargo.toml b/Cargo.toml index 00e286b..794a1be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.21.2" +version = "0.22.0" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" @@ -49,30 +49,34 @@ readme = "README.md" [features] -default = [] -tracing = ["dep:tracing", "dep:log", "dep:log-mdc"] -metrics = ["dep:metrics"] -backtrace = [] -axum = ["dep:axum", "dep:serde_json"] -actix = ["dep:actix-web", "dep:serde_json"] +default = ["std"] +std = [ + "uuid/std", + "serde/std" +] +tracing = ["dep:tracing", "dep:log", "dep:log-mdc", "std"] +metrics = ["dep:metrics", "std"] +backtrace = ["std"] +axum = ["dep:axum", "dep:serde_json", "std"] +actix = ["dep:actix-web", "dep:serde_json", "std"] # Разделили: лёгкая обработка ошибок (sqlx-core) и опциональные миграции (полный sqlx) sqlx = ["dep:sqlx-core"] # maps sqlx_core::Error sqlx-migrate = ["dep:sqlx"] # maps sqlx::migrate::MigrateError -redis = ["dep:redis"] -validator = ["dep:validator"] -serde_json = ["dep:serde_json"] -config = ["dep:config"] -multipart = ["axum"] -tokio = ["dep:tokio"] -reqwest = ["dep:reqwest"] -teloxide = ["dep:teloxide-core"] -telegram-webapp-sdk = ["dep:telegram-webapp-sdk"] -frontend = ["dep:wasm-bindgen", "dep:js-sys", "dep:serde-wasm-bindgen"] -turnkey = [] -tonic = ["dep:tonic"] -openapi = ["dep:utoipa"] +redis = ["dep:redis", "std"] +validator = ["dep:validator", "std"] +serde_json = ["dep:serde_json", "std"] +config = ["dep:config", "std"] +multipart = ["axum", "std"] +tokio = ["dep:tokio", "std"] +reqwest = ["dep:reqwest", "std"] +teloxide = ["dep:teloxide-core", "std"] +telegram-webapp-sdk = ["dep:telegram-webapp-sdk", "std"] +frontend = ["dep:wasm-bindgen", "dep:js-sys", "dep:serde-wasm-bindgen", "std"] +turnkey = ["std"] +tonic = ["dep:tonic", "std"] +openapi = ["dep:utoipa", "std"] [workspace.dependencies] masterror-derive = { version = "0.9.0" } @@ -81,13 +85,16 @@ masterror-template = { version = "0.3.6" } [dependencies] masterror-derive = { version = "0.9" } masterror-template = { workspace = true } -tracing = { version = "0.1", optional = true } +tracing = { version = "0.1", optional = true, default-features = false, features = [ + "attributes", + "std" +] } log = { version = "0.4", optional = true } log-mdc = { version = "0.1", optional = true } metrics = { version = "0.24", optional = true } -serde = { version = "1", features = ["derive"] } -serde_json = { version = "1", optional = true } +serde = { version = "1", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1", optional = true, default-features = false, features = ["std"] } http = "1" sha2 = "0.10" @@ -118,9 +125,7 @@ telegram-webapp-sdk = { version = "0.2", optional = true } wasm-bindgen = { version = "0.2", optional = true } js-sys = { version = "0.3", optional = true } serde-wasm-bindgen = { version = "0.6", optional = true } -uuid = { version = "1", default-features = false, features = [ - "std" -] } +uuid = { version = "1", default-features = false } tonic = { version = "0.12", optional = true } [dev-dependencies] @@ -142,6 +147,7 @@ toml = "0.9" [package.metadata.masterror.readme] feature_order = [ + "std", "axum", "actix", "openapi", @@ -184,6 +190,9 @@ description = "IntoResponse integration with structured JSON bodies" [package.metadata.masterror.readme.features.actix] description = "Actix Web ResponseError and Responder implementations" +[package.metadata.masterror.readme.features.std] +description = "Enable std support (default); required for runtime integrations" + [package.metadata.masterror.readme.features.openapi] description = "Generate utoipa OpenAPI schema for ErrorResponse" diff --git a/README.md b/README.md index 740f243..5829698 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,15 @@ The build script keeps the full feature snippet below in sync with ~~~toml [dependencies] -masterror = { version = "0.21.2", default-features = false } +masterror = { version = "0.22.0", default-features = false } # or with features: -# masterror = { version = "0.21.2", features = [ -# "axum", "actix", "openapi", "serde_json", -# "tracing", "metrics", "backtrace", "sqlx", -# "sqlx-migrate", "reqwest", "redis", "validator", -# "config", "tokio", "multipart", "teloxide", -# "telegram-webapp-sdk", "tonic", "frontend", "turnkey" +# masterror = { version = "0.22.0", features = [ +# "std", "axum", "actix", "openapi", +# "serde_json", "tracing", "metrics", "backtrace", +# "sqlx", "sqlx-migrate", "reqwest", "redis", +# "validator", "config", "tokio", "multipart", +# "teloxide", "telegram-webapp-sdk", "tonic", "frontend", +# "turnkey" # ] } ~~~ @@ -418,4 +419,3 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED"); --- MSRV: **1.90** · License: **MIT OR Apache-2.0** · No `unsafe` - diff --git a/src/app_error/constructors.rs b/src/app_error/constructors.rs index 30abece..aa6240c 100644 --- a/src/app_error/constructors.rs +++ b/src/app_error/constructors.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use alloc::borrow::Cow; use super::core::AppError; use crate::AppErrorKind; diff --git a/src/app_error/context.rs b/src/app_error/context.rs index a3ad334..64a6e82 100644 --- a/src/app_error/context.rs +++ b/src/app_error/context.rs @@ -1,4 +1,5 @@ -use std::{error::Error as StdError, panic::Location}; +use alloc::vec::Vec; +use core::{error::Error as CoreError, panic::Location}; use super::{ core::{AppError, Error, MessageEditPolicy}, @@ -133,7 +134,7 @@ impl Context { pub(crate) fn into_error(mut self, source: E) -> Error where - E: StdError + Send + Sync + 'static + E: CoreError + Send + Sync + 'static { if let Some(location) = self.caller_location { self.fields.push(Field::new( diff --git a/src/app_error/core.rs b/src/app_error/core.rs index 3c791b9..a4b455f 100644 --- a/src/app_error/core.rs +++ b/src/app_error/core.rs @@ -1,3 +1,15 @@ +use alloc::{ + borrow::Cow, + boxed::Box, + string::{String, ToString}, + sync::Arc +}; +use core::{ + error::Error as CoreError, + fmt::{Display, Formatter, Result as FmtResult}, + ops::{Deref, DerefMut}, + sync::atomic::{AtomicBool, Ordering} +}; #[cfg(feature = "backtrace")] use std::{ backtrace::Backtrace, @@ -7,16 +19,6 @@ use std::{ atomic::{AtomicU8, Ordering as AtomicOrdering} } }; -use std::{ - borrow::Cow, - error::Error as StdError, - fmt::{Display, Formatter, Result as FmtResult}, - ops::{Deref, DerefMut}, - sync::{ - Arc, - atomic::{AtomicBool, Ordering} - } -}; #[cfg(feature = "tracing")] use tracing::{Level, event}; @@ -24,6 +26,14 @@ use tracing::{Level, event}; use super::metadata::{Field, FieldRedaction, Metadata}; use crate::{AppCode, AppErrorKind, RetryAdvice}; +#[cfg(feature = "std")] +pub type CapturedBacktrace = std::backtrace::Backtrace; + +#[cfg(not(feature = "std"))] +#[allow(dead_code)] +#[derive(Debug)] +pub enum CapturedBacktrace {} + /// Controls whether the public message may be redacted before exposure. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum MessageEditPolicy { @@ -51,7 +61,7 @@ pub struct ErrorInner { pub retry: Option, /// Optional authentication challenge for `WWW-Authenticate`. pub www_authenticate: Option, - pub source: Option>, + pub source: Option>, #[cfg(feature = "backtrace")] pub backtrace: Option, #[cfg(feature = "backtrace")] @@ -184,11 +194,11 @@ impl Display for Error { } } -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl CoreError for Error { + fn source(&self) -> Option<&(dyn CoreError + 'static)> { self.source .as_deref() - .map(|source| source as &(dyn StdError + 'static)) + .map(|source| source as &(dyn CoreError + 'static)) } } @@ -247,7 +257,7 @@ impl Error { } #[cfg(feature = "backtrace")] - fn capture_backtrace(&self) -> Option<&std::backtrace::Backtrace> { + fn capture_backtrace(&self) -> Option<&CapturedBacktrace> { if let Some(backtrace) = self.backtrace.as_ref() { return Some(backtrace); } @@ -258,18 +268,18 @@ impl Error { } #[cfg(not(feature = "backtrace"))] - fn capture_backtrace(&self) -> Option<&std::backtrace::Backtrace> { + fn capture_backtrace(&self) -> Option<&CapturedBacktrace> { None } #[cfg(feature = "backtrace")] - fn set_backtrace_slot(&mut self, backtrace: std::backtrace::Backtrace) { + fn set_backtrace_slot(&mut self, backtrace: CapturedBacktrace) { self.backtrace = Some(backtrace); self.captured_backtrace = OnceLock::new(); } #[cfg(not(feature = "backtrace"))] - fn set_backtrace_slot(&mut self, _backtrace: std::backtrace::Backtrace) {} + fn set_backtrace_slot(&mut self, _backtrace: CapturedBacktrace) {} pub(crate) fn emit_telemetry(&self) { if self.take_dirty() { @@ -416,7 +426,7 @@ impl Error { /// Attach a source error for diagnostics. #[must_use] - pub fn with_source(mut self, source: impl StdError + Send + Sync + 'static) -> Self { + pub fn with_source(mut self, source: impl CoreError + Send + Sync + 'static) -> Self { self.source = Some(Arc::new(source)); self.mark_dirty(); self @@ -437,7 +447,7 @@ impl Error { /// assert_eq!(Arc::strong_count(&source), 2); /// ``` #[must_use] - pub fn with_source_arc(mut self, source: Arc) -> Self { + pub fn with_source_arc(mut self, source: Arc) -> Self { self.source = Some(source); self.mark_dirty(); self @@ -445,7 +455,7 @@ impl Error { /// Attach a captured backtrace. #[must_use] - pub fn with_backtrace(mut self, backtrace: std::backtrace::Backtrace) -> Self { + pub fn with_backtrace(mut self, backtrace: CapturedBacktrace) -> Self { self.set_backtrace_slot(backtrace); self.mark_dirty(); self @@ -460,13 +470,13 @@ impl Error { /// Borrow the backtrace, capturing it lazily when the `backtrace` feature /// is enabled. #[must_use] - pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> { + pub fn backtrace(&self) -> Option<&CapturedBacktrace> { self.capture_backtrace() } /// Borrow the source if present. #[must_use] - pub fn source_ref(&self) -> Option<&(dyn StdError + Send + Sync + 'static)> { + pub fn source_ref(&self) -> Option<&(dyn CoreError + Send + Sync + 'static)> { self.source.as_deref() } diff --git a/src/app_error/metadata.rs b/src/app_error/metadata.rs index 7735ee1..125f0de 100644 --- a/src/app_error/metadata.rs +++ b/src/app_error/metadata.rs @@ -1,6 +1,5 @@ -use std::{ - borrow::Cow, - collections::BTreeMap, +use alloc::{borrow::Cow, collections::BTreeMap, string::String}; +use core::{ fmt::{Display, Formatter, Result as FmtResult, Write}, net::IpAddr, time::Duration @@ -354,8 +353,8 @@ impl Metadata { impl IntoIterator for Metadata { type Item = Field; - type IntoIter = std::iter::Map< - std::collections::btree_map::IntoIter<&'static str, Field>, + type IntoIter = core::iter::Map< + alloc::collections::btree_map::IntoIter<&'static str, Field>, fn((&'static str, Field)) -> Field >; @@ -371,7 +370,8 @@ impl IntoIterator for Metadata { /// Factories for [`Field`] values. pub mod field { - use std::{borrow::Cow, net::IpAddr, time::Duration}; + use alloc::borrow::Cow; + use core::{net::IpAddr, time::Duration}; #[cfg(feature = "serde_json")] use serde_json::Value as JsonValue; @@ -425,7 +425,7 @@ pub mod field { /// Build a duration metadata field. /// /// ``` - /// use std::time::Duration; + /// use core::time::Duration; /// use masterror::{field, FieldValue}; /// /// let (_, value, _) = field::duration("elapsed", Duration::from_millis(1500)).into_parts(); @@ -439,7 +439,7 @@ pub mod field { /// Build an IP address metadata field. /// /// ``` - /// use std::net::{IpAddr, Ipv4Addr}; + /// use core::net::{IpAddr, Ipv4Addr}; /// use masterror::{field, FieldValue}; /// /// let (_, value, _) = field::ip("peer", IpAddr::from(Ipv4Addr::LOCALHOST)).into_parts(); diff --git a/src/code/app_code.rs b/src/code/app_code.rs index 9afc1f3..37ffc12 100644 --- a/src/code/app_code.rs +++ b/src/code/app_code.rs @@ -1,5 +1,5 @@ -use std::{ - error::Error as StdError, +use core::{ + error::Error as CoreError, fmt::{self, Display}, str::FromStr }; @@ -23,7 +23,7 @@ impl Display for ParseAppCodeError { } } -impl StdError for ParseAppCodeError {} +impl CoreError for ParseAppCodeError {} /// Stable machine-readable error code exposed to clients. /// diff --git a/src/convert.rs b/src/convert.rs index 1dc4966..3915860 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -74,6 +74,8 @@ //! assert!(matches!(err.kind, AppErrorKind::BadRequest)); //! ``` +use alloc::string::String; +#[cfg(feature = "std")] use std::io::Error as IoError; use crate::AppError; @@ -148,6 +150,7 @@ pub use self::tonic::StatusConversionError; /// let app_err: AppError = io_err.into(); /// assert!(matches!(app_err.kind, AppErrorKind::Internal)); /// ``` +#[cfg(feature = "std")] impl From for AppError { fn from(err: IoError) -> Self { AppError::internal(err.to_string()) diff --git a/src/kind.rs b/src/kind.rs index e025012..b0589f0 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -35,68 +35,63 @@ //! assert_eq!(kind.status_code().as_u16(), 404); //! ``` +use core::{ + error::Error as CoreError, + fmt::{self, Display, Formatter} +}; + #[cfg(feature = "axum")] use axum::http::StatusCode; -use crate::Error; - /// Canonical application error taxonomy. /// /// Keep it small, stable, and framework-agnostic. Each variant has a clear, /// documented meaning and a predictable mapping to an HTTP status code. -#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AppErrorKind { // ── Generic, client-visible failures (4xx/5xx) ──────────────────────────── /// Resource does not exist or is not visible to the caller. /// /// Maps to **404 Not Found**. - #[error("Not found")] NotFound, /// Input failed validation (shape, constraints, business rules). /// /// Prefer this over `BadRequest` when you validate structured input. /// Maps to **422 Unprocessable Entity**. - #[error("Validation error")] Validation, /// State conflict with an existing resource or concurrent update. /// /// Typical cases: unique key violation, version mismatch (ETag). /// Maps to **409 Conflict**. - #[error("Conflict")] Conflict, /// Authentication required or failed (missing/invalid credentials). /// /// Maps to **401 Unauthorized**. - #[error("Unauthorized")] Unauthorized, /// Authenticated but not allowed to perform the operation. /// /// Maps to **403 Forbidden**. - #[error("Forbidden")] Forbidden, /// Operation is not implemented or not supported by this deployment. /// /// Maps to **501 Not Implemented**. - #[error("Not implemented")] NotImplemented, /// Unexpected server-side failure not captured by more specific kinds. /// /// Use sparingly; prefer a more precise category when possible. /// Maps to **500 Internal Server Error**. - #[error("Internal server error")] Internal, /// Malformed request or missing required parameters. /// /// Prefer `Validation` for structured input with field-level issues. /// Maps to **400 Bad Request**. - #[error("Bad request")] BadRequest, // ── Domain-specific categories (map conservatively) ─────────────────────── @@ -104,21 +99,18 @@ pub enum AppErrorKind { /// /// Treated as an authentication failure. /// Maps to **401 Unauthorized**. - #[error("Telegram authentication error")] TelegramAuth, /// Provided JWT is invalid (expired, malformed, wrong signature/claims). /// /// Treated as an authentication failure. /// Maps to **401 Unauthorized**. - #[error("Invalid JWT")] InvalidJwt, /// Database-related failure (query, connection, migration, etc.). /// /// Keep driver-specific details out of the public contract. /// Maps to **500 Internal Server Error**. - #[error("Database error")] Database, /// Generic service-layer failure (business logic or internal @@ -126,19 +118,16 @@ pub enum AppErrorKind { /// /// Use when no more specific category applies. /// Maps to **500 Internal Server Error**. - #[error("Service error")] Service, /// Configuration error (missing/invalid environment or runtime config). /// /// Maps to **500 Internal Server Error**. - #[error("Configuration error")] Config, /// Failure in the Turnkey subsystem/integration. /// /// Maps to **500 Internal Server Error**. - #[error("Turnkey error")] Turnkey, // ── Infrastructure / network ────────────────────────────────────────────── @@ -146,62 +135,86 @@ pub enum AppErrorKind { /// /// Typically returned by timeouts around I/O or remote calls. /// Maps to **504 Gateway Timeout**. - #[error("Operation timed out")] Timeout, /// Network-level error (DNS, connect, TLS, request build). /// /// For upstream HTTP status failures use `ExternalApi` instead. /// Maps to **503 Service Unavailable**. - #[error("Network error")] Network, /// Client exceeded rate limits or quota. /// /// Maps to **429 Too Many Requests**. - #[error("Rate limit exceeded")] RateLimited, /// External dependency is unavailable or degraded. /// /// Examples: cache down, message broker unreachable, third-party outage. /// Maps to **503 Service Unavailable**. - #[error("External dependency unavailable")] DependencyUnavailable, // ── Serialization / external API / infra subsystems ─────────────────────── /// Failed to serialize data (encode). /// /// Maps to **500 Internal Server Error**. - #[error("Serialization error")] Serialization, /// Failed to deserialize data (decode). /// /// Maps to **500 Internal Server Error**. - #[error("Deserialization error")] Deserialization, /// Upstream API returned an error or the call failed at protocol level. /// /// Use `Network` for connect/build failures; use this for HTTP status /// errors. Maps to **500 Internal Server Error** by default. - #[error("External API error")] ExternalApi, /// Queue processing failure (publish/consume/ack). /// /// Maps to **500 Internal Server Error**. - #[error("Queue processing error")] Queue, /// Cache subsystem failure (read/write/encoding). /// /// Maps to **500 Internal Server Error**. - #[error("Cache error")] Cache } +impl Display for AppErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let label = match self { + Self::NotFound => "Not found", + Self::Validation => "Validation error", + Self::Conflict => "Conflict", + Self::Unauthorized => "Unauthorized", + Self::Forbidden => "Forbidden", + Self::NotImplemented => "Not implemented", + Self::Internal => "Internal server error", + Self::BadRequest => "Bad request", + Self::TelegramAuth => "Telegram authentication error", + Self::InvalidJwt => "Invalid JWT", + Self::Database => "Database error", + Self::Service => "Service error", + Self::Config => "Configuration error", + Self::Turnkey => "Turnkey error", + Self::Timeout => "Operation timed out", + Self::Network => "Network error", + Self::RateLimited => "Rate limit exceeded", + Self::DependencyUnavailable => "External dependency unavailable", + Self::Serialization => "Serialization error", + Self::Deserialization => "Deserialization error", + Self::ExternalApi => "External API error", + Self::Queue => "Queue processing error", + Self::Cache => "Cache error" + }; + f.write_str(label) + } +} + +impl CoreError for AppErrorKind {} + impl AppErrorKind { /// Framework-agnostic mapping to an HTTP status code (`u16`). /// diff --git a/src/lib.rs b/src/lib.rs index dff5e40..f98b7f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,14 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![forbid(unsafe_code)] +#![deny(rustdoc::broken_intra_doc_links)] +#![warn( + missing_docs, + missing_debug_implementations, + rust_2018_idioms, + clippy::all +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + //! Framework-agnostic application error types for backend services. //! //! # Overview @@ -315,16 +326,7 @@ //! //! at your option. -#![forbid(unsafe_code)] -#![deny(rustdoc::broken_intra_doc_links)] -#![warn( - missing_docs, - missing_debug_implementations, - rust_2018_idioms, - clippy::all -)] -// Show feature-gated items on docs.rs -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +extern crate alloc; mod app_error; mod code; diff --git a/src/response/core.rs b/src/response/core.rs index 6b429f4..8240c37 100644 --- a/src/response/core.rs +++ b/src/response/core.rs @@ -99,3 +99,4 @@ impl ErrorResponse { crate::response::internal::ErrorResponseFormatter::new(self) } } +use alloc::{format, string::String}; diff --git a/src/response/details.rs b/src/response/details.rs index faa699e..3de1226 100644 --- a/src/response/details.rs +++ b/src/response/details.rs @@ -63,3 +63,6 @@ impl ErrorResponse { Ok(self.with_details_json(details)) } } +use alloc::string::String; +#[cfg(feature = "serde_json")] +use alloc::string::ToString; diff --git a/src/response/internal.rs b/src/response/internal.rs index 1cd90bd..cc30758 100644 --- a/src/response/internal.rs +++ b/src/response/internal.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Debug, Display, Formatter}; +use core::fmt::{self, Debug, Display, Formatter}; use super::{core::ErrorResponse, problem_json::ProblemJson}; diff --git a/src/response/legacy.rs b/src/response/legacy.rs index 98c3383..a11489a 100644 --- a/src/response/legacy.rs +++ b/src/response/legacy.rs @@ -23,3 +23,4 @@ impl ErrorResponse { }) } } +use alloc::string::String; diff --git a/src/response/mapping.rs b/src/response/mapping.rs index c0d2302..ecd3f4d 100644 --- a/src/response/mapping.rs +++ b/src/response/mapping.rs @@ -1,4 +1,5 @@ -use std::fmt::{Display, Formatter, Result as FmtResult}; +use alloc::string::ToString; +use core::fmt::{Display, Formatter, Result as FmtResult}; use super::core::ErrorResponse; use crate::AppError; diff --git a/src/response/metadata.rs b/src/response/metadata.rs index 56b6b74..cf05362 100644 --- a/src/response/metadata.rs +++ b/src/response/metadata.rs @@ -1,4 +1,5 @@ -use std::time::Duration; +use alloc::string::String; +use core::time::Duration; use super::core::{ErrorResponse, RetryAdvice}; @@ -24,7 +25,7 @@ impl ErrorResponse { /// # Examples /// /// ```rust - /// use std::time::Duration; + /// use core::time::Duration; /// /// use masterror::{AppCode, ErrorResponse}; /// diff --git a/src/response/problem_json.rs b/src/response/problem_json.rs index cb4fef0..9478f50 100644 --- a/src/response/problem_json.rs +++ b/src/response/problem_json.rs @@ -1,4 +1,10 @@ -use std::{borrow::Cow, collections::BTreeMap, fmt::Write, net::IpAddr}; +use alloc::{ + borrow::Cow, + collections::BTreeMap, + string::{String, ToString}, + vec::Vec +}; +use core::{fmt::Write, net::IpAddr}; use http::StatusCode; use serde::Serialize; diff --git a/src/result_ext.rs b/src/result_ext.rs index 31d5f6c..abf162e 100644 --- a/src/result_ext.rs +++ b/src/result_ext.rs @@ -1,4 +1,4 @@ -use std::error::Error as StdError; +use core::error::Error as CoreError; use crate::app_error::{Context, Error}; @@ -31,13 +31,13 @@ pub trait ResultExt { #[allow(clippy::result_large_err)] fn ctx(self, build: impl FnOnce() -> Context) -> Result where - E: StdError + Send + Sync + 'static; + E: CoreError + Send + Sync + 'static; } impl ResultExt for Result { fn ctx(self, build: impl FnOnce() -> Context) -> Result where - E: StdError + Send + Sync + 'static + E: CoreError + Send + Sync + 'static { self.map_err(|err| build().into_error(err)) } diff --git a/tests/ui/app_error/fail/enum_missing_variant.stderr b/tests/ui/app_error/fail/enum_missing_variant.stderr index d000de1..bbc297c 100644 --- a/tests/ui/app_error/fail/enum_missing_variant.stderr +++ b/tests/ui/app_error/fail/enum_missing_variant.stderr @@ -1,8 +1,9 @@ error: all variants must use #[app_error(...)] to derive AppError conversion --> tests/ui/app_error/fail/enum_missing_variant.rs:8:5 | -8 | #[error("without")] - | ^ +8 | / #[error("without")] +9 | | Without, + | |___________^ warning: unused import: `AppErrorKind` --> tests/ui/app_error/fail/enum_missing_variant.rs:1:17 @@ -10,4 +11,4 @@ warning: unused import: `AppErrorKind` 1 | use masterror::{AppErrorKind, Error}; | ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/app_error/fail/missing_code.stderr b/tests/ui/app_error/fail/missing_code.stderr index 70ccade..4f02301 100644 --- a/tests/ui/app_error/fail/missing_code.stderr +++ b/tests/ui/app_error/fail/missing_code.stderr @@ -2,7 +2,7 @@ error: AppCode conversion requires `code = ...` in #[app_error(...)] --> tests/ui/app_error/fail/missing_code.rs:9:5 | 9 | #[app_error(kind = AppErrorKind::Service)] - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused imports: `AppCode` and `AppErrorKind` --> tests/ui/app_error/fail/missing_code.rs:1:17 @@ -10,4 +10,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Error}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/app_error/fail/missing_kind.stderr b/tests/ui/app_error/fail/missing_kind.stderr index c615e98..021c135 100644 --- a/tests/ui/app_error/fail/missing_kind.stderr +++ b/tests/ui/app_error/fail/missing_kind.stderr @@ -2,4 +2,4 @@ error: missing `kind = ...` in #[app_error(...)] --> tests/ui/app_error/fail/missing_kind.rs:5:1 | 5 | #[app_error(message)] - | ^ + | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/formatter/fail/duplicate_fmt.stderr b/tests/ui/formatter/fail/duplicate_fmt.stderr index 5b08225..5b8f363 100644 --- a/tests/ui/formatter/fail/duplicate_fmt.stderr +++ b/tests/ui/formatter/fail/duplicate_fmt.stderr @@ -2,4 +2,4 @@ error: duplicate `fmt` handler specified --> tests/ui/formatter/fail/duplicate_fmt.rs:4:36 | 4 | #[error(fmt = crate::format_error, fmt = crate::format_error)] - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/formatter/fail/implicit_after_named.stderr b/tests/ui/formatter/fail/implicit_after_named.stderr index d416399..be76742 100644 --- a/tests/ui/formatter/fail/implicit_after_named.stderr +++ b/tests/ui/formatter/fail/implicit_after_named.stderr @@ -8,4 +8,5 @@ error: multiple unused formatting arguments | argument never used | argument never used | + = note: consider adding 2 format specifiers = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/formatter/fail/unsupported_flag.stderr b/tests/ui/formatter/fail/unsupported_flag.stderr index d7acdb1..b8bf229 100644 --- a/tests/ui/formatter/fail/unsupported_flag.stderr +++ b/tests/ui/formatter/fail/unsupported_flag.stderr @@ -1,5 +1,5 @@ error: placeholder spanning bytes 0..11 uses an unsupported formatter - --> tests/ui/formatter/fail/unsupported_flag.rs:4:9 + --> tests/ui/formatter/fail/unsupported_flag.rs:4:10 | 4 | #[error("{value:##x}")] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^ diff --git a/tests/ui/formatter/fail/unsupported_formatter.stderr b/tests/ui/formatter/fail/unsupported_formatter.stderr index 5869420..a6a40c2 100644 --- a/tests/ui/formatter/fail/unsupported_formatter.stderr +++ b/tests/ui/formatter/fail/unsupported_formatter.stderr @@ -1,5 +1,5 @@ error: placeholder spanning bytes 0..9 uses an unsupported formatter - --> tests/ui/formatter/fail/unsupported_formatter.rs:4:9 + --> tests/ui/formatter/fail/unsupported_formatter.rs:4:10 | 4 | #[error("{value:y}")] - | ^^^^^^^^^^^ + | ^^^^^^^^^ diff --git a/tests/ui/formatter/fail/uppercase_binary.stderr b/tests/ui/formatter/fail/uppercase_binary.stderr index bbe04b4..3d332c7 100644 --- a/tests/ui/formatter/fail/uppercase_binary.stderr +++ b/tests/ui/formatter/fail/uppercase_binary.stderr @@ -1,5 +1,5 @@ error: placeholder spanning bytes 0..9 uses an unsupported formatter - --> tests/ui/formatter/fail/uppercase_binary.rs:4:9 + --> tests/ui/formatter/fail/uppercase_binary.rs:4:10 | 4 | #[error("{value:B}")] - | ^^^^^^^^^^^ + | ^^^^^^^^^ diff --git a/tests/ui/formatter/fail/uppercase_pointer.stderr b/tests/ui/formatter/fail/uppercase_pointer.stderr index 2c30e71..0bd10fa 100644 --- a/tests/ui/formatter/fail/uppercase_pointer.stderr +++ b/tests/ui/formatter/fail/uppercase_pointer.stderr @@ -1,5 +1,5 @@ error: placeholder spanning bytes 0..9 uses an unsupported formatter - --> tests/ui/formatter/fail/uppercase_pointer.rs:4:9 + --> tests/ui/formatter/fail/uppercase_pointer.rs:4:10 | 4 | #[error("{value:P}")] - | ^^^^^^^^^^^ + | ^^^^^^^^^ diff --git a/tests/ui/masterror/fail/duplicate_attr.stderr b/tests/ui/masterror/fail/duplicate_attr.stderr index c3fb86b..113a10d 100644 --- a/tests/ui/masterror/fail/duplicate_attr.stderr +++ b/tests/ui/masterror/fail/duplicate_attr.stderr @@ -10,4 +10,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Masterror}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/duplicate_telemetry.stderr b/tests/ui/masterror/fail/duplicate_telemetry.stderr index b331baa..9ada290 100644 --- a/tests/ui/masterror/fail/duplicate_telemetry.stderr +++ b/tests/ui/masterror/fail/duplicate_telemetry.stderr @@ -10,4 +10,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Masterror}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/empty_redact.stderr b/tests/ui/masterror/fail/empty_redact.stderr index b2658a1..fd151cc 100644 --- a/tests/ui/masterror/fail/empty_redact.stderr +++ b/tests/ui/masterror/fail/empty_redact.stderr @@ -10,4 +10,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Masterror}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/enum_missing_variant.stderr b/tests/ui/masterror/fail/enum_missing_variant.stderr index 83d517f..5a25e12 100644 --- a/tests/ui/masterror/fail/enum_missing_variant.stderr +++ b/tests/ui/masterror/fail/enum_missing_variant.stderr @@ -1,8 +1,9 @@ error: all variants must use #[masterror(...)] to derive masterror::Error conversion --> tests/ui/masterror/fail/enum_missing_variant.rs:8:5 | -8 | #[error("missing")] - | ^ +8 | / #[error("missing")] +9 | | Missing + | |___________^ warning: unused imports: `AppCode` and `AppErrorKind` --> tests/ui/masterror/fail/enum_missing_variant.rs:1:17 @@ -10,4 +11,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Masterror}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/missing_category.stderr b/tests/ui/masterror/fail/missing_category.stderr index f929951..bdadf45 100644 --- a/tests/ui/masterror/fail/missing_category.stderr +++ b/tests/ui/masterror/fail/missing_category.stderr @@ -2,7 +2,7 @@ error: missing `category = ...` in #[masterror(...)] --> tests/ui/masterror/fail/missing_category.rs:5:1 | 5 | #[masterror(code = AppCode::Internal)] - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused import: `AppCode` --> tests/ui/masterror/fail/missing_category.rs:1:17 @@ -10,4 +10,4 @@ warning: unused import: `AppCode` 1 | use masterror::{AppCode, Masterror}; | ^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/missing_code.stderr b/tests/ui/masterror/fail/missing_code.stderr index 34abc91..037fac8 100644 --- a/tests/ui/masterror/fail/missing_code.stderr +++ b/tests/ui/masterror/fail/missing_code.stderr @@ -2,7 +2,7 @@ error: missing `code = ...` in #[masterror(...)] --> tests/ui/masterror/fail/missing_code.rs:5:1 | 5 | #[masterror(category = AppErrorKind::Internal)] - | ^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused import: `AppErrorKind` --> tests/ui/masterror/fail/missing_code.rs:1:17 @@ -10,4 +10,4 @@ warning: unused import: `AppErrorKind` 1 | use masterror::{AppErrorKind, Masterror}; | ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default diff --git a/tests/ui/masterror/fail/unknown_option.stderr b/tests/ui/masterror/fail/unknown_option.stderr index d579838..1822edf 100644 --- a/tests/ui/masterror/fail/unknown_option.stderr +++ b/tests/ui/masterror/fail/unknown_option.stderr @@ -10,4 +10,4 @@ warning: unused imports: `AppCode` and `AppErrorKind` 1 | use masterror::{AppCode, AppErrorKind, Masterror}; | ^^^^^^^ ^^^^^^^^^^^^ | - = note: `#[warn(unused_imports)]` on by default + = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default