spark_rust/
error.rs

1//! # Error handling for the Spark Wallet SDK
2//!
3//! This module defines the error types used throughout the Spark Wallet SDK.
4//! The error system uses a hierarchical structure, with the top-level `SparkSdkError`
5//! enum wrapping various subcategory errors.
6//!
7//! ## Error Structure
8//!
9//! The error structure follows a domain-driven design:
10//!
11//! ```text
12//! SparkSdkError
13//! ├── NetworkError - Network communication and RPC issues
14//! ├── WalletError - Wallet operations and leaf management
15//! ├── CryptoError - Cryptographic and signing operations
16//! ├── ValidationError - Input validation and verification
17//! ├── TransactionError - Bitcoin transaction handling
18//! ├── IoError - Input/output and serialization
19//! ├── InternalError - SDK initialization and internal processing
20//! └── General - General errors with a string message
21//! ```
22//!
23//! ## Error Handling
24//!
25//! Most functions in the SDK return `Result<T, SparkSdkError>`, allowing you to handle
26//! errors with standard Rust error handling patterns:
27//!
28//! ```rust
29//! use spark_rust::SparkSdk;
30//! use spark_rust::SparkNetwork;
31//! use spark_rust::error::SparkSdkError;
32//! use spark_rust::signer::default_signer::DefaultSigner;
33//! use spark_rust::signer::traits::SparkSigner;
34//!
35//! async fn example() -> Result<(), SparkSdkError> {
36//!     let mnemonic = "abandon ability able about above absent absorb abstract absurd abuse access accident";
37//!     let signer = DefaultSigner::from_mnemonic(mnemonic, SparkNetwork::Regtest).await?;
38//!     let sdk = SparkSdk::new(SparkNetwork::Regtest, signer).await?;
39//!     
40//!     // When you need to handle specific error categories
41//!     match sdk.generate_deposit_address().await {
42//!         Ok(address) => println!("Deposit address: {}", address.deposit_address),
43//!         Err(SparkSdkError::Network(e)) => println!("Network error: {}", e),
44//!         Err(SparkSdkError::Validation(e)) => println!("Validation error: {}", e),
45//!         Err(e) => println!("Other error: {}", e),
46//!     }
47//!     
48//!     Ok(())
49//! }
50//! ```
51//!
52//! ## Error Conversion
53//!
54//! The error system implements `From` traits for common error types, allowing
55//! automatic conversion using the `?` operator:
56//!
57//! - `String` and `&str` convert to `SparkSdkError::General`
58//! - `tonic::transport::Error` converts to `SparkSdkError::Network`
59//! - `secp256k1::Error` converts to `SparkSdkError::Crypto`
60//!
61//! This makes error propagation and handling more ergonomic throughout the SDK.
62
63use bitcoin::secp256k1;
64use lightning_invoice::ParseOrSemanticError;
65use std::io;
66use thiserror::Error;
67use tonic::codegen::http::uri::InvalidUri;
68
69// Re-export all errors from subcategories defined below
70pub use self::crypto::CryptoError;
71pub use self::internal::InternalError;
72pub use self::io_error::IoError;
73pub use self::network::NetworkError;
74pub use self::transaction::TransactionError;
75pub use self::validation::ValidationError;
76pub use self::wallet::WalletError;
77
78/// Main error type for the Spark Wallet SDK.
79///
80/// This enum wraps all subcategory errors and provides a uniform error
81/// interface for SDK functions. Most SDK methods return `Result<T, SparkSdkError>`.
82///
83/// When handling errors, you can either match on the specific error type:
84///
85/// ```rust
86/// # use spark_rust::error::SparkSdkError;
87/// # fn example(error: SparkSdkError) {
88/// match error {
89///     SparkSdkError::Network(network_error) => {
90///         // Handle network-specific errors
91///         println!("Network error: {}", network_error);
92///     },
93///     SparkSdkError::Wallet(wallet_error) => {
94///         // Handle wallet-specific errors
95///         println!("Wallet error: {}", wallet_error);
96///     },
97///     // Other error categories...
98///     _ => println!("Other error: {}", error),
99/// }
100/// # }
101/// ```
102///
103/// Or simply use the error's `Display` implementation for general handling:
104///
105/// ```rust
106/// # use spark_rust::error::SparkSdkError;
107/// # fn example(error: SparkSdkError) {
108/// println!("Error occurred: {}", error);
109/// # }
110/// ```
111#[derive(Debug, Error)]
112pub enum SparkSdkError {
113    /// Network communication errors, including RPC calls and HTTP requests.
114    #[error(transparent)]
115    Network(#[from] NetworkError),
116
117    /// Wallet operation errors, including leaf management and funds handling.
118    #[error(transparent)]
119    Wallet(#[from] WalletError),
120
121    /// Cryptographic operation errors, including signing and key management.
122    #[error(transparent)]
123    Crypto(#[from] CryptoError),
124
125    /// Input validation errors, parameter checking, and verification issues.
126    #[error(transparent)]
127    Validation(#[from] ValidationError),
128
129    /// Bitcoin transaction errors, including creation and processing issues.
130    #[error(transparent)]
131    Transaction(#[from] TransactionError),
132
133    /// Input/output errors, including serialization and file operations.
134    #[error(transparent)]
135    Io(#[from] IoError),
136
137    /// SDK internal errors, typically for initialization or processing problems.
138    #[error(transparent)]
139    Internal(#[from] InternalError),
140
141    /// General errors with a string message for cases that don't fit other categories.
142    #[error("General error: {0}")]
143    General(String),
144}
145
146/// Network-related errors for RPC, HTTP, and other communication issues.
147pub mod network {
148    use super::*;
149
150    /// Errors related to network communication, including RPC calls and HTTP requests.
151    #[derive(Debug, Error)]
152    pub enum NetworkError {
153        /// gRPC transport layer errors.
154        #[error("Transport error: {0}")]
155        Transport(#[from] tonic::transport::Error),
156
157        /// HTTP client errors from reqwest.
158        #[error("HTTP error: {0}")]
159        Http(#[from] reqwest::Error),
160
161        /// gRPC status errors.
162        #[error("Status error: {0}")]
163        Status(#[from] tonic::Status),
164
165        /// Invalid URI format errors.
166        #[error("Invalid URI: {0}")]
167        InvalidUri(#[from] InvalidUri),
168
169        /// Invalid URL format with optional details.
170        #[error("Invalid URL: {url}")]
171        InvalidUrl {
172            url: String,
173            details: Option<String>,
174        },
175
176        /// RPC connection failures with endpoint information.
177        #[error("RPC connection error with endpoint: {uri}")]
178        RpcConnection {
179            uri: String,
180            details: Option<String>,
181        },
182
183        /// Bitcoin RPC specific errors.
184        #[error("Bitcoin RPC error: {0}")]
185        BitcoinRpc(String),
186
187        /// Invalid GraphQL operation format.
188        #[error("Invalid GraphQL operation: {0}")]
189        InvalidGraphQLOperation(String),
190
191        /// GraphQL query errors.
192        #[error("GraphQL error with query: {0}")]
193        GraphQL(String),
194
195        /// GraphQL request failures with status code.
196        #[error("GraphQL request failed: {status_code}")]
197        GraphQLRequestFailed { status_code: u16 },
198
199        /// Faucet request failures with status code.
200        #[error("Faucet request failed with status: {status_code}")]
201        FaucetRequestFailed { status_code: u16 },
202
203        /// Invalid responses from the server that can't be parsed.
204        #[error("Invalid response from server")]
205        InvalidResponse,
206    }
207}
208
209/// Wallet-related errors for leaf management and funds handling.
210pub mod wallet {
211    use super::*;
212
213    /// Errors related to wallet operations, leaf management, and funds handling.
214    #[derive(Debug, Error)]
215    pub enum WalletError {
216        /// No leaves (UTXOs) found in the wallet.
217        #[error("No leaves found for wallet")]
218        NoLeavesFound,
219
220        /// No leaf with the exact amount requested.
221        #[error("No leaf with exact amount")]
222        NoLeafWithExactAmount,
223
224        /// Insufficient funds during leaf selection.
225        #[error("Leaf selection failed: insufficient funds")]
226        LeafSelectionInsufficientFunds,
227
228        /// No suitable leaves available for the requested operation.
229        #[error("Leaf selection failed: no suitable leaves available")]
230        LeafSelectionNoSuitableLeaves,
231
232        /// Leaf not found with the specified ID.
233        #[error("Leaf not found with id: {0}")]
234        LeafNotFound(String),
235
236        /// Leaf was used for an operation but not found afterward.
237        #[error("Leaf is used for operation but not found after. Leaf ID: {leaf_id}")]
238        LeafNotFoundAfterOperation { leaf_id: String },
239
240        /// Leaf is not of Bitcoin type.
241        #[error("Leaf is not Bitcoin type: {leaf_id}")]
242        LeafIsNotBitcoin { leaf_id: String },
243
244        /// Leaf already exists in the wallet.
245        #[error("Leaf already exists in wallet: {leaf_id}")]
246        LeafAlreadyExistsInWallet { leaf_id: String },
247
248        /// Leaf is not available for use (e.g., locked or in use).
249        #[error("Leaf not available for use: {leaf_id}")]
250        LeafNotAvailableForUse { leaf_id: String },
251
252        /// Leaf is not using the expected lock.
253        #[error("Leaf not using expected lock. Expected: {expected}, Found: {actual}")]
254        LeafNotUsingExpectedLock { expected: String, actual: String },
255
256        /// Leaf not found in the wallet.
257        #[error("Leaf not found in wallet: {leaf_id}")]
258        LeafNotFoundInWallet { leaf_id: String },
259
260        /// Invalid account specified.
261        #[error("Invalid account: {account_id}")]
262        InvalidAccount { account_id: String },
263
264        /// Cooperative exit failed.
265        #[error("Cooperative exit failed: {reason}")]
266        CooperativeExit { reason: String },
267
268        /// Leaf has no parent.
269        #[error("Leaf has no parent: {leaf_id}")]
270        LeafHasNoParent { leaf_id: String },
271
272        /// Leaf parent not found.
273        #[error("Leaf parent not found: {leaf_id}")]
274        LeafParentNotFound { leaf_id: String },
275
276        /// Post refresh node signature length mismatch.
277        #[error("Post refresh node signature length mismatch")]
278        PostRefreshNodeSignatureLengthMismatch,
279    }
280}
281
282/// Cryptographic operation errors for signing and key management.
283pub mod crypto {
284    use spark_cryptography::error::SparkCryptographyError;
285
286    use super::*;
287
288    /// Errors related to cryptographic operations, including signing and key management.
289    #[derive(Debug, Error)]
290    pub enum CryptoError {
291        /// Secp256k1 cryptographic errors.
292        #[error("Signature error: {0}")]
293        Secp256k1(#[from] secp256k1::Error),
294
295        /// FROST threshold signing failures.
296        #[error("FROST signing failed with job id: {job_id}")]
297        FrostSigning { job_id: String },
298
299        /// FROST signature aggregation failures.
300        #[error("FROST aggregation failed")]
301        FrostAggregation,
302
303        /// Invalid hash value.
304        #[error("Invalid hash: {hash}")]
305        InvalidHash { hash: String },
306
307        /// Decryption failures.
308        #[error("Decryption failed")]
309        Decryption,
310
311        /// Secret key not found for a public key.
312        #[error("Secret key not found for public key: {public_key}")]
313        SecretKeyNotFound { public_key: String },
314
315        /// Missing keyshare information for threshold signing.
316        #[error("Missing keyshare information")]
317        MissingKeyshareInfo,
318
319        /// FROST signature not found with the specified job ID.
320        #[error("Could not find FROST signature with job id: {job_id}")]
321        FrostSignatureNotFound { job_id: String },
322
323        /// Invalid key type for the operation.
324        #[error("Invalid key type: {key_type}")]
325        InvalidKeyType { key_type: String },
326
327        /// Invalid seed for key derivation.
328        #[error("Invalid seed")]
329        InvalidSeed,
330
331        /// Child key derivation failures.
332        #[error("Child key derivation failed with path: {derivation_path}")]
333        ChildKeyDerivationError { derivation_path: String },
334
335        /// Missing root node signature shares.
336        #[error("Missing root node signature shares")]
337        MissingRootNodeSignatureShares,
338
339        /// Invalid cryptographic input for operations.
340        #[error("Invalid crypto input: {field}")]
341        InvalidInput { field: String },
342
343        #[from(SparkCryptographyError)]
344        #[error("Spark cryptography error: {0}")]
345        SparkCryptographyError(#[from] SparkCryptographyError),
346    }
347}
348
349/// Validation errors for input checking and verification.
350pub mod validation {
351    use super::*;
352
353    /// Errors related to input validation, parameter checking, and verification.
354    #[derive(Debug, Error)]
355    pub enum ValidationError {
356        /// Invalid configuration parameter.
357        #[error("Invalid configuration: {parameter}")]
358        Config { parameter: String },
359
360        /// Invalid input field value.
361        #[error("Invalid input: {field}")]
362        InvalidInput { field: String },
363
364        /// Invalid function argument.
365        #[error("Invalid argument: {argument}")]
366        InvalidArgument { argument: String },
367
368        /// Invalid Bitcoin address format.
369        #[error("Invalid address: {address}")]
370        InvalidAddress { address: String },
371
372        /// Deposit address validation failures.
373        #[error("Deposit address validation failed: {address}")]
374        DepositAddressValidationFailed { address: String },
375
376        /// Deposit transaction not found in the Bitcoin network.
377        #[error("Deposit transaction not found in network: {txid}")]
378        DepositTransactionNotFoundInNetwork { txid: String },
379
380        /// Invalid keyshare configuration.
381        #[error("Invalid keyshare config with id: {config_id}")]
382        InvalidKeyshareConfig { config_id: String },
383
384        /// Invalid sequence number.
385        #[error("Invalid sequence number: {sequence}")]
386        InvalidSequence { sequence: u64 },
387
388        /// Invalid UUID format.
389        #[error("Invalid UUID: {0}")]
390        InvalidUuid(#[from] uuid::Error),
391
392        /// Invalid BOLT11 lightning invoice format.
393        #[error("Invalid Bolt11 invoice: {0}")]
394        InvalidBolt11Invoice(String),
395
396        /// Transfer claim did not update balance (integration tests only).
397        #[cfg(feature = "integration-tests")]
398        #[error("Transfer claim did not update balance")]
399        TransferClaimDidNotUpdateBalance,
400
401        #[error("Invalid bitcoin network: {0}")]
402        InvalidBitcoinNetwork(String),
403    }
404}
405
406/// Transaction errors for Bitcoin transaction handling.
407pub mod transaction {
408    use super::*;
409
410    /// Errors related to Bitcoin transaction creation, signing, and processing.
411    #[derive(Debug, Error)]
412    pub enum TransactionError {
413        /// Transfer operation failures.
414        #[error("Transfer failed: {reason}")]
415        Transfer { reason: String },
416
417        /// Invalid Bitcoin transaction format.
418        #[error("Invalid bitcoin transaction: {0}")]
419        InvalidBitcoinTransaction(String),
420
421        /// Invalid deposit transaction format.
422        #[error("Invalid deposit transaction: {0}")]
423        InvalidDepositTx(String),
424    }
425}
426
427/// IO errors for serialization and file operations.
428pub mod io_error {
429
430    use super::*;
431
432    /// Errors related to input/output, serialization, and file operations.
433    #[derive(Debug, Error)]
434    pub enum IoError {
435        /// Standard IO errors.
436        #[error("IO error: {0}")]
437        Io(#[from] io::Error),
438
439        /// JSON serialization/deserialization errors.
440        #[error("JSON serialization error: {0}")]
441        SerdeJson(#[from] serde_json::Error),
442
443        /// Hex decoding errors.
444        #[error("Hex decoding error: {0}")]
445        Decoding(#[from] hex::FromHexError),
446
447        /// BOLT11 invoice decoding errors.
448        #[error("Bolt11 invoice decoding error: {0}")]
449        Bolt11InvoiceDecoding(String),
450    }
451}
452
453/// Internal errors for SDK initialization and processing.
454pub mod internal {
455    use super::*;
456
457    /// Errors related to SDK initialization and internal processing.
458    #[derive(Debug, Error)]
459    pub enum InternalError {
460        /// Initialization failures for SDK components.
461        #[error("Initialization failed: {component}")]
462        Initialization { component: String },
463
464        /// Tree creation failures.
465        #[error("Tree creation failed: {reason}")]
466        TreeCreation { reason: String },
467
468        /// Test database query errors (for testing only).
469        #[error("Test database query error: {query}")]
470        TestDatabaseQuery { query: String },
471
472        /// Async task failures.
473        #[error("Async task failed: {task}")]
474        AsyncTask { task: String },
475    }
476}
477
478// Implement From traits for common conversions
479impl From<String> for SparkSdkError {
480    /// Converts a String to a SparkSdkError::General.
481    ///
482    /// This allows passing a string directly as an error:
483    /// ```rust
484    /// # use spark_rust::error::SparkSdkError;
485    /// # fn example() -> Result<(), SparkSdkError> {
486    /// if something_is_wrong() {
487    ///     return Err("Something went wrong".to_string().into());
488    /// }
489    /// # Ok(())
490    /// # }
491    /// # fn something_is_wrong() -> bool { false }
492    /// ```
493    fn from(s: String) -> Self {
494        Self::General(s)
495    }
496}
497
498impl From<&str> for SparkSdkError {
499    /// Converts a string slice to a SparkSdkError::General.
500    ///
501    /// This allows passing a string literal directly as an error:
502    /// ```rust
503    /// # use spark_rust::error::SparkSdkError;
504    /// # fn example() -> Result<(), SparkSdkError> {
505    /// if something_is_wrong() {
506    ///     return Err("Something went wrong".into());
507    /// }
508    /// # Ok(())
509    /// # }
510    /// # fn something_is_wrong() -> bool { false }
511    /// ```
512    fn from(s: &str) -> Self {
513        Self::General(s.to_string())
514    }
515}
516
517// Add direct conversion from tonic transport errors
518impl From<tonic::transport::Error> for SparkSdkError {
519    /// Converts a tonic transport error to a SparkSdkError::Network.
520    fn from(err: tonic::transport::Error) -> Self {
521        Self::Network(NetworkError::Transport(err))
522    }
523}
524
525// Add direct conversion from secp256k1 errors
526impl From<secp256k1::Error> for SparkSdkError {
527    /// Converts a secp256k1 error to a SparkSdkError::Crypto.
528    fn from(err: secp256k1::Error) -> Self {
529        Self::Crypto(CryptoError::Secp256k1(err))
530    }
531}
532
533// Add manual conversion from ParseOrSemanticError
534impl From<ParseOrSemanticError> for IoError {
535    /// Converts a BOLT11 invoice parsing error to an IoError.
536    fn from(err: ParseOrSemanticError) -> Self {
537        Self::Bolt11InvoiceDecoding(err.to_string())
538    }
539}