diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8341cc8..1767e62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### 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`.
diff --git a/Cargo.lock b/Cargo.lock
index 49d41d4..9521552 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"
@@ -1271,6 +1334,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
+ "telegram-webapp-sdk",
"thiserror",
"tokio",
"tracing",
@@ -1278,6 +1342,18 @@ dependencies = [
"validator",
]
+[[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 = "matchit"
version = "0.8.4"
@@ -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 (registry+https://github.com/rust-lang/crates.io-index)",
+ "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..c5cdf04 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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..6c2e0d7
--- /dev/null
+++ b/src/convert/telegram_webapp_sdk.rs
@@ -0,0 +1,60 @@
+//! 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,ignore
+//! 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));
+//! ```
+
+#[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 validation_error_maps_to_telegram_auth() {
+ let err: AppError = ValidationError::SignatureMismatch.into();
+ assert!(matches!(err.kind, AppErrorKind::TelegramAuth));
+ }
+}
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