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
73/// Errors that can arise when working with BIP‑39 helpers.
74#[derive(Error, Debug)]
75pub enum Bip39Error {
76    #[error("InvalidWordCount({0})")]
77    InvalidWordCount(usize),
78    #[error("Mnemonic({0})")]
79    Mnemonic(String),
80}
81
82/// Signing-related errors (keys, I/O for secrets, signature placement).
83#[derive(Error, Debug)]
84pub enum SignError {
85    #[error("InvalidBase58")]
86    InvalidBase58,
87    #[error("InvalidPubkeyFormat")]
88    InvalidPubkeyFormat,
89    #[error("InvalidKeyLength")]
90    InvalidKeyLength,
91    #[error("SigningFailed({0})")]
92    SigningFailed(String),
93
94    #[error("SignerKeyNotFound")]
95    SignerKeyNotFound,
96
97    #[error("SigningNotRequired")]
98    SigningNotRequiredForKey,
99
100    #[error("JsonParse({0})")]
101    JsonParse(#[source] serde_json::Error),
102}
103
104/// Signature verification domain errors.
105#[derive(Error, Debug)]
106pub enum VerifyError {
107    #[error("Base58Decode({0})")]
108    Base58Decode(#[from] bs58::decode::Error),
109    #[error("InvalidSigLen({0})")]
110    InvalidSignatureLength(usize),
111    #[error("InvalidPubkeyLen({0})")]
112    InvalidPubkeyLength(usize),
113    #[error("InvalidSigFormat")]
114    InvalidSignatureFormat,
115    #[error("InvalidPubkeyFormat")]
116    InvalidPubkeyFormat,
117    #[error("VerifyFailed")]
118    VerificationFailed,
119}
120
121/// Keypair construction errors (seed handling).
122#[derive(Error, Debug)]
123pub enum KeypairError {
124    #[error("SeedTooShort({0})")]
125    SeedTooShort(usize),
126    #[error("SeedSlice({0})")]
127    SeedSlice(&'static str),
128}
129
130/// Wallet generation errors.
131#[derive(Error, Debug)]
132pub enum GenError {
133    #[error("InvalidSeedLength")]
134    InvalidSeedLength,
135    #[error("CryptoError({0})")]
136    CryptoError(String),
137    #[error("DerivationPath({0})")]
138    InvalidDerivationPath(String),
139}
140
141/// High-level transaction parsing errors (UI formats, textual fields).
142#[derive(Debug, Error)]
143pub enum TransactionParseError {
144    #[error("Base64({0})")]
145    InvalidBase64(String),
146    #[error("Base58({0})")]
147    InvalidBase58(String),
148    #[error("InstructionData({0})")]
149    InvalidInstructionData(String),
150    #[error("PubkeyFormat({0})")]
151    InvalidPubkeyFormat(String),
152    #[error("SigLen({0})")]
153    InvalidSignatureLength(usize),
154    #[error("PubkeyLen({0})")]
155    InvalidPubkeyLength(usize),
156    #[error("SigFormat({0})")]
157    InvalidSignatureFormat(String),
158
159    #[error("BlockhashLen({0})")]
160    InvalidBlockhashLength(usize),
161    #[error("BlockhashFormat({0})")]
162    InvalidBlockhashFormat(String),
163    #[error("Format({0})")]
164    InvalidFormat(String),
165    #[error("Serialization({0})")]
166    Serialization(String),
167}
168
169/// Low-level deserialization errors (binary transaction decoding).
170#[derive(Debug, Error)]
171pub enum DeserializeError {
172    #[error("Deserialize({0})")]
173    Deserialization(String),
174}
175
176/// Adapter-level I/O errors (files, stdin/stdout) with optional path context.
177#[derive(Debug, Error)]
178pub enum IoError {
179    Io(#[from] io::Error),
180
181    /// `path=None` denotes stdin/stdout
182    IoWithPath {
183        #[source]
184        source: std::io::Error,
185        path: Option<String>,
186    },
187}
188
189impl fmt::Display for IoError {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match self {
192            IoError::Io(e) => write!(f, "io({})", e),
193            IoError::IoWithPath { source, path } => {
194                let p = path.as_deref().unwrap_or("stdio");
195                write!(f, "io({}: {})", p, source)
196            }
197        }
198    }
199}
200
201impl IoError {
202    pub fn kind(&self) -> io::ErrorKind {
203        match self {
204            IoError::Io(err) => err.kind(),
205            IoError::IoWithPath { source, .. } => source.kind(),
206        }
207    }
208
209    pub fn with_path(source: io::Error, path: impl Into<String>) -> Self {
210        Self::IoWithPath {
211            source,
212            path: Some(path.into()),
213        }
214    }
215
216    pub fn stdio(source: io::Error) -> Self {
217        Self::IoWithPath { source, path: None }
218    }
219}
220
221impl AsExitCode for IoError {
222    fn as_exit_code(&self) -> i32 {
223        use io::ErrorKind::*;
224        match self.kind() {
225            NotFound => ExitCode::NoInput.as_i32(),
226            InvalidInput => ExitCode::Usage.as_i32(),
227            PermissionDenied | AlreadyExists => ExitCode::IoErr.as_i32(),
228            _ => ExitCode::IoErr.as_i32(),
229        }
230    }
231}
232
233impl AsExitCode for ToolError {
234    fn as_exit_code(&self) -> i32 {
235        match self {
236            ToolError::InvalidInput(_) => ExitCode::Usage.as_i32(),
237            ToolError::Io(err) => err.as_exit_code(),
238            ToolError::FileExists { .. } => ExitCode::IoErr.as_i32(),
239            ToolError::Bip39(_)
240            | ToolError::Base58(_)
241            | ToolError::Sign(_)
242            | ToolError::Keypair(_)
243            | ToolError::Gen(_)
244            | ToolError::Verify(_)
245            | ToolError::Deserialize(_)
246            | ToolError::TransactionParse(_) => ExitCode::DataErr.as_i32(),
247        }
248    }
249}