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//! # Security: debug vs release error metadata
14//!
15//! In **debug builds** (`cfg(debug_assertions)`), decoding errors include detailed
16//! hints — expected vs actual lengths, received HRP values — to aid development.
17//! In **release builds** these details are stripped to prevent length/HRP oracles.
18//!
19//! Prefer `Display` (`{}`) over `Debug` (`{:?}`) when logging errors in production —
20//! `Debug` may expose struct fields in debug builds.
21//!
22//! See [SECURITY.md — Error Metadata](https://github.com/Slurp9187/secure-gate/blob/main/secure-gate-core/SECURITY.md#error-metadata-debug-vs-release)
23//! for full guidance.
24
25use thiserror::Error;
26
27/// Error returned when a byte slice cannot be converted to a fixed-size array.
28///
29/// In **debug builds** includes expected and actual lengths for development debugging.
30/// In **release builds** uses generic messages to prevent leaking expected-length metadata.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
32pub enum FromSliceError {
33    #[cfg(debug_assertions)]
34    /// Length mismatch in debug builds (detailed).
35    #[error("slice length mismatch: expected {expected}, got {actual}")]
36    InvalidLength {
37        /// Actual slice length in bytes.
38        actual: usize,
39        /// Expected length in bytes.
40        expected: usize,
41    },
42    #[cfg(not(debug_assertions))]
43    /// Length mismatch in release builds (generic).
44    #[error("slice length mismatch")]
45    InvalidLength,
46}
47
48/// Errors produced when decoding Bech32 (BIP-173) or Bech32m (BIP-350) strings.
49///
50/// *Requires feature `encoding-bech32` or `encoding-bech32m`.*
51///
52/// In **debug builds** `UnexpectedHrp` and `InvalidLength` carry `expected`/`got`
53/// fields for development debugging. In **release builds** these variants are opaque
54/// to prevent leaking expected-length or HRP metadata.
55#[cfg(any(feature = "encoding-bech32", feature = "encoding-bech32m"))]
56#[derive(Clone, Debug, PartialEq, Eq, Error)]
57pub enum Bech32Error {
58    /// The Human-Readable Part (HRP) is invalid.
59    #[error("invalid Human-Readable Part (HRP)")]
60    InvalidHrp,
61    /// Bit conversion during encoding/decoding failed.
62    ///
63    /// **Currently unreachable.** After `CheckedHrpstring::new()` succeeds, the
64    /// `.byte_iter()` iterator is infallible — all bit-conversion happens during
65    /// the `new()` call and any failure surfaces as `OperationFailed` instead.
66    /// This variant is preserved as public API for forward compatibility should a
67    /// fallible conversion path be introduced in a future release of the `bech32` crate.
68    #[error("bit conversion failed")]
69    ConversionFailed,
70    /// General bech32 operation failure.
71    #[error("bech32 operation failed")]
72    OperationFailed,
73    #[cfg(debug_assertions)]
74    /// Unexpected HRP in debug builds (detailed).
75    #[error("unexpected HRP: expected {expected}, got {got}")]
76    UnexpectedHrp {
77        /// Human-readable part that was expected.
78        expected: String,
79        /// Human-readable part present in the input.
80        got: String,
81    },
82    #[cfg(not(debug_assertions))]
83    /// Unexpected HRP in release builds (generic).
84    #[error("unexpected HRP")]
85    UnexpectedHrp,
86    #[cfg(debug_assertions)]
87    /// Length mismatch in debug builds (detailed).
88    #[error("decoded length mismatch: expected {expected}, got {got}")]
89    InvalidLength {
90        /// Expected decoded length in bytes.
91        expected: usize,
92        /// Actual decoded length in bytes.
93        got: usize,
94    },
95    #[cfg(not(debug_assertions))]
96    /// Length mismatch in release builds (generic).
97    #[error("decoded length mismatch")]
98    InvalidLength,
99}
100
101/// Errors produced when decoding base64url strings.
102///
103/// *Requires feature `encoding-base64`.*
104///
105/// In **debug builds** `InvalidLength` carries `expected`/`got` fields for
106/// development debugging. In **release builds** this variant is opaque to
107/// prevent leaking expected-length metadata.
108#[cfg(feature = "encoding-base64")]
109#[derive(Clone, Debug, PartialEq, Eq, Error)]
110pub enum Base64Error {
111    /// The string is not valid base64url.
112    #[error("invalid base64 string")]
113    InvalidBase64,
114    #[cfg(debug_assertions)]
115    /// Length mismatch in debug builds (detailed).
116    #[error("decoded length mismatch: expected {expected}, got {got}")]
117    InvalidLength {
118        /// Expected decoded length in bytes.
119        expected: usize,
120        /// Actual decoded length in bytes.
121        got: usize,
122    },
123    #[cfg(not(debug_assertions))]
124    /// Length mismatch in release builds (generic).
125    #[error("decoded length mismatch")]
126    InvalidLength,
127}
128
129/// Errors produced when decoding hexadecimal strings.
130///
131/// *Requires feature `encoding-hex`.*
132///
133/// In **debug builds** `InvalidLength` carries `expected`/`got` fields for
134/// development debugging. In **release builds** this variant is opaque to
135/// prevent leaking expected-length metadata.
136#[cfg(feature = "encoding-hex")]
137#[derive(Clone, Debug, PartialEq, Eq, Error)]
138pub enum HexError {
139    /// The string is not valid hexadecimal.
140    #[error("invalid hex string")]
141    InvalidHex,
142    #[cfg(debug_assertions)]
143    /// Length mismatch in debug builds (detailed).
144    #[error("decoded length mismatch: expected {expected}, got {got}")]
145    InvalidLength {
146        /// Expected decoded length in bytes.
147        expected: usize,
148        /// Actual decoded length in bytes.
149        got: usize,
150    },
151    #[cfg(not(debug_assertions))]
152    /// Length mismatch in release builds (generic).
153    #[error("decoded length mismatch")]
154    InvalidLength,
155}
156
157/// Unified error type for multi-format decoding operations.
158///
159/// Wraps format-specific errors from hex, base64url, bech32, and bech32m decoders.
160/// Always available; variants depend on enabled features.
161#[derive(Clone, Debug, Error)]
162pub enum DecodingError {
163    /// Bech32 or Bech32m decoding failed.
164    #[cfg(feature = "encoding-bech32")]
165    #[error("invalid bech32 string")]
166    InvalidBech32(#[source] Bech32Error),
167    /// Base64url decoding failed.
168    #[cfg(feature = "encoding-base64")]
169    #[error("invalid base64 string")]
170    InvalidBase64(#[source] Base64Error),
171    /// Hexadecimal decoding failed.
172    #[cfg(feature = "encoding-hex")]
173    #[error("invalid hex string")]
174    InvalidHex(#[source] HexError),
175    /// Generic encoding failure in debug builds (includes a non-sensitive hint).
176    #[cfg(debug_assertions)]
177    #[error("invalid encoding: {hint}")]
178    InvalidEncoding {
179        /// Short hint for developers (debug builds only).
180        hint: String,
181    },
182    #[cfg(not(debug_assertions))]
183    /// Generic encoding failure in release builds (opaque).
184    #[error("invalid encoding")]
185    InvalidEncoding,
186}