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}