From d7251805a0e4387435a671b412a608ca853a1897 Mon Sep 17 00:00:00 2001 From: RA <70325462+RAprogramm@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:18:23 +0700 Subject: [PATCH] Fix tonic status conversion for Rust 1.90 --- CHANGELOG.md | 7 +++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 142 +------------------------------------------ src/convert.rs | 3 + src/convert/tonic.rs | 45 ++++---------- src/lib.rs | 2 +- 7 files changed, 29 insertions(+), 174 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91611e9..1278292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.20.6] - 2025-10-06 + +### Fixed +- Restored compilation on Rust 1.90+ by aliasing the infallible gRPC + conversion error to `core::convert::Infallible` and re-exporting it without + exposing the private `convert::tonic` module. + ## [0.20.5] - 2025-10-05 ### Changed diff --git a/Cargo.lock b/Cargo.lock index d6d73a1..e4be860 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1727,7 +1727,7 @@ dependencies = [ [[package]] name = "masterror" -version = "0.20.5" +version = "0.20.6" dependencies = [ "actix-web", "axum 0.8.4", diff --git a/Cargo.toml b/Cargo.toml index 9fd4521..7a09469 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.20.5" +version = "0.20.6" rust-version = "1.90" edition = "2024" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 2b5e531..8740547 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ The build script keeps the full feature snippet below in sync with ~~~toml [dependencies] -masterror = { version = "0.20.5", default-features = false } +masterror = { version = "0.20.6", default-features = false } # or with features: -# masterror = { version = "0.20.5", features = [ +# masterror = { version = "0.20.6", features = [ # "axum", "actix", "openapi", "serde_json", # "tracing", "metrics", "backtrace", "sqlx", # "sqlx-migrate", "reqwest", "redis", "validator", @@ -85,7 +85,6 @@ masterror = { version = "0.20.5", default-features = false } ---
- Quick start Create an error: @@ -378,143 +377,8 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED"); ~~~
-
- Web framework integrations - -
- Axum - -~~~rust -// features = ["axum", "serde_json"] -... - assert!(payload.is_object()); - - #[cfg(target_arch = "wasm32")] - { - if let Err(console_err) = err.log_to_browser_console() { - eprintln!( - "failed to log to browser console: {:?}", - console_err.context() - ); - } - } - - Ok(()) -} -~~~ - -- On non-WASM targets `log_to_browser_console` returns - `BrowserConsoleError::UnsupportedTarget`. -- `BrowserConsoleError::context()` exposes optional browser diagnostics for - logging/telemetry when console logging fails. - -
- -
- -
- Feature flags - -- `axum` — IntoResponse integration with structured JSON bodies -- `actix` — Actix Web ResponseError and Responder implementations -- `openapi` — Generate utoipa OpenAPI schema for ErrorResponse -- `serde_json` — Attach structured JSON details to AppError -- `tracing` — Emit structured tracing events when errors are constructed -- `metrics` — Increment `error_total{code,category}` counter for each AppError -- `backtrace` — Capture lazy `Backtrace` snapshots when telemetry is flushed -- `sqlx` — Classify sqlx_core::Error variants into AppError kinds -- `sqlx-migrate` — Map sqlx::migrate::MigrateError into AppError (Database) -- `reqwest` — Classify reqwest::Error as timeout/network/external API -- `redis` — Map redis::RedisError into cache-aware AppError -- `validator` — Convert validator::ValidationErrors into validation failures -- `config` — Propagate config::ConfigError as configuration issues -- `tokio` — Classify tokio::time::error::Elapsed as timeout -- `multipart` — Handle axum multipart extraction errors -- `teloxide` — Convert teloxide_core::RequestError into domain errors -- `telegram-webapp-sdk` — Surface Telegram WebApp validation failures -- `tonic` — Convert AppError into tonic::Status with redaction -- `frontend` — Log to the browser console and convert to JsValue on WASM -- `turnkey` — Ship Turnkey-specific error taxonomy and conversions - -
- -
- Conversions - -- `std::io::Error` → Internal -- `String` → BadRequest -- `sqlx::Error` → NotFound/Database -- `redis::RedisError` → Cache -- `reqwest::Error` → Timeout/Network/ExternalApi -- `axum::extract::multipart::MultipartError` → BadRequest -- `validator::ValidationErrors` → Validation -- `config::ConfigError` → Config -- `tokio::time::error::Elapsed` → Timeout -- `teloxide_core::RequestError` → RateLimited/Network/ExternalApi/Deserialization/Internal -- `telegram_webapp_sdk::utils::validate_init_data::ValidationError` → TelegramAuth - -
- -
- Typical setups - -Minimal core: -~~~toml -masterror = { version = "0.20.5", default-features = false } -~~~ - -API (Axum + JSON + deps): - -~~~toml -masterror = { version = "0.20.5", features = [ - "axum", "serde_json", "openapi", - "sqlx", "reqwest", "redis", "validator", "config", "tokio" -] } -~~~ - -API (Actix + JSON + deps): - -~~~toml -masterror = { version = "0.20.5", features = [ - "actix", "serde_json", "openapi", - "sqlx", "reqwest", "redis", "validator", "config", "tokio" -] } -~~~ - -
- -
- Turnkey - -~~~rust -// features = ["turnkey"] -use masterror::turnkey::{classify_turnkey_error, TurnkeyError, TurnkeyErrorKind}; -use masterror::{AppError, AppErrorKind}; - -// Classify a raw SDK/provider error -let kind = classify_turnkey_error("429 Too Many Requests"); -assert!(matches!(kind, TurnkeyErrorKind::RateLimited)); - -// Wrap into AppError -let e = TurnkeyError::new(TurnkeyErrorKind::RateLimited, "throttled upstream"); -let app: AppError = e.into(); -assert_eq!(app.kind, AppErrorKind::RateLimited); -~~~ - -
- -
- Migration 0.2 → 0.3 - -- Use `ErrorResponse::new(status, AppCode::..., "msg")` instead of legacy -- New helpers: `.with_retry_after_secs`, `.with_retry_after_duration`, `.with_www_authenticate` -- `ErrorResponse::new_legacy` is temporary shim - -
- -
- Versioning & MSRV +### Further resources - Explore the [error-handling wiki](docs/wiki/index.md) for step-by-step guides, comparisons with `thiserror`/`anyhow`, and troubleshooting recipes. diff --git a/src/convert.rs b/src/convert.rs index bb409a5..1dc4966 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -130,6 +130,9 @@ mod telegram_webapp_sdk; #[cfg_attr(docsrs, doc(cfg(feature = "tonic")))] mod tonic; +#[cfg(feature = "tonic")] +pub use self::tonic::StatusConversionError; + /// Map `std::io::Error` to an internal application error. /// /// Rationale: I/O failures are infrastructure-level and should not leak diff --git a/src/convert/tonic.rs b/src/convert/tonic.rs index 07e4b87..9691f6f 100644 --- a/src/convert/tonic.rs +++ b/src/convert/tonic.rs @@ -12,13 +12,14 @@ //! ## Example //! //! ```rust,ignore -//! use masterror::{AppError, AppErrorKind}; +//! use masterror::AppError; //! //! let status = tonic::Status::from(AppError::not_found("missing")); //! assert_eq!(status.code(), tonic::Code::NotFound); //! ``` -use std::{borrow::Cow, fmt}; +use core::convert::Infallible; +use std::borrow::Cow; use tonic::{ Code, Status, @@ -32,37 +33,25 @@ use crate::{ mapping_for_code }; -/// Error returned when converting [`Error`] into [`Status`] fails. +/// Error alias retained for backwards compatibility with 0.20 conversions. /// -/// This type is never constructed in practice because the conversion is -/// guaranteed to succeed. It exists solely to preserve the `TryFrom` API in -/// addition to the infallible [`From`] conversion. +/// Since Rust 1.90 the standard library implements [`TryFrom`] for every +/// [`Into`] conversion with [`core::convert::Infallible`] as the error type. +/// Tonic conversions are therefore guaranteed to succeed, and this alias keeps +/// the historic [`StatusConversionError`] name available for downstream APIs. /// /// # Examples /// ```rust,ignore /// use masterror::{AppError, StatusConversionError}; /// use tonic::{Code, Status}; /// -/// fn convert() -> Result { -/// Status::try_from(AppError::not_found("missing")) -/// } -/// -/// # fn main() -> Result<(), StatusConversionError> { -/// let status = convert()?; +/// let status: Result = Status::try_from( +/// AppError::not_found("missing") +/// ); +/// let status = status.expect("conversion cannot fail"); /// assert_eq!(status.code(), Code::NotFound); -/// # Ok(()) -/// # } /// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct StatusConversionError; - -impl fmt::Display for StatusConversionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("conversion to tonic::Status cannot fail") - } -} - -impl std::error::Error for StatusConversionError {} +pub type StatusConversionError = Infallible; impl From for Status { fn from(error: Error) -> Self { @@ -70,14 +59,6 @@ impl From for Status { } } -impl TryFrom for Status { - type Error = StatusConversionError; - - fn try_from(error: Error) -> Result { - Ok(Status::from(error)) - } -} - fn status_from_error(error: &Error) -> Status { error.emit_telemetry(); diff --git a/src/lib.rs b/src/lib.rs index 956876b..b91d32d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -366,4 +366,4 @@ pub use result_ext::ResultExt; #[cfg(feature = "tonic")] #[cfg_attr(docsrs, doc(cfg(feature = "tonic")))] -pub use crate::convert::tonic::StatusConversionError; +pub use crate::convert::StatusConversionError;