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    /// Not enough disk space for the operation.
68    #[error("insufficient disk space: {0}")]
69    InsufficientDiskSpace(String),
70
71    /// Cost estimation could not reach a representative quote.
72    ///
73    /// Returned by [`crate::data::Client::estimate_upload_cost`] when every
74    /// sampled chunk address reported `AlreadyStored`, so the network price
75    /// for the remainder of the file cannot be inferred from a sample.
76    /// The attached message describes how many addresses were tried.
77    #[error("cost estimation inconclusive: {0}")]
78    CostEstimationInconclusive(String),
79
80    /// Upload partially succeeded -- some chunks stored, some failed after retries.
81    ///
82    /// The `stored` addresses can be used for progress tracking and resume.
83    #[error(
84        "partial upload: {stored_count}/{total_chunks} stored, {failed_count} failed: {reason}"
85    )]
86    PartialUpload {
87        /// Addresses of successfully stored chunks.
88        stored: Vec<[u8; 32]>,
89        /// Number of successfully stored chunks.
90        stored_count: usize,
91        /// Addresses and error messages of chunks that failed after retries.
92        failed: Vec<([u8; 32], String)>,
93        /// Number of failed chunks.
94        failed_count: usize,
95        /// Total number of chunks the upload was attempting to store.
96        total_chunks: usize,
97        /// Root cause description.
98        reason: String,
99    },
100}
101
102// ant-node is only linked when the `devnet` feature is on, so the
103// blanket `From` impl follows that gate. LocalDevnet maps node errors
104// to `Error::Network` via this conversion; default builds never see it.
105#[cfg(feature = "devnet")]
106impl From<ant_node::Error> for Error {
107    fn from(e: ant_node::Error) -> Self {
108        Self::Network(e.to_string())
109    }
110}
111
112#[cfg(test)]
113#[allow(clippy::unwrap_used, clippy::expect_used)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_display_network() {
119        let err = Error::Network("connection refused".to_string());
120        assert_eq!(err.to_string(), "network error: connection refused");
121    }
122
123    #[test]
124    fn test_display_storage() {
125        let err = Error::Storage("disk full".to_string());
126        assert_eq!(err.to_string(), "storage error: disk full");
127    }
128
129    #[test]
130    fn test_display_payment() {
131        let err = Error::Payment("insufficient funds".to_string());
132        assert_eq!(err.to_string(), "payment error: insufficient funds");
133    }
134
135    #[test]
136    fn test_display_protocol() {
137        let err = Error::Protocol("invalid message".to_string());
138        assert_eq!(err.to_string(), "protocol error: invalid message");
139    }
140
141    #[test]
142    fn test_display_invalid_data() {
143        let err = Error::InvalidData("bad hash".to_string());
144        assert_eq!(err.to_string(), "invalid data: bad hash");
145    }
146
147    #[test]
148    fn test_display_serialization() {
149        let err = Error::Serialization("decode failed".to_string());
150        assert_eq!(err.to_string(), "serialization error: decode failed");
151    }
152
153    #[test]
154    fn test_display_crypto() {
155        let err = Error::Crypto("key mismatch".to_string());
156        assert_eq!(err.to_string(), "crypto error: key mismatch");
157    }
158
159    #[test]
160    fn test_display_io() {
161        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
162        let err = Error::Io(io_err);
163        assert_eq!(err.to_string(), "I/O error: file missing");
164    }
165
166    #[test]
167    fn test_display_config() {
168        let err = Error::Config("bad value".to_string());
169        assert_eq!(err.to_string(), "configuration error: bad value");
170    }
171
172    #[test]
173    fn test_display_timeout() {
174        let err = Error::Timeout("30s elapsed".to_string());
175        assert_eq!(err.to_string(), "timeout: 30s elapsed");
176    }
177
178    #[test]
179    fn test_display_insufficient_peers() {
180        let err = Error::InsufficientPeers("need 5, got 2".to_string());
181        assert_eq!(err.to_string(), "insufficient peers: need 5, got 2");
182    }
183
184    #[test]
185    fn test_display_signature_verification() {
186        let err = Error::SignatureVerification("invalid sig".to_string());
187        assert_eq!(
188            err.to_string(),
189            "signature verification failed: invalid sig"
190        );
191    }
192
193    #[test]
194    fn test_display_encryption() {
195        let err = Error::Encryption("decrypt failed".to_string());
196        assert_eq!(err.to_string(), "encryption error: decrypt failed");
197    }
198
199    #[test]
200    fn test_display_insufficient_disk_space() {
201        let err = Error::InsufficientDiskSpace("need 100 MB but only 10 MB available".to_string());
202        assert_eq!(
203            err.to_string(),
204            "insufficient disk space: need 100 MB but only 10 MB available"
205        );
206    }
207
208    #[test]
209    fn test_display_cost_estimation_inconclusive() {
210        let err = Error::CostEstimationInconclusive(
211            "sampled 5 addresses, all already stored".to_string(),
212        );
213        assert_eq!(
214            err.to_string(),
215            "cost estimation inconclusive: sampled 5 addresses, all already stored"
216        );
217    }
218
219    #[test]
220    fn test_from_io_error() {
221        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
222        let err: Error = io_err.into();
223        assert!(matches!(err, Error::Io(_)));
224    }
225}