Skip to main content

amp_rs/signer/
error.rs

1use thiserror::Error;
2
3/// Comprehensive error types for signer operations
4///
5/// This enum covers all possible error scenarios that can occur during
6/// transaction signing operations, providing detailed context for debugging
7/// and proper error handling in client applications.
8#[derive(Error, Debug)]
9pub enum SignerError {
10    /// LWK-specific errors from the Liquid Wallet Kit
11    ///
12    /// This variant captures errors from LWK operations including:
13    /// - `SwSigner` creation failures
14    /// - Transaction signing failures  
15    /// - PSET (Partially Signed Element Transaction) operations
16    /// - Key derivation and cryptographic operations
17    #[error("LWK signing operation failed: {0}")]
18    Lwk(String),
19    #[error("LWK signing operation failed: {error_message}\n\nOperation: {operation}\n\nContext: {context}")]
20    LwkDetailed {
21        /// The LWK operation that failed (e.g., "`sign_transaction`", "`create_signer`")
22        operation: String,
23        /// Additional context about the operation
24        context: String,
25        /// The error message from LWK
26        error_message: String,
27    },
28
29    /// Invalid mnemonic phrase errors
30    ///
31    /// This variant captures mnemonic-related errors including:
32    /// - Invalid word count (not 12, 15, 18, 21, or 24 words)
33    /// - Invalid characters or formatting
34    /// - BIP39 checksum validation failures
35    /// - Empty or malformed mnemonic phrases
36    #[error("Invalid mnemonic phrase: {0}")]
37    InvalidMnemonic(String),
38
39    /// Hex string parsing and decoding errors
40    ///
41    /// This variant captures errors when parsing hex-encoded data including:
42    /// - Invalid hex characters
43    /// - Odd-length hex strings
44    /// - Empty hex strings
45    /// - Malformed transaction hex data
46    #[error("Hex parsing failed: {0}")]
47    HexParse(#[from] hex::FromHexError),
48    #[error("Hex parsing failed: {hex_error}\n\nParsing: {parsing_context}\nHex String (first 100 chars): {hex_preview}")]
49    HexParseDetailed {
50        /// Context about what was being parsed
51        parsing_context: String,
52        /// Preview of the hex string that failed to parse
53        hex_preview: String,
54        /// The original hex parsing error
55        hex_error: String,
56    },
57
58    /// Invalid transaction structure or content errors
59    ///
60    /// This variant captures transaction validation errors including:
61    /// - Malformed transaction structure
62    /// - Missing inputs or outputs
63    /// - Invalid transaction serialization
64    /// - PSET conversion failures
65    /// - Transaction size or format issues
66    #[error("Invalid transaction structure: {0}")]
67    InvalidTransaction(String),
68    #[error("Invalid transaction: {error_message}\n\nTransaction ID: {txid}\nValidation Details: {validation_details}")]
69    InvalidTransactionDetailed {
70        /// The transaction ID if available
71        txid: String,
72        /// Specific validation failure details
73        validation_details: String,
74        /// The error message
75        error_message: String,
76    },
77
78    /// Network-related communication errors
79    ///
80    /// This variant captures network errors that may occur during
81    /// remote operations or API calls (reserved for future use).
82    #[error("Network communication failed: {0}")]
83    Network(#[from] reqwest::Error),
84
85    /// JSON serialization and deserialization errors
86    ///
87    /// This variant captures JSON processing errors including:
88    /// - Mnemonic file parsing failures
89    /// - Invalid JSON structure in storage files
90    /// - Serialization failures when writing storage
91    #[error("JSON serialization failed: {0}")]
92    Serialization(#[from] serde_json::Error),
93    #[error("Serialization error: {serde_error}\n\nOperation: {operation}\nData Type: {data_type}\n\nContext: {context}")]
94    SerializationDetailed {
95        /// The serialization operation (serialize/deserialize)
96        operation: String,
97        /// The data type being processed
98        data_type: String,
99        /// Additional context about the operation
100        context: String,
101        /// The original serde error message
102        serde_error: String,
103    },
104
105    /// File system I/O operation errors
106    ///
107    /// This variant captures file operation errors including:
108    /// - Mnemonic file read/write failures
109    /// - Permission denied errors
110    /// - Disk space or filesystem issues
111    /// - Atomic write operation failures
112    #[error("File I/O operation failed: {0}")]
113    FileIo(#[from] std::io::Error),
114}
115
116// Additional error conversions for external library errors
117// These provide seamless integration with third-party error types
118
119/// Convert BIP39 mnemonic errors to `SignerError`
120///
121/// This conversion handles all BIP39-related errors including:
122/// - Invalid word count
123/// - Invalid words not in BIP39 wordlist  
124/// - Checksum validation failures
125/// - Language detection issues
126impl From<bip39::Error> for SignerError {
127    fn from(err: bip39::Error) -> Self {
128        Self::InvalidMnemonic(format!("BIP39 validation failed: {err}"))
129    }
130}
131
132/// Convert Elements transaction encoding errors to `SignerError`
133///
134/// This conversion handles transaction serialization/deserialization errors
135/// from the Elements library including:
136/// - Consensus encoding failures
137/// - Invalid transaction structure
138/// - Serialization format errors
139impl From<elements::encode::Error> for SignerError {
140    fn from(err: elements::encode::Error) -> Self {
141        Self::InvalidTransaction(format!("Elements transaction encoding failed: {err}"))
142    }
143}
144
145// Note: LWK errors are handled manually in the implementation code
146// rather than through automatic conversion. This provides better control
147// over error context and allows for operation-specific error messages
148// that help with debugging and troubleshooting.
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use std::io::{Error as IoError, ErrorKind};
154
155    #[test]
156    fn test_lwk_error_variant() {
157        let error_msg = "SwSigner creation failed";
158        let error = SignerError::Lwk(error_msg.to_string());
159
160        // Test error message formatting
161        let formatted = format!("{}", error);
162        assert_eq!(
163            formatted,
164            "LWK signing operation failed: SwSigner creation failed"
165        );
166
167        // Test debug formatting
168        let debug_formatted = format!("{:?}", error);
169        assert!(debug_formatted.contains("Lwk"));
170        assert!(debug_formatted.contains(error_msg));
171
172        // Test error source (should be None for this variant)
173        assert!(std::error::Error::source(&error).is_none());
174    }
175
176    #[test]
177    fn test_invalid_mnemonic_error_variant() {
178        let error_msg = "Invalid word count: expected 12, got 5";
179        let error = SignerError::InvalidMnemonic(error_msg.to_string());
180
181        // Test error message formatting
182        let formatted = format!("{}", error);
183        assert_eq!(
184            formatted,
185            "Invalid mnemonic phrase: Invalid word count: expected 12, got 5"
186        );
187
188        // Test debug formatting
189        let debug_formatted = format!("{:?}", error);
190        assert!(debug_formatted.contains("InvalidMnemonic"));
191        assert!(debug_formatted.contains(error_msg));
192
193        // Test error source (should be None for this variant)
194        assert!(std::error::Error::source(&error).is_none());
195    }
196
197    #[test]
198    fn test_hex_parse_error_conversion() {
199        // Create a hex parsing error by trying to decode invalid hex
200        let hex_result = hex::decode("invalid_hex_zz");
201        let hex_error = hex_result.unwrap_err();
202        let signer_error = SignerError::from(hex_error);
203
204        // Test error variant
205        match signer_error {
206            SignerError::HexParse(_) => {} // Expected
207            other => panic!("Expected HexParse variant, got: {:?}", other),
208        }
209
210        // Test error message formatting
211        let formatted = format!("{}", signer_error);
212        assert!(formatted.starts_with("Hex parsing failed:"));
213
214        // Test error source preservation
215        let source = std::error::Error::source(&signer_error);
216        assert!(source.is_some());
217    }
218
219    #[test]
220    fn test_invalid_transaction_error_variant() {
221        let error_msg = "Transaction deserialization failed: invalid input count";
222        let error = SignerError::InvalidTransaction(error_msg.to_string());
223
224        // Test error message formatting
225        let formatted = format!("{}", error);
226        assert_eq!(formatted, "Invalid transaction structure: Transaction deserialization failed: invalid input count");
227
228        // Test debug formatting
229        let debug_formatted = format!("{:?}", error);
230        assert!(debug_formatted.contains("InvalidTransaction"));
231        assert!(debug_formatted.contains(error_msg));
232
233        // Test error source (should be None for this variant)
234        assert!(std::error::Error::source(&error).is_none());
235    }
236
237    #[test]
238    fn test_network_error_conversion() {
239        // Create a reqwest error by making an invalid request
240        let client = reqwest::Client::new();
241        let request_result = client.get("http://").build();
242        let reqwest_error = request_result.unwrap_err();
243        let signer_error = SignerError::from(reqwest_error);
244
245        // Test error variant
246        match signer_error {
247            SignerError::Network(_) => {} // Expected
248            other => panic!("Expected Network variant, got: {:?}", other),
249        }
250
251        // Test error message formatting
252        let formatted = format!("{}", signer_error);
253        assert!(formatted.starts_with("Network communication failed:"));
254
255        // Test error source preservation
256        let source = std::error::Error::source(&signer_error);
257        assert!(source.is_some());
258    }
259
260    #[test]
261    fn test_serialization_error_conversion() {
262        // Create a JSON serialization error by trying to parse invalid JSON
263        let invalid_json = r#"{"invalid": json syntax"#;
264        let json_error = serde_json::from_str::<serde_json::Value>(invalid_json).unwrap_err();
265        let signer_error = SignerError::from(json_error);
266
267        // Test error variant
268        match signer_error {
269            SignerError::Serialization(_) => {} // Expected
270            other => panic!("Expected Serialization variant, got: {:?}", other),
271        }
272
273        // Test error message formatting
274        let formatted = format!("{}", signer_error);
275        assert!(formatted.starts_with("JSON serialization failed:"));
276
277        // Test error source preservation
278        let source = std::error::Error::source(&signer_error);
279        assert!(source.is_some());
280    }
281
282    #[test]
283    fn test_file_io_error_conversion() {
284        // Create an I/O error
285        let io_error = IoError::new(ErrorKind::NotFound, "File not found");
286        let signer_error = SignerError::from(io_error);
287
288        // Test error variant
289        match signer_error {
290            SignerError::FileIo(_) => {} // Expected
291            other => panic!("Expected FileIo variant, got: {:?}", other),
292        }
293
294        // Test error message formatting
295        let formatted = format!("{}", signer_error);
296        assert!(formatted.starts_with("File I/O operation failed:"));
297        assert!(formatted.contains("File not found"));
298
299        // Test error source preservation
300        let source = std::error::Error::source(&signer_error);
301        assert!(source.is_some());
302        assert_eq!(source.unwrap().to_string(), "File not found");
303    }
304
305    #[test]
306    fn test_bip39_error_conversion() {
307        // Create a BIP39 error (invalid word count)
308        let bip39_error = bip39::Error::BadWordCount(5);
309        let signer_error = SignerError::from(bip39_error);
310
311        // Test error variant
312        match &signer_error {
313            SignerError::InvalidMnemonic(msg) => {
314                assert!(msg.contains("BIP39 validation failed"));
315                assert!(msg.contains("word count"));
316            }
317            other => panic!("Expected InvalidMnemonic variant, got: {:?}", other),
318        }
319
320        // Test error message formatting
321        let formatted = format!("{}", signer_error);
322        assert!(formatted.starts_with("Invalid mnemonic phrase:"));
323        assert!(formatted.contains("BIP39 validation failed"));
324    }
325
326    #[test]
327    fn test_elements_encode_error_conversion() {
328        // Create an Elements encoding error
329        let elements_error = elements::encode::Error::ParseFailed("Invalid transaction format");
330        let signer_error = SignerError::from(elements_error);
331
332        // Test error variant
333        match &signer_error {
334            SignerError::InvalidTransaction(msg) => {
335                assert!(msg.contains("Elements transaction encoding failed"));
336                assert!(msg.contains("Invalid transaction format"));
337            }
338            other => panic!("Expected InvalidTransaction variant, got: {:?}", other),
339        }
340
341        // Test error message formatting
342        let formatted = format!("{}", signer_error);
343        assert!(formatted.starts_with("Invalid transaction structure:"));
344        assert!(formatted.contains("Elements transaction encoding failed"));
345    }
346
347    #[test]
348    fn test_error_chain_preservation() {
349        // Test that error chains are properly preserved through conversions
350
351        // Create a nested I/O error
352        let inner_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
353        let signer_error = SignerError::from(inner_error);
354
355        // Test that the source chain is preserved
356        let source = std::error::Error::source(&signer_error);
357        assert!(source.is_some());
358        assert_eq!(source.unwrap().to_string(), "Access denied");
359
360        // Test error message includes context
361        let formatted = format!("{}", signer_error);
362        assert!(formatted.contains("File I/O operation failed"));
363        assert!(formatted.contains("Access denied"));
364    }
365
366    #[test]
367    fn test_error_send_sync_traits() {
368        // Test that SignerError implements Send and Sync for async compatibility
369        fn assert_send<T: Send>() {}
370        fn assert_sync<T: Sync>() {}
371
372        assert_send::<SignerError>();
373        assert_sync::<SignerError>();
374    }
375
376    #[test]
377    fn test_error_static_lifetime() {
378        // Test that SignerError can be used with 'static lifetime
379        fn assert_static<T: 'static>() {}
380        assert_static::<SignerError>();
381    }
382
383    #[test]
384    fn test_all_error_variants_display() {
385        // Test that all error variants have proper Display implementations
386        let errors = vec![
387            SignerError::Lwk("test lwk error".to_string()),
388            SignerError::InvalidMnemonic("test mnemonic error".to_string()),
389            SignerError::HexParse(hex::decode("invalid_hex_zz").unwrap_err()),
390            SignerError::InvalidTransaction("test transaction error".to_string()),
391            SignerError::Network(reqwest::Client::new().get("http://").build().unwrap_err()),
392            SignerError::Serialization(
393                serde_json::from_str::<serde_json::Value>(r#"{"invalid": json"#).unwrap_err(),
394            ),
395            SignerError::FileIo(IoError::new(ErrorKind::NotFound, "test file error")),
396        ];
397
398        for error in errors {
399            let display_str = format!("{}", error);
400            assert!(
401                !display_str.is_empty(),
402                "Error display should not be empty: {:?}",
403                error
404            );
405
406            let debug_str = format!("{:?}", error);
407            assert!(
408                !debug_str.is_empty(),
409                "Error debug should not be empty: {:?}",
410                error
411            );
412        }
413    }
414
415    #[test]
416    fn test_error_message_preservation() {
417        // Test that custom error messages are preserved correctly
418        let test_cases = vec![
419            (
420                "LWK operation failed with code 123",
421                SignerError::Lwk("LWK operation failed with code 123".to_string()),
422            ),
423            (
424                "Mnemonic has invalid checksum",
425                SignerError::InvalidMnemonic("Mnemonic has invalid checksum".to_string()),
426            ),
427            (
428                "Transaction missing required inputs",
429                SignerError::InvalidTransaction("Transaction missing required inputs".to_string()),
430            ),
431        ];
432
433        for (expected_msg, error) in test_cases {
434            let formatted = format!("{}", error);
435            assert!(
436                formatted.contains(expected_msg),
437                "Error message should contain '{}', got: '{}'",
438                expected_msg,
439                formatted
440            );
441        }
442    }
443
444    #[test]
445    fn test_error_propagation_through_result() {
446        // Test error propagation through Result types (simulating call stack)
447        fn level_3() -> Result<(), SignerError> {
448            Err(SignerError::Lwk("Deep error".to_string()))
449        }
450
451        fn level_2() -> Result<(), SignerError> {
452            level_3()?;
453            Ok(())
454        }
455
456        fn level_1() -> Result<(), SignerError> {
457            level_2()?;
458            Ok(())
459        }
460
461        let result = level_1();
462        assert!(result.is_err());
463
464        match result.unwrap_err() {
465            SignerError::Lwk(msg) => assert_eq!(msg, "Deep error"),
466            other => panic!("Expected Lwk error, got: {:?}", other),
467        }
468    }
469
470    #[test]
471    fn test_error_conversion_preserves_context() {
472        // Test that automatic conversions preserve error context
473
474        // Test hex error conversion
475        let hex_result: Result<Vec<u8>, hex::FromHexError> = hex::decode("invalid_hex_zz");
476        let hex_error = hex_result.unwrap_err();
477        let signer_error = SignerError::from(hex_error);
478
479        let formatted = format!("{}", signer_error);
480        assert!(formatted.contains("Hex parsing failed"));
481
482        // Test I/O error conversion
483        let io_error = IoError::new(
484            ErrorKind::PermissionDenied,
485            "Cannot write to read-only file",
486        );
487        let signer_error = SignerError::from(io_error);
488
489        let formatted = format!("{}", signer_error);
490        assert!(formatted.contains("File I/O operation failed"));
491        assert!(formatted.contains("Cannot write to read-only file"));
492    }
493
494    #[test]
495    fn test_error_debug_format_completeness() {
496        // Test that debug format includes all relevant information
497        let error = SignerError::InvalidMnemonic("Test mnemonic error with details".to_string());
498        let debug_str = format!("{:#?}", error);
499
500        // Debug format should include variant name and message
501        assert!(debug_str.contains("InvalidMnemonic"));
502        assert!(debug_str.contains("Test mnemonic error with details"));
503    }
504
505    #[test]
506    fn test_multiple_error_conversions() {
507        // Test multiple error conversions in sequence
508        let test_cases = vec![
509            "invalid_hex_zz", // Invalid character
510            "abc",            // Odd length
511        ];
512
513        for invalid_hex in test_cases {
514            let hex_error = hex::decode(invalid_hex).unwrap_err();
515            let signer_error = SignerError::from(hex_error);
516            match signer_error {
517                SignerError::HexParse(_) => {} // Expected
518                other => panic!("Expected HexParse variant, got: {:?}", other),
519            }
520
521            // Verify error message is meaningful
522            let formatted = format!("{}", signer_error);
523            assert!(formatted.starts_with("Hex parsing failed:"));
524        }
525    }
526
527    #[test]
528    fn test_error_equality_and_comparison() {
529        // Test that errors can be compared for debugging purposes
530        let error1 = SignerError::Lwk("same message".to_string());
531        let error2 = SignerError::Lwk("same message".to_string());
532        let error3 = SignerError::Lwk("different message".to_string());
533
534        // Note: SignerError doesn't implement PartialEq by design (errors often contain
535        // non-comparable types), but we can test that the debug representations are consistent
536        let debug1 = format!("{:?}", error1);
537        let debug2 = format!("{:?}", error2);
538        let debug3 = format!("{:?}", error3);
539
540        assert_eq!(debug1, debug2);
541        assert_ne!(debug1, debug3);
542    }
543}