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;