Skip to main content

solana_tools_lite/
errors.rs

1use std::fmt;
2use std::io;
3use thiserror::Error;
4
5/// Crate-wide result type alias that bubbles up `ToolError` by default.
6pub type Result<T, E = ToolError> = std::result::Result<T, E>;
7
8/// Exit-code contract shared with CLI/front-ends.
9pub trait AsExitCode {
10    fn as_exit_code(&self) -> i32;
11}
12
13#[repr(i32)]
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub enum ExitCode {
16    Usage = 64,
17    DataErr = 65,
18    NoInput = 66,
19    Software = 70,
20    IoErr = 74,
21}
22
23impl ExitCode {
24    pub const fn as_i32(self) -> i32 {
25        self as i32
26    }
27}
28
29/// Top-level error every command bubbles up.
30///
31/// Routing rules
32/// - `Sign` – I/O with context (IoWithPath), key parsing/format/length, signing domain errors
33/// - `TransactionParse` – user input/output format issues (JSON/Base64/Base58, blockhash, etc.)
34/// - `Deserialize` – internal raw transaction deserialization (binary → domain)
35/// - `Bip39`, `Bincode`, `Base58` – wrapped library errors
36/// - `InvalidInput` – CLI-level validation (mutually exclusive args, stdin forbidden, etc.)
37#[derive(Debug, Error)]
38pub enum ToolError {
39    #[error("bip39: {0}")]
40    Bip39(#[from] Bip39Error),
41
42    #[error("base58: {0}")]
43    Base58(#[from] bs58::decode::Error),
44
45    #[error("sign: {0}")]
46    Sign(#[from] SignError),
47
48    #[error("keypair: {0}")]
49    Keypair(#[from] KeypairError),
50
51    #[error("gen: {0}")]
52    Gen(#[from] GenError),
53
54    #[error("tx_parse: {0}")]
55    TransactionParse(#[from] TransactionParseError),
56
57    #[error("deserialize: {0}")]
58    Deserialize(#[from] DeserializeError),
59
60    #[error("io: {0}")]
61    Io(#[from] IoError),
62
63    #[error("verify: {0}")]
64    Verify(#[from] VerifyError),
65
66    #[error("file_exists: {path}")]
67    FileExists { path: String },
68
69    #[error("{0}")]
70    InvalidInput(String),
71
72    #[error("configuration: {0}")]
73    ConfigurationError(String),
74}
75
76/// Errors that can arise when working with BIP‑39 helpers.
77#[derive(Error, Debug)]
78pub enum Bip39Error {
79    #[error("InvalidWordCount({0})")]
80    InvalidWordCount(usize),
81    #[error("Mnemonic({0})")]
82    Mnemonic(String),
83}
84
85/// Signing-related errors (keys, I/O for secrets, signature placement).
86#[derive(Error, Debug)]
87pub enum SignError {
88    #[error("InvalidBase58")]
89    InvalidBase58,
90    #[error("InvalidPubkeyFormat")]
91    InvalidPubkeyFormat,
92    #[error("InvalidKeyLength")]
93    InvalidKeyLength,
94    #[error("SigningFailed({0})")]
95    SigningFailed(String),
96
97    #[error("SignerKeyNotFound")]
98    SignerKeyNotFound,
99
100    #[error("SigningNotRequired")]
101    SigningNotRequiredForKey,
102
103    #[error("JsonParse({0})")]
104    JsonParse(#[source] serde_json::Error),
105}
106
107/// Signature verification domain errors.
108#[derive(Error, Debug)]
109pub enum VerifyError {
110    #[error("Base58Decode({0})")]
111    Base58Decode(#[from] bs58::decode::Error),
112    #[error("InvalidSigLen({0})")]
113    InvalidSignatureLength(usize),
114    #[error("InvalidPubkeyLen({0})")]
115    InvalidPubkeyLength(usize),
116    #[error("InvalidSigFormat")]
117    InvalidSignatureFormat,
118    #[error("InvalidPubkeyFormat")]
119    InvalidPubkeyFormat,
120    #[error("VerifyFailed")]
121    VerificationFailed,
122}
123
124/// Keypair construction errors (seed handling).
125#[derive(Error, Debug)]
126pub enum KeypairError {
127    #[error("SeedTooShort({0})")]
128    SeedTooShort(usize),
129    #[error("SeedSlice({0})")]
130    SeedSlice(&'static str),
131}
132
133/// Wallet generation errors.
134#[derive(Error, Debug)]
135pub enum GenError {
136    #[error("InvalidSeedLength")]
137    InvalidSeedLength,
138    #[error("CryptoError({0})")]
139    CryptoError(String),
140    #[error("DerivationPath({0})")]
141    InvalidDerivationPath(String),
142}
143
144/// High-level transaction parsing errors (UI formats, textual fields).
145#[derive(Debug, Error)]
146pub enum TransactionParseError {
147    #[error("Base64({0})")]
148    InvalidBase64(String),
149    #[error("Base58({0})")]
150    InvalidBase58(String),
151    #[error("InstructionData({0})")]
152    InvalidInstructionData(String),
153    #[error("PubkeyFormat({0})")]
154    InvalidPubkeyFormat(String),
155    #[error("SigLen({0})")]
156    InvalidSignatureLength(usize),
157    #[error("PubkeyLen({0})")]
158    InvalidPubkeyLength(usize),
159    #[error("SigFormat({0})")]
160    InvalidSignatureFormat(String),
161
162    #[error("BlockhashLen({0})")]
163    InvalidBlockhashLength(usize),
164    #[error("BlockhashFormat({0})")]
165    InvalidBlockhashFormat(String),
166    #[error("Format({0})")]
167    InvalidFormat(String),
168    #[error("Serialization({0})")]
169    Serialization(String),
170}
171
172/// Low-level deserialization errors (binary transaction decoding).
173#[derive(Debug, Error)]
174pub enum DeserializeError {
175    #[error("Deserialize({0})")]
176    Deserialization(String),
177}
178
179/// Adapter-level I/O errors (files, stdin/stdout) with optional path context.
180#[derive(Debug, Error)]
181pub enum IoError {
182    Io(#[from] io::Error),
183
184    /// `path=None` denotes stdin/stdout
185    IoWithPath {
186        #[source]
187        source: std::io::Error,
188        path: Option<String>,
189    },
190}
191
192impl fmt::Display for IoError {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match self {
195            IoError::Io(e) => write!(f, "io({})", e),
196            IoError::IoWithPath { source, path } => {
197                let p = path.as_deref().unwrap_or("stdio");
198                write!(f, "io({}: {})", p, source)
199            }
200        }
201    }
202}
203
204impl IoError {
205    pub fn kind(&self) -> io::ErrorKind {
206        match self {
207            IoError::Io(err) => err.kind(),
208            IoError::IoWithPath { source, .. } => source.kind(),
209        }
210    }
211
212    pub fn with_path(source: io::Error, path: impl Into<String>) -> Self {
213        Self::IoWithPath {
214            source,
215            path: Some(path.into()),
216        }
217    }
218
219    pub fn stdio(source: io::Error) -> Self {
220        Self::IoWithPath { source, path: None }
221    }
222}
223
224impl AsExitCode for IoError {
225    fn as_exit_code(&self) -> i32 {
226        use io::ErrorKind::*;
227        match self.kind() {
228            NotFound => ExitCode::NoInput.as_i32(),
229            InvalidInput => ExitCode::Usage.as_i32(),
230            PermissionDenied | AlreadyExists => ExitCode::IoErr.as_i32(),
231            _ => ExitCode::IoErr.as_i32(),
232        }
233    }
234}
235
236impl AsExitCode for ToolError {
237    fn as_exit_code(&self) -> i32 {
238        match self {
239            ToolError::InvalidInput(_) => ExitCode::Usage.as_i32(),
240            ToolError::ConfigurationError(_) => ExitCode::Software.as_i32(),
241            ToolError::Io(err) => err.as_exit_code(),
242            ToolError::FileExists { .. } => ExitCode::IoErr.as_i32(),
243            ToolError::Bip39(_)
244            | ToolError::Base58(_)
245            | ToolError::Sign(_)
246            | ToolError::Keypair(_)
247            | ToolError::Gen(_)
248            | ToolError::Verify(_)
249            | ToolError::Deserialize(_)
250            | ToolError::TransactionParse(_) => ExitCode::DataErr.as_i32(),
251        }
252    }
253}