From 451f4236990026db22775402a098d0ebea92d1f0 Mon Sep 17 00:00:00 2001 From: RA <70325462+RAprogramm@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:56:19 +0700 Subject: [PATCH] feat(telegram): expand validation error coverage --- CHANGELOG.md | 5 ++ Cargo.lock | 125 +++++++++++++++++++++++++++++ Cargo.toml | 4 +- README.md | 11 +-- src/convert.rs | 4 + src/convert/telegram_webapp_sdk.rs | 76 ++++++++++++++++++ src/lib.rs | 3 + 7 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 src/convert/telegram_webapp_sdk.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8341cc8..3a47f16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ All notable changes to this project will be documented in this file. ## [Unreleased] + +## [0.3.4] - 2025-09-12 ### Added - `ErrorResponse::with_retry_after_duration` helper for specifying retry advice via `Duration`. +- Conversion from `telegram_webapp_sdk::utils::validate_init_data::ValidationError` into `AppError` (feature `telegram-webapp-sdk`). ### Changed - `AppError::log` now includes the stable `code` field alongside `kind`. @@ -18,6 +21,7 @@ All notable changes to this project will be documented in this file. - Added Axum test asserting `MultipartError` becomes `AppErrorKind::BadRequest` and preserves the message. - Expanded Actix test to check JSON body and `Retry-After`/`WWW-Authenticate` headers. - Covered fallback classification of unknown messages as `TurnkeyErrorKind::Service`. +- Expanded coverage of `telegram_webapp_sdk` mapping across all `ValidationError` variants. ## [0.3.3] - 2025-09-11 ### Added @@ -95,6 +99,7 @@ All notable changes to this project will be documented in this file. - **MSRV:** 1.89 - **No unsafe:** the crate forbids `unsafe`. +[0.3.4]: https://github.com/RAprogramm/masterror/releases/tag/v0.3.4 [0.3.3]: https://github.com/RAprogramm/masterror/releases/tag/v0.3.3 [0.3.2]: https://github.com/RAprogramm/masterror/releases/tag/v0.3.2 [0.3.1]: https://github.com/RAprogramm/masterror/releases/tag/v0.3.1 diff --git a/Cargo.lock b/Cargo.lock index 49d41d4..e671fda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,33 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.20.11" @@ -621,6 +648,30 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -677,6 +728,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flume" version = "0.11.1" @@ -876,6 +933,12 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha256" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" + [[package]] name = "home" version = "0.5.11" @@ -1261,6 +1324,18 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "masterror" version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7c3a243a6f697e05d0b971c22d0ac029b9080c20b2bbc5f4a3f43ea6024a60" +dependencies = [ + "http 1.3.1", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "masterror" +version = "0.3.4" dependencies = [ "actix-web", "axum", @@ -1271,6 +1346,7 @@ dependencies = [ "serde", "serde_json", "sqlx", + "telegram-webapp-sdk", "thiserror", "tokio", "tracing", @@ -1833,6 +1909,15 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1851,6 +1936,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -1871,6 +1962,17 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.219" @@ -2260,6 +2362,29 @@ dependencies = [ "syn", ] +[[package]] +name = "telegram-webapp-sdk" +version = "0.1.0" +source = "git+https://github.com/RAprogramm/telegram-webapp-sdk?rev=0c5a1d557e1cefe2b37a190c9359be05fe48d41a#0c5a1d557e1cefe2b37a190c9359be05fe48d41a" +dependencies = [ + "base64 0.21.7", + "ed25519-dalek", + "hex", + "hmac-sha256", + "js-sys", + "masterror 0.3.3", + "once_cell", + "percent-encoding", + "serde", + "serde-wasm-bindgen", + "serde_json", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "thiserror" version = "2.0.16" diff --git a/Cargo.toml b/Cargo.toml index 78c7536..ff5a601 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "masterror" -version = "0.3.3" +version = "0.3.4" rust-version = "1.89" edition = "2024" description = "Application error types and response mapping" @@ -24,6 +24,7 @@ config = ["dep:config"] # config::ConfigError -> AppError multipart = ["axum"] tokio = ["dep:tokio"] reqwest = ["dep:reqwest"] +telegram-webapp-sdk = ["dep:telegram-webapp-sdk"] turnkey = [] openapi = ["dep:utoipa"] @@ -54,6 +55,7 @@ config = { version = "0.15", optional = true } utoipa = { version = "5.3", optional = true } tokio = { version = "1", optional = true, features = ["time"] } reqwest = { version = "0.12", optional = true, default-features = false } +telegram-webapp-sdk = { git = "https://github.com/RAprogramm/telegram-webapp-sdk", rev = "0c5a1d557e1cefe2b37a190c9359be05fe48d41a", optional = true } [dev-dependencies] serde_json = "1" diff --git a/README.md b/README.md index f51fbb7..1153e60 100644 --- a/README.md +++ b/README.md @@ -169,11 +169,11 @@ utoipa = "5"
Feature flags -- `axum` — IntoResponse -- `actix` — ResponseError/Responder -- `openapi` — utoipa schema -- `serde_json` — JSON details -- `sqlx`, `redis`, `reqwest`, `validator`, `config`, `tokio`, `multipart` +- `axum` — IntoResponse +- `actix` — ResponseError/Responder +- `openapi` — utoipa schema +- `serde_json` — JSON details +- `sqlx`, `redis`, `reqwest`, `validator`, `config`, `tokio`, `multipart`, `telegram-webapp-sdk` - `turnkey` — domain taxonomy and conversions for Turnkey errors
@@ -190,6 +190,7 @@ utoipa = "5" - `validator::ValidationErrors` → Validation - `config::ConfigError` → Config - `tokio::time::error::Elapsed` → Timeout +- `telegram_webapp_sdk::utils::validate_init_data::ValidationError` → TelegramAuth diff --git a/src/convert.rs b/src/convert.rs index c712845..52f8c42 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -113,6 +113,10 @@ mod tokio; #[cfg_attr(docsrs, doc(cfg(feature = "validator")))] mod validator; +#[cfg(feature = "telegram-webapp-sdk")] +#[cfg_attr(docsrs, doc(cfg(feature = "telegram-webapp-sdk")))] +mod telegram_webapp_sdk; + /// 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/telegram_webapp_sdk.rs b/src/convert/telegram_webapp_sdk.rs new file mode 100644 index 0000000..19d7439 --- /dev/null +++ b/src/convert/telegram_webapp_sdk.rs @@ -0,0 +1,76 @@ +//! Conversion from +//! [`telegram_webapp_sdk::utils::validate_init_data::ValidationError`] into +//! [`AppError`]. +//! +//! Enabled with the `telegram-webapp-sdk` feature flag. +//! +//! ## Mapping +//! +//! All [`ValidationError`] variants are mapped to `AppErrorKind::TelegramAuth` +//! and the original error text is preserved in the message. +//! +//! ## Rationale +//! +//! Failing to validate Telegram `initData` indicates an authentication problem +//! with the incoming request. Mapping to `TelegramAuth` keeps this distinction +//! explicit and allows callers to handle it separately from generic bad +//! requests. +//! +//! ## Example +//! +//! ```rust +//! # #[cfg(feature = "telegram-webapp-sdk")] +//! # { +//! use masterror::{AppError, AppErrorKind}; +//! use telegram_webapp_sdk::utils::validate_init_data::ValidationError; +//! +//! fn convert(err: ValidationError) -> AppError { +//! err.into() +//! } +//! +//! let e = convert(ValidationError::SignatureMismatch); +//! assert!(matches!(e.kind, AppErrorKind::TelegramAuth)); +//! assert_eq!(e.message.as_deref(), Some("signature mismatch")); +//! # } +//! ``` + +#[cfg(feature = "telegram-webapp-sdk")] +use telegram_webapp_sdk::utils::validate_init_data::ValidationError; + +#[cfg(feature = "telegram-webapp-sdk")] +use crate::AppError; + +/// Map [`ValidationError`] into an [`AppError`] with kind `TelegramAuth`. +#[cfg(feature = "telegram-webapp-sdk")] +#[cfg_attr(docsrs, doc(cfg(feature = "telegram-webapp-sdk")))] +impl From for AppError { + fn from(err: ValidationError) -> Self { + AppError::telegram_auth(err.to_string()) + } +} + +#[cfg(all(test, feature = "telegram-webapp-sdk"))] +mod tests { + use telegram_webapp_sdk::utils::validate_init_data::ValidationError; + + use super::*; + use crate::AppErrorKind; + + #[test] + fn all_variants_map_to_telegram_auth_and_preserve_message() { + let cases = vec![ + ValidationError::MissingField("hash"), + ValidationError::InvalidEncoding, + ValidationError::InvalidSignatureEncoding, + ValidationError::SignatureMismatch, + ValidationError::InvalidPublicKey, + ]; + + for case in cases { + let msg = case.to_string(); + let app: AppError = case.into(); + assert!(matches!(app.kind, AppErrorKind::TelegramAuth)); + assert_eq!(app.message.as_deref(), Some(msg.as_str())); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f71625a..de3cb9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,9 @@ //! - `config` — `From` mapping //! - `tokio` — `From` mapping //! - `reqwest` — `From` mapping +//! - `telegram-webapp-sdk` — +//! `From` +//! mapping //! - `serde_json` — support for structured JSON details in [`ErrorResponse`]; //! also pulled transitively by `axum` //! - `multipart` — compatibility flag for Axum multipart