Skip to main content

secure_gate/
error.rs

1//! Centralized error types for the secure-gate crate.
2//!
3//! # Error types
4//!
5//! | Type | Produced by | Feature |
6//! |------|------------|---------|
7//! | [`FromSliceError`] | [`Fixed::try_from(&[u8])`](crate::Fixed) | Always |
8//! | [`HexError`] | [`Fixed::try_from_hex`](crate::Fixed::try_from_hex), [`FromHexStr`](crate::FromHexStr) | `encoding-hex` |
9//! | [`Base64Error`] | [`Fixed::try_from_base64url`](crate::Fixed::try_from_base64url), [`FromBase64UrlStr`](crate::FromBase64UrlStr) | `encoding-base64` |
10//! | [`Bech32Error`] | `try_from_bech32*`, [`FromBech32Str`](crate::FromBech32Str), [`FromBech32mStr`](crate::FromBech32mStr) | `encoding-bech32` / `encoding-bech32m` |
11//! | [`DecodingError`] | Unified wrapper for all above | Always |
12//!
13//! # Design: build-invariant, heap-free, forward-compatible
14//!
15//! - **Identical shapes in debug and release builds.** No variant is gated on
16//!   `cfg(debug_assertions)`, so code that matches on these enums compiles and
17//!   behaves the same under every profile.
18//! - **No heap data.** Errors carry at most `usize` length metadata; they never
19//!   contain payload bytes, HRP strings, or other input-derived text. All error
20//!   types are `Copy` and work without `alloc`.
21//! - **`#[non_exhaustive]`.** Variants (and fields of struct variants) may be
22//!   added in future releases without a semver-major bump; downstream matches
23//!   need a wildcard arm.
24//!
25//! # Security: what errors may reveal
26//!
27//! `InvalidLength` variants carry the expected and actual byte counts in all
28//! builds. Expected lengths are compile-time protocol parameters (key sizes,
29//! nonce sizes) and actual lengths derive from the caller's own input, so
30//! neither is treated as secret — this matches the crate's threat model
31//! (see SECURITY.md). Genuinely input-derived strings (received HRPs, encoding
32//! hints) are **never** captured, in any build. If even coarse error categories
33//! are sensitive in your deployment, redact errors at the logging boundary.
34
35use thiserror::Error;
36
37/// Error returned when a byte slice cannot be converted to a fixed-size array.
38///
39/// Carries the expected and actual lengths in all build profiles. Lengths are
40/// public protocol parameters, not secret material.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
42#[non_exhaustive]
43pub enum FromSliceError {
44    /// The slice length does not match the target array length.
45    #[error("slice length mismatch: expected {expected}, got {got}")]
46    #[non_exhaustive]
47    InvalidLength {
48        /// Number of bytes the target array requires.
49        expected: usize,
50        /// Number of bytes actually provided.
51        got: usize,
52    },
53}
54
55/// Errors produced when decoding Bech32 (BIP-173) or Bech32m (BIP-350) strings.
56///
57/// *Requires feature `encoding-bech32` or `encoding-bech32m`.*
58///
59/// Variant shapes are identical in debug and release builds. No input-derived
60/// strings (such as the received HRP) are ever captured — the caller already
61/// holds the input and the expected HRP.
62#[cfg(any(feature = "encoding-bech32", feature = "encoding-bech32m"))]
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
64#[non_exhaustive]
65pub enum Bech32Error {
66    /// The Human-Readable Part (HRP) is invalid.
67    #[error("invalid Human-Readable Part (HRP)")]
68    InvalidHrp,
69    /// Bit conversion during encoding/decoding failed.
70    ///
71    /// **Currently unreachable.** After `CheckedHrpstring::new()` succeeds, the
72    /// `.byte_iter()` iterator is infallible — all bit-conversion happens during
73    /// the `new()` call and any failure surfaces as `OperationFailed` instead.
74    /// This variant is preserved as public API for forward compatibility should a
75    /// fallible conversion path be introduced in a future release of the `bech32` crate.
76    #[error("bit conversion failed")]
77    ConversionFailed,
78    /// General bech32 operation failure (malformed string or checksum mismatch).
79    #[error("bech32 operation failed")]
80    OperationFailed,
81    /// The decoded HRP does not match the HRP the caller required.
82    ///
83    /// The received HRP is deliberately not captured — it is input-derived text.
84    /// The caller passed the expected HRP and holds the input string.
85    #[error("unexpected HRP")]
86    UnexpectedHrp,
87    /// The decoded payload length does not match the target type's length.
88    #[error("decoded length mismatch: expected {expected}, got {got}")]
89    #[non_exhaustive]
90    InvalidLength {
91        /// Number of bytes the target type requires.
92        expected: usize,
93        /// Number of bytes actually decoded.
94        got: usize,
95    },
96}
97
98/// Errors produced when decoding base64url strings.
99///
100/// *Requires feature `encoding-base64`.*
101///
102/// Variant shapes are identical in debug and release builds; only numeric
103/// length metadata is carried.
104#[cfg(feature = "encoding-base64")]
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
106#[non_exhaustive]
107pub enum Base64Error {
108    /// The string is not valid base64url.
109    #[error("invalid base64 string")]
110    InvalidBase64,
111    /// The decoded payload length does not match the target type's length.
112    #[error("decoded length mismatch: expected {expected}, got {got}")]
113    #[non_exhaustive]
114    InvalidLength {
115        /// Number of bytes the target type requires.
116        expected: usize,
117        /// Number of bytes actually decoded.
118        got: usize,
119    },
120}
121
122/// Errors produced when decoding hexadecimal strings.
123///
124/// *Requires feature `encoding-hex`.*
125///
126/// Variant shapes are identical in debug and release builds; only numeric
127/// length metadata is carried.
128#[cfg(feature = "encoding-hex")]
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
130#[non_exhaustive]
131pub enum HexError {
132    /// The string is not valid hexadecimal.
133    #[error("invalid hex string")]
134    InvalidHex,
135    /// The decoded payload length does not match the target type's length.
136    #[error("decoded length mismatch: expected {expected}, got {got}")]
137    #[non_exhaustive]
138    InvalidLength {
139        /// Number of bytes the target type requires.
140        expected: usize,
141        /// Number of bytes actually decoded.
142        got: usize,
143    },
144}
145
146/// Unified error type for multi-format decoding operations.
147///
148/// Wraps format-specific errors from hex, base64url, bech32, and bech32m decoders.
149/// Always available; variants depend on enabled features. Like the format-specific
150/// errors it wraps, this type is heap-free, `Copy`, and build-invariant.
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
152#[non_exhaustive]
153pub enum DecodingError {
154    /// The input is not valid Bech32.
155    #[cfg(feature = "encoding-bech32")]
156    #[error("invalid bech32 string")]
157    InvalidBech32(#[source] Bech32Error),
158    /// The input is not valid Base64url.
159    #[cfg(feature = "encoding-base64")]
160    #[error("invalid base64 string")]
161    InvalidBase64(#[source] Base64Error),
162    /// The input is not valid hexadecimal.
163    #[cfg(feature = "encoding-hex")]
164    #[error("invalid hex string")]
165    InvalidHex(#[source] HexError),
166    /// The encoding could not be identified.
167    ///
168    /// Deliberately carries no hint text — free-form diagnostics derived from
169    /// the input would embed input data in the error value.
170    #[error("invalid encoding")]
171    InvalidEncoding,
172}