Skip to main content

ant_core/data/
error.rs

1//! Error types for data operations.
2
3use thiserror::Error;
4
5/// Result type alias using the data Error type.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Errors that can occur in data operations.
9#[derive(Error, Debug)]
10pub enum Error {
11    /// Network operation failed.
12    #[error("network error: {0}")]
13    Network(String),
14
15    /// Storage operation failed.
16    #[error("storage error: {0}")]
17    Storage(String),
18
19    /// Payment operation failed.
20    #[error("payment error: {0}")]
21    Payment(String),
22
23    /// Protocol error.
24    #[error("protocol error: {0}")]
25    Protocol(String),
26
27    /// Invalid data received.
28    #[error("invalid data: {0}")]
29    InvalidData(String),
30
31    /// Serialization error.
32    #[error("serialization error: {0}")]
33    Serialization(String),
34
35    /// Cryptographic error.
36    #[error("crypto error: {0}")]
37    Crypto(String),
38
39    /// I/O error.
40    #[error("I/O error: {0}")]
41    Io(#[from] std::io::Error),
42
43    /// Configuration error.
44    #[error("configuration error: {0}")]
45    Config(String),
46
47    /// Timeout waiting for a response.
48    #[error("timeout: {0}")]
49    Timeout(String),
50
51    /// Insufficient peers for the operation.
52    #[error("insufficient peers: {0}")]
53    InsufficientPeers(String),
54
55    /// BLS signature verification failed.
56    #[error("signature verification failed: {0}")]
57    SignatureVerification(String),
58
59    /// Self-encryption operation failed.
60    #[error("encryption error: {0}")]
61    Encryption(String),
62
63    /// Data already exists on the network — no payment needed.
64    #[error("already stored on network")]
65    AlreadyStored,
66
67    /// A peer's quote `pub_key` does not BLAKE3-hash to the peer ID. The
68    /// storer would reject any `ProofOfPayment` containing this quote, so
69    /// the client drops the response before payment.
70    #[error("bad quote binding from peer {peer_id}: {detail}")]
71    BadQuoteBinding {
72        /// The peer ID we got the quote from (claimed identity).
73        peer_id: String,
74        /// Diagnostic detail (e.g. "BLAKE3(pub_key) = …, peer_id = …").
75        detail: String,
76    },
77
78    /// Not enough disk space for the operation.
79    #[error("insufficient disk space: {0}")]
80    InsufficientDiskSpace(String),
81
82    /// Cost estimation could not reach a representative quote.
83    ///
84    /// Returned by [`crate::data::Client::estimate_upload_cost`] when every
85    /// sampled chunk address reported `AlreadyStored`, so the network price
86    /// for the remainder of the file cannot be inferred from a sample.
87    /// The attached message describes how many addresses were tried.
88    #[error("cost estimation inconclusive: {0}")]
89    CostEstimationInconclusive(String),
90
91    /// Upload partially succeeded -- some chunks stored, some failed after retries.
92    ///
93    /// The `stored` addresses can be used for progress tracking and resume.
94    #[error(
95        "partial upload: {stored_count}/{total_chunks} stored, {failed_count} failed: {reason}"
96    )]
97    PartialUpload {
98        /// Addresses of successfully stored chunks.
99        stored: Vec<[u8; 32]>,
100        /// Number of successfully stored chunks.
101        stored_count: usize,
102        /// Addresses and error messages of chunks that failed after retries.
103        failed: Vec<([u8; 32], String)>,
104        /// Number of failed chunks.
105        failed_count: usize,
106        /// Total number of chunks the upload was attempting to store.
107        total_chunks: usize,
108        /// Root cause description.
109        reason: String,
110    },
111}
112
113// ant-node is only linked when the `devnet` feature is on, so the
114// blanket `From` impl follows that gate. LocalDevnet maps node errors
115// to `Error::Network` via this conversion; default builds never see it.
116#[cfg(feature = "devnet")]
117impl From<ant_node::Error> for Error {
118    fn from(e: ant_node::Error) -> Self {
119        Self::Network(e.to_string())
120    }
121}
122
123#[cfg(test)]
124#[allow(clippy::unwrap_used, clippy::expect_used)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_display_network() {
130        let err = Error::Network("connection refused".to_string());
131        assert_eq!(err.to_string(), "network error: connection refused");
132    }
133
134    #[test]
135    fn test_display_storage() {
136        let err = Error::Storage("disk full".to_string());
137        assert_eq!(err.to_string(), "storage error: disk full");
138    }
139
140    #[test]
141    fn test_display_payment() {
142        let err = Error::Payment("insufficient funds".to_string());
143        assert_eq!(err.to_string(), "payment error: insufficient funds");
144    }
145
146    #[test]
147    fn test_display_protocol() {
148        let err = Error::Protocol("invalid message".to_string());
149        assert_eq!(err.to_string(), "protocol error: invalid message");
150    }
151
152    #[test]
153    fn test_display_invalid_data() {
154        let err = Error::InvalidData("bad hash".to_string());
155        assert_eq!(err.to_string(), "invalid data: bad hash");
156    }
157
158    #[test]
159    fn test_display_serialization() {
160        let err = Error::Serialization("decode failed".to_string());
161        assert_eq!(err.to_string(), "serialization error: decode failed");
162    }
163
164    #[test]
165    fn test_display_crypto() {
166        let err = Error::Crypto("key mismatch".to_string());
167        assert_eq!(err.to_string(), "crypto error: key mismatch");
168    }
169
170    #[test]
171    fn test_display_io() {
172        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
173        let err = Error::Io(io_err);
174        assert_eq!(err.to_string(), "I/O error: file missing");
175    }
176
177    #[test]
178    fn test_display_config() {
179        let err = Error::Config("bad value".to_string());
180        assert_eq!(err.to_string(), "configuration error: bad value");
181    }
182
183    #[test]
184    fn test_display_timeout() {
185        let err = Error::Timeout("30s elapsed".to_string());
186        assert_eq!(err.to_string(), "timeout: 30s elapsed");
187    }
188
189    #[test]
190    fn test_display_insufficient_peers() {
191        let err = Error::InsufficientPeers("need 5, got 2".to_string());
192        assert_eq!(err.to_string(), "insufficient peers: need 5, got 2");
193    }
194
195    #[test]
196    fn test_display_signature_verification() {
197        let err = Error::SignatureVerification("invalid sig".to_string());
198        assert_eq!(
199            err.to_string(),
200            "signature verification failed: invalid sig"
201        );
202    }
203
204    #[test]
205    fn test_display_encryption() {
206        let err = Error::Encryption("decrypt failed".to_string());
207        assert_eq!(err.to_string(), "encryption error: decrypt failed");
208    }
209
210    #[test]
211    fn test_display_insufficient_disk_space() {
212        let err = Error::InsufficientDiskSpace("need 100 MB but only 10 MB available".to_string());
213        assert_eq!(
214            err.to_string(),
215            "insufficient disk space: need 100 MB but only 10 MB available"
216        );
217    }
218
219    #[test]
220    fn test_display_cost_estimation_inconclusive() {
221        let err = Error::CostEstimationInconclusive(
222            "sampled 5 addresses, all already stored".to_string(),
223        );
224        assert_eq!(
225            err.to_string(),
226            "cost estimation inconclusive: sampled 5 addresses, all already stored"
227        );
228    }
229
230    #[test]
231    fn test_from_io_error() {
232        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
233        let err: Error = io_err.into();
234        assert!(matches!(err, Error::Io(_)));
235    }
236}