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}