khodpay_bip39/error.rs
1//! Error handling for BIP39 mnemonic operations.
2//!
3//! This module provides comprehensive error types for all BIP39-related operations,
4//! including mnemonic generation, validation, and seed derivation.
5//!
6//! # Error Types
7//!
8//! The main [`Error`] enum covers all possible failure modes:
9//! - **Validation errors**: Invalid entropy, word count, or mnemonic phrases
10//! - **Word errors**: Invalid words or checksum failures
11//! - **External errors**: Random number generation and underlying BIP39 crate errors
12//!
13//! # Examples
14//!
15//! ```rust
16//! use khodpay_bip39::{Error, Result};
17//!
18//! // Creating and matching specific errors
19//! let entropy_error = Error::InvalidEntropyLength { length: 10 };
20//! match entropy_error {
21//! Error::InvalidEntropyLength { length } => {
22//! println!("Entropy too short: {} bytes", length);
23//! }
24//! _ => {}
25//! }
26//!
27//! // Error equality comparison
28//! let error1 = Error::InvalidWordCount { count: 13 };
29//! let error2 = Error::InvalidWordCount { count: 13 };
30//! assert_eq!(error1, error2);
31//! ```
32
33use thiserror::Error;
34
35/// Comprehensive error types for BIP39 mnemonic operations.
36///
37/// This enum represents all possible errors that can occur when working with
38/// BIP39 mnemonics, from entropy generation to seed derivation.
39///
40/// # Error Categories
41///
42/// - **Input Validation**: [`InvalidEntropyLength`], [`InvalidWordCount`], [`InvalidMnemonic`]
43/// - **Mnemonic Validation**: [`InvalidWord`], [`InvalidChecksum`]
44/// - **External Dependencies**: [`RandomGeneration`], [`Bip39Error`]
45///
46/// [`InvalidEntropyLength`]: Error::InvalidEntropyLength
47/// [`InvalidWordCount`]: Error::InvalidWordCount
48/// [`InvalidMnemonic`]: Error::InvalidMnemonic
49/// [`InvalidWord`]: Error::InvalidWord
50/// [`InvalidChecksum`]: Error::InvalidChecksum
51/// [`RandomGeneration`]: Error::RandomGeneration
52/// [`Bip39Error`]: Error::Bip39Error
53#[derive(Debug, Error)]
54pub enum Error {
55 /// The provided entropy has an invalid length.
56 ///
57 /// BIP39 requires entropy to be one of the following lengths:
58 /// - 16 bytes (128 bits) → 12 words
59 /// - 20 bytes (160 bits) → 15 words
60 /// - 24 bytes (192 bits) → 18 words
61 /// - 28 bytes (224 bits) → 21 words
62 /// - 32 bytes (256 bits) → 24 words
63 ///
64 /// # Example
65 /// ```rust
66 /// # use khodpay_bip39::Error;
67 /// let error = Error::InvalidEntropyLength { length: 10 };
68 /// println!("{}", error); // "Invalid entropy length: 10 bytes. Valid lengths are..."
69 /// ```
70 #[error(
71 "Invalid entropy length: {length} bytes. Valid lengths are 16, 20, 24, 28, or 32 bytes"
72 )]
73 InvalidEntropyLength {
74 /// The actual length of the invalid entropy in bytes
75 length: usize,
76 },
77
78 /// The provided mnemonic phrase is invalid.
79 ///
80 /// This error occurs when a mnemonic phrase fails general validation,
81 /// such as having the wrong format, length, or structure.
82 ///
83 /// # Example
84 /// ```rust
85 /// # use khodpay_bip39::Error;
86 /// let error = Error::InvalidMnemonic {
87 /// reason: "Too few words".to_string()
88 /// };
89 /// ```
90 #[error("Invalid mnemonic phrase: {reason}")]
91 InvalidMnemonic {
92 /// Detailed reason why the mnemonic is invalid
93 reason: String,
94 },
95
96 /// The provided word count is not supported.
97 ///
98 /// BIP39 supports only specific word counts that correspond to
99 /// valid entropy lengths. See [`InvalidEntropyLength`] for the mapping.
100 ///
101 /// [`InvalidEntropyLength`]: Error::InvalidEntropyLength
102 #[error("Invalid word count: {count}. Valid counts are 12, 15, 18, 21, or 24 words")]
103 InvalidWordCount {
104 /// The invalid word count provided
105 count: usize,
106 },
107
108 /// A word in the mnemonic phrase is not in the BIP39 word list.
109 ///
110 /// Each word in a BIP39 mnemonic must be from the official 2048-word list.
111 /// This error provides both the invalid word and its position for debugging.
112 ///
113 /// # Example
114 /// ```rust
115 /// # use khodpay_bip39::Error;
116 /// let error = Error::InvalidWord {
117 /// word: "invalidword".to_string(),
118 /// position: 5,
119 /// };
120 /// println!("{}", error); // "Invalid word 'invalidword' at position 5"
121 /// ```
122 #[error("Invalid word '{word}' at position {position}")]
123 InvalidWord {
124 /// The invalid word that was found
125 word: String,
126 /// Zero-based position of the invalid word in the phrase
127 position: usize,
128 },
129
130 /// The mnemonic phrase has an invalid checksum.
131 ///
132 /// BIP39 mnemonics include a checksum derived from the entropy.
133 /// This error occurs when the checksum verification fails,
134 /// indicating the mnemonic may be corrupted or incorrectly generated.
135 #[error("Invalid checksum for mnemonic phrase")]
136 InvalidChecksum,
137
138 /// Error occurred during random number generation.
139 ///
140 /// This error is automatically converted from [`rand::Error`] and
141 /// indicates a failure in the random number generator used for
142 /// entropy generation.
143 #[error("Random number generation failed")]
144 RandomGeneration,
145
146 /// Error from the underlying BIP39 crate.
147 ///
148 /// This error wraps errors from the external `bip39` crate that
149 /// we use for some low-level operations.
150 #[error("BIP39 error: {message}")]
151 Bip39Error {
152 /// Error message from the underlying BIP39 crate
153 message: String,
154 },
155}
156
157/// Custom equality implementation for [`enum@Error`].
158///
159/// This implementation allows comparing errors for equality, which is useful
160/// in tests and error matching. For errors containing external types
161/// (like [`rand::Error`] or [`bip39_upstream::Error`]), we compare only
162/// by error type since the wrapped errors may not implement [`PartialEq`].
163///
164/// # Examples
165///
166/// ```rust
167/// # use khodpay_bip39::Error;
168/// let error1 = Error::InvalidWordCount { count: 12 };
169/// let error2 = Error::InvalidWordCount { count: 12 };
170/// let error3 = Error::InvalidWordCount { count: 15 };
171///
172/// assert_eq!(error1, error2); // Same variant and data
173/// assert_ne!(error1, error3); // Same variant, different data
174/// ```
175impl PartialEq for Error {
176 fn eq(&self, other: &Self) -> bool {
177 match (self, other) {
178 (
179 Error::InvalidEntropyLength { length: l1 },
180 Error::InvalidEntropyLength { length: l2 },
181 ) => l1 == l2,
182 (Error::InvalidMnemonic { reason: r1 }, Error::InvalidMnemonic { reason: r2 }) => {
183 r1 == r2
184 }
185 (Error::InvalidWordCount { count: c1 }, Error::InvalidWordCount { count: c2 }) => {
186 c1 == c2
187 }
188 (
189 Error::InvalidWord {
190 word: w1,
191 position: p1,
192 },
193 Error::InvalidWord {
194 word: w2,
195 position: p2,
196 },
197 ) => w1 == w2 && p1 == p2,
198 (Error::InvalidChecksum, Error::InvalidChecksum) => true,
199 (Error::RandomGeneration, Error::RandomGeneration) => true,
200 (Error::Bip39Error { message: m1 }, Error::Bip39Error { message: m2 }) => m1 == m2,
201 _ => false,
202 }
203 }
204}
205
206/// Marker trait indicating that [`enum@Error`] can be compared for equality.
207///
208/// This is automatically implemented since we provide [`PartialEq`].
209impl Eq for Error {}
210
211/// Convert from `rand::Error` to our `Error` type.
212impl From<rand::Error> for Error {
213 fn from(_error: rand::Error) -> Self {
214 Error::RandomGeneration
215 }
216}
217
218/// Convert from `bip39_upstream::Error` to our `Error` type.
219impl From<bip39_upstream::Error> for Error {
220 fn from(error: bip39_upstream::Error) -> Self {
221 Error::Bip39Error {
222 message: error.to_string(),
223 }
224 }
225}
226
227/// Convenient type alias for [`std::result::Result`] with our [`enum@Error`] type.
228///
229/// This allows using `Result<T>` instead of `Result<T, Error>` throughout
230/// the codebase, making function signatures cleaner and more readable.
231///
232/// # Examples
233///
234/// ```rust
235/// use khodpay_bip39::{Result, Error};
236///
237/// fn validate_entropy(entropy: &[u8]) -> Result<()> {
238/// if entropy.len() != 32 {
239/// return Err(Error::InvalidEntropyLength {
240/// length: entropy.len()
241/// });
242/// }
243/// Ok(())
244/// }
245/// ```
246pub type Result<T> = std::result::Result<T, Error>;
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_error_display() {
254 let error = Error::InvalidEntropyLength { length: 10 };
255 assert_eq!(
256 error.to_string(),
257 "Invalid entropy length: 10 bytes. Valid lengths are 16, 20, 24, 28, or 32 bytes"
258 );
259 }
260
261 #[test]
262 fn test_error_equality() {
263 let error1 = Error::InvalidWordCount { count: 13 };
264 let error2 = Error::InvalidWordCount { count: 13 };
265 let error3 = Error::InvalidWordCount { count: 14 };
266
267 assert_eq!(error1, error2);
268 assert_ne!(error1, error3);
269 }
270
271 #[test]
272 fn test_invalid_mnemonic_error() {
273 let error = Error::InvalidMnemonic {
274 reason: "Too short".to_string(),
275 };
276 assert_eq!(error.to_string(), "Invalid mnemonic phrase: Too short");
277 }
278
279 #[test]
280 fn test_invalid_word_error() {
281 let error = Error::InvalidWord {
282 word: "invalidword".to_string(),
283 position: 5,
284 };
285 assert_eq!(
286 error.to_string(),
287 "Invalid word 'invalidword' at position 5"
288 );
289 }
290
291 #[test]
292 fn test_invalid_checksum_error() {
293 let error = Error::InvalidChecksum;
294 assert_eq!(error.to_string(), "Invalid checksum for mnemonic phrase");
295 }
296}