use thiserror::Error;
#[derive(Error, Debug)]
pub enum SignerError {
#[error("LWK signing operation failed: {0}")]
Lwk(String),
#[error("LWK signing operation failed: {error_message}\n\nOperation: {operation}\n\nContext: {context}")]
LwkDetailed {
operation: String,
context: String,
error_message: String,
},
#[error("Invalid mnemonic phrase: {0}")]
InvalidMnemonic(String),
#[error("Hex parsing failed: {0}")]
HexParse(#[from] hex::FromHexError),
#[error("Hex parsing failed: {hex_error}\n\nParsing: {parsing_context}\nHex String (first 100 chars): {hex_preview}")]
HexParseDetailed {
parsing_context: String,
hex_preview: String,
hex_error: String,
},
#[error("Invalid transaction structure: {0}")]
InvalidTransaction(String),
#[error("Invalid transaction: {error_message}\n\nTransaction ID: {txid}\nValidation Details: {validation_details}")]
InvalidTransactionDetailed {
txid: String,
validation_details: String,
error_message: String,
},
#[error("Network communication failed: {0}")]
Network(#[from] reqwest::Error),
#[error("JSON serialization failed: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Serialization error: {serde_error}\n\nOperation: {operation}\nData Type: {data_type}\n\nContext: {context}")]
SerializationDetailed {
operation: String,
data_type: String,
context: String,
serde_error: String,
},
#[error("File I/O operation failed: {0}")]
FileIo(#[from] std::io::Error),
}
impl From<bip39::Error> for SignerError {
fn from(err: bip39::Error) -> Self {
Self::InvalidMnemonic(format!("BIP39 validation failed: {err}"))
}
}
impl From<elements::encode::Error> for SignerError {
fn from(err: elements::encode::Error) -> Self {
Self::InvalidTransaction(format!("Elements transaction encoding failed: {err}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn test_lwk_error_variant() {
let error_msg = "SwSigner creation failed";
let error = SignerError::Lwk(error_msg.to_string());
let formatted = format!("{}", error);
assert_eq!(
formatted,
"LWK signing operation failed: SwSigner creation failed"
);
let debug_formatted = format!("{:?}", error);
assert!(debug_formatted.contains("Lwk"));
assert!(debug_formatted.contains(error_msg));
assert!(std::error::Error::source(&error).is_none());
}
#[test]
fn test_invalid_mnemonic_error_variant() {
let error_msg = "Invalid word count: expected 12, got 5";
let error = SignerError::InvalidMnemonic(error_msg.to_string());
let formatted = format!("{}", error);
assert_eq!(
formatted,
"Invalid mnemonic phrase: Invalid word count: expected 12, got 5"
);
let debug_formatted = format!("{:?}", error);
assert!(debug_formatted.contains("InvalidMnemonic"));
assert!(debug_formatted.contains(error_msg));
assert!(std::error::Error::source(&error).is_none());
}
#[test]
fn test_hex_parse_error_conversion() {
let hex_result = hex::decode("invalid_hex_zz");
let hex_error = hex_result.unwrap_err();
let signer_error = SignerError::from(hex_error);
match signer_error {
SignerError::HexParse(_) => {} other => panic!("Expected HexParse variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("Hex parsing failed:"));
let source = std::error::Error::source(&signer_error);
assert!(source.is_some());
}
#[test]
fn test_invalid_transaction_error_variant() {
let error_msg = "Transaction deserialization failed: invalid input count";
let error = SignerError::InvalidTransaction(error_msg.to_string());
let formatted = format!("{}", error);
assert_eq!(formatted, "Invalid transaction structure: Transaction deserialization failed: invalid input count");
let debug_formatted = format!("{:?}", error);
assert!(debug_formatted.contains("InvalidTransaction"));
assert!(debug_formatted.contains(error_msg));
assert!(std::error::Error::source(&error).is_none());
}
#[test]
fn test_network_error_conversion() {
let client = reqwest::Client::new();
let request_result = client.get("http://").build();
let reqwest_error = request_result.unwrap_err();
let signer_error = SignerError::from(reqwest_error);
match signer_error {
SignerError::Network(_) => {} other => panic!("Expected Network variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("Network communication failed:"));
let source = std::error::Error::source(&signer_error);
assert!(source.is_some());
}
#[test]
fn test_serialization_error_conversion() {
let invalid_json = r#"{"invalid": json syntax"#;
let json_error = serde_json::from_str::<serde_json::Value>(invalid_json).unwrap_err();
let signer_error = SignerError::from(json_error);
match signer_error {
SignerError::Serialization(_) => {} other => panic!("Expected Serialization variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("JSON serialization failed:"));
let source = std::error::Error::source(&signer_error);
assert!(source.is_some());
}
#[test]
fn test_file_io_error_conversion() {
let io_error = IoError::new(ErrorKind::NotFound, "File not found");
let signer_error = SignerError::from(io_error);
match signer_error {
SignerError::FileIo(_) => {} other => panic!("Expected FileIo variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("File I/O operation failed:"));
assert!(formatted.contains("File not found"));
let source = std::error::Error::source(&signer_error);
assert!(source.is_some());
assert_eq!(source.unwrap().to_string(), "File not found");
}
#[test]
fn test_bip39_error_conversion() {
let bip39_error = bip39::Error::BadWordCount(5);
let signer_error = SignerError::from(bip39_error);
match &signer_error {
SignerError::InvalidMnemonic(msg) => {
assert!(msg.contains("BIP39 validation failed"));
assert!(msg.contains("word count"));
}
other => panic!("Expected InvalidMnemonic variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("Invalid mnemonic phrase:"));
assert!(formatted.contains("BIP39 validation failed"));
}
#[test]
fn test_elements_encode_error_conversion() {
let elements_error = elements::encode::Error::ParseFailed("Invalid transaction format");
let signer_error = SignerError::from(elements_error);
match &signer_error {
SignerError::InvalidTransaction(msg) => {
assert!(msg.contains("Elements transaction encoding failed"));
assert!(msg.contains("Invalid transaction format"));
}
other => panic!("Expected InvalidTransaction variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("Invalid transaction structure:"));
assert!(formatted.contains("Elements transaction encoding failed"));
}
#[test]
fn test_error_chain_preservation() {
let inner_error = IoError::new(ErrorKind::PermissionDenied, "Access denied");
let signer_error = SignerError::from(inner_error);
let source = std::error::Error::source(&signer_error);
assert!(source.is_some());
assert_eq!(source.unwrap().to_string(), "Access denied");
let formatted = format!("{}", signer_error);
assert!(formatted.contains("File I/O operation failed"));
assert!(formatted.contains("Access denied"));
}
#[test]
fn test_error_send_sync_traits() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<SignerError>();
assert_sync::<SignerError>();
}
#[test]
fn test_error_static_lifetime() {
fn assert_static<T: 'static>() {}
assert_static::<SignerError>();
}
#[test]
fn test_all_error_variants_display() {
let errors = vec![
SignerError::Lwk("test lwk error".to_string()),
SignerError::InvalidMnemonic("test mnemonic error".to_string()),
SignerError::HexParse(hex::decode("invalid_hex_zz").unwrap_err()),
SignerError::InvalidTransaction("test transaction error".to_string()),
SignerError::Network(reqwest::Client::new().get("http://").build().unwrap_err()),
SignerError::Serialization(
serde_json::from_str::<serde_json::Value>(r#"{"invalid": json"#).unwrap_err(),
),
SignerError::FileIo(IoError::new(ErrorKind::NotFound, "test file error")),
];
for error in errors {
let display_str = format!("{}", error);
assert!(
!display_str.is_empty(),
"Error display should not be empty: {:?}",
error
);
let debug_str = format!("{:?}", error);
assert!(
!debug_str.is_empty(),
"Error debug should not be empty: {:?}",
error
);
}
}
#[test]
fn test_error_message_preservation() {
let test_cases = vec![
(
"LWK operation failed with code 123",
SignerError::Lwk("LWK operation failed with code 123".to_string()),
),
(
"Mnemonic has invalid checksum",
SignerError::InvalidMnemonic("Mnemonic has invalid checksum".to_string()),
),
(
"Transaction missing required inputs",
SignerError::InvalidTransaction("Transaction missing required inputs".to_string()),
),
];
for (expected_msg, error) in test_cases {
let formatted = format!("{}", error);
assert!(
formatted.contains(expected_msg),
"Error message should contain '{}', got: '{}'",
expected_msg,
formatted
);
}
}
#[test]
fn test_error_propagation_through_result() {
fn level_3() -> Result<(), SignerError> {
Err(SignerError::Lwk("Deep error".to_string()))
}
fn level_2() -> Result<(), SignerError> {
level_3()?;
Ok(())
}
fn level_1() -> Result<(), SignerError> {
level_2()?;
Ok(())
}
let result = level_1();
assert!(result.is_err());
match result.unwrap_err() {
SignerError::Lwk(msg) => assert_eq!(msg, "Deep error"),
other => panic!("Expected Lwk error, got: {:?}", other),
}
}
#[test]
fn test_error_conversion_preserves_context() {
let hex_result: Result<Vec<u8>, hex::FromHexError> = hex::decode("invalid_hex_zz");
let hex_error = hex_result.unwrap_err();
let signer_error = SignerError::from(hex_error);
let formatted = format!("{}", signer_error);
assert!(formatted.contains("Hex parsing failed"));
let io_error = IoError::new(
ErrorKind::PermissionDenied,
"Cannot write to read-only file",
);
let signer_error = SignerError::from(io_error);
let formatted = format!("{}", signer_error);
assert!(formatted.contains("File I/O operation failed"));
assert!(formatted.contains("Cannot write to read-only file"));
}
#[test]
fn test_error_debug_format_completeness() {
let error = SignerError::InvalidMnemonic("Test mnemonic error with details".to_string());
let debug_str = format!("{:#?}", error);
assert!(debug_str.contains("InvalidMnemonic"));
assert!(debug_str.contains("Test mnemonic error with details"));
}
#[test]
fn test_multiple_error_conversions() {
let test_cases = vec![
"invalid_hex_zz", "abc", ];
for invalid_hex in test_cases {
let hex_error = hex::decode(invalid_hex).unwrap_err();
let signer_error = SignerError::from(hex_error);
match signer_error {
SignerError::HexParse(_) => {} other => panic!("Expected HexParse variant, got: {:?}", other),
}
let formatted = format!("{}", signer_error);
assert!(formatted.starts_with("Hex parsing failed:"));
}
}
#[test]
fn test_error_equality_and_comparison() {
let error1 = SignerError::Lwk("same message".to_string());
let error2 = SignerError::Lwk("same message".to_string());
let error3 = SignerError::Lwk("different message".to_string());
let debug1 = format!("{:?}", error1);
let debug2 = format!("{:?}", error2);
let debug3 = format!("{:?}", error3);
assert_eq!(debug1, debug2);
assert_ne!(debug1, debug3);
}
}