btpc_core/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6/// Broad, stable categories used by adapters to map core failures.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8#[non_exhaustive]
9pub enum ErrorCategory {
10    /// Filesystem or other I/O failure.
11    Io,
12    /// Invalid bencode syntax.
13    BencodeSyntax,
14    /// Parseable but non-canonical bencode.
15    BencodeCanonical,
16    /// Invalid metainfo field or cross-field relationship.
17    Metainfo,
18    /// A configured resource limit was exceeded.
19    ResourceLimit,
20    /// A requested feature or policy is unsupported.
21    Unsupported,
22    /// Payload data did not match metainfo.
23    Verification,
24    /// The operation was cancelled.
25    Cancelled,
26}
27
28/// Errors returned by the BTPC core library.
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum Error {
32    /// An I/O operation failed for a path.
33    Io {
34        /// Path associated with the failed operation.
35        path: PathBuf,
36        /// Underlying I/O error.
37        source: io::Error,
38    },
39    /// Bencode syntax is invalid.
40    BencodeSyntax {
41        /// Byte offset where the violation was detected.
42        offset: usize,
43        /// Human-readable explanation.
44        message: String,
45    },
46    /// Bencode is parseable but not canonical.
47    BencodeCanonical {
48        /// Byte offset where the violation was detected.
49        offset: usize,
50        /// Human-readable explanation.
51        message: String,
52    },
53    /// A metainfo field is invalid.
54    Metainfo {
55        /// Dot-separated bencode field path.
56        field: String,
57        /// Human-readable explanation.
58        message: String,
59    },
60    /// A parser or allocation resource limit was exceeded.
61    ResourceLimit {
62        /// Stable name of the configured limit.
63        limit: &'static str,
64        /// Observed or requested amount.
65        actual: usize,
66        /// Configured maximum amount.
67        maximum: usize,
68    },
69    /// A requested feature or policy is unsupported.
70    Unsupported {
71        /// Human-readable explanation.
72        message: String,
73    },
74    /// Payload verification found a mismatch.
75    Verification {
76        /// Optional payload path associated with the mismatch.
77        path: Option<PathBuf>,
78        /// Human-readable explanation.
79        message: String,
80    },
81    /// The operation was cancelled.
82    Cancelled,
83}
84
85impl Error {
86    /// Creates an I/O error with path context.
87    pub fn io(path: impl Into<PathBuf>, source: io::Error) -> Self {
88        Self::Io {
89            path: path.into(),
90            source,
91        }
92    }
93
94    /// Creates a bencode syntax error.
95    pub fn bencode_syntax(offset: usize, message: impl Into<String>) -> Self {
96        Self::BencodeSyntax {
97            offset,
98            message: message.into(),
99        }
100    }
101
102    /// Creates a canonical bencode error.
103    pub fn bencode_canonical(offset: usize, message: impl Into<String>) -> Self {
104        Self::BencodeCanonical {
105            offset,
106            message: message.into(),
107        }
108    }
109
110    /// Creates a metainfo field error.
111    pub fn metainfo_field(field: impl Into<String>, message: impl Into<String>) -> Self {
112        Self::Metainfo {
113            field: field.into(),
114            message: message.into(),
115        }
116    }
117
118    /// Creates a resource-limit error.
119    pub(crate) const fn resource_limit(limit: &'static str, actual: usize, maximum: usize) -> Self {
120        Self::ResourceLimit {
121            limit,
122            actual,
123            maximum,
124        }
125    }
126
127    /// Creates an unsupported-feature error.
128    pub fn unsupported(message: impl Into<String>) -> Self {
129        Self::Unsupported {
130            message: message.into(),
131        }
132    }
133
134    /// Creates a payload verification mismatch.
135    pub fn verification_mismatch(path: Option<PathBuf>, message: impl Into<String>) -> Self {
136        Self::Verification {
137            path,
138            message: message.into(),
139        }
140    }
141
142    /// Creates a cancellation error.
143    #[must_use]
144    pub const fn cancelled() -> Self {
145        Self::Cancelled
146    }
147
148    /// Returns the stable category for this error.
149    #[must_use]
150    pub const fn category(&self) -> ErrorCategory {
151        match self {
152            Self::Io { .. } => ErrorCategory::Io,
153            Self::BencodeSyntax { .. } => ErrorCategory::BencodeSyntax,
154            Self::BencodeCanonical { .. } => ErrorCategory::BencodeCanonical,
155            Self::Metainfo { .. } => ErrorCategory::Metainfo,
156            Self::ResourceLimit { .. } => ErrorCategory::ResourceLimit,
157            Self::Unsupported { .. } => ErrorCategory::Unsupported,
158            Self::Verification { .. } => ErrorCategory::Verification,
159            Self::Cancelled => ErrorCategory::Cancelled,
160        }
161    }
162
163    /// Returns the associated byte offset, when present.
164    #[must_use]
165    pub const fn offset(&self) -> Option<usize> {
166        match self {
167            Self::BencodeSyntax { offset, .. } | Self::BencodeCanonical { offset, .. } => {
168                Some(*offset)
169            }
170            _ => None,
171        }
172    }
173
174    /// Returns the associated metainfo field path, when present.
175    #[must_use]
176    pub fn field(&self) -> Option<&str> {
177        match self {
178            Self::Metainfo { field, .. } => Some(field),
179            _ => None,
180        }
181    }
182
183    /// Returns the associated filesystem path, when present.
184    #[must_use]
185    pub fn path(&self) -> Option<&Path> {
186        match self {
187            Self::Io { path, .. } => Some(path),
188            Self::Verification { path, .. } => path.as_deref(),
189            _ => None,
190        }
191    }
192
193    /// Returns the stable resource-limit name, when present.
194    #[must_use]
195    pub const fn limit(&self) -> Option<&'static str> {
196        match self {
197            Self::ResourceLimit { limit, .. } => Some(limit),
198            _ => None,
199        }
200    }
201
202    /// Returns the observed and maximum resource amounts, when present.
203    #[must_use]
204    pub const fn actual_and_maximum(&self) -> Option<(usize, usize)> {
205        match self {
206            Self::ResourceLimit {
207                actual, maximum, ..
208            } => Some((*actual, *maximum)),
209            _ => None,
210        }
211    }
212}
213
214impl fmt::Display for Error {
215    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match self {
217            Self::Io { path, source } => {
218                write!(formatter, "I/O error for {}: {source}", path.display())
219            }
220            Self::BencodeSyntax { offset, message } => {
221                write!(
222                    formatter,
223                    "bencode syntax error at byte {offset}: {message}"
224                )
225            }
226            Self::BencodeCanonical { offset, message } => write!(
227                formatter,
228                "non-canonical bencode at byte {offset}: {message}"
229            ),
230            Self::Metainfo { field, message } => {
231                write!(formatter, "invalid metainfo field {field}: {message}")
232            }
233            Self::ResourceLimit {
234                limit,
235                actual,
236                maximum,
237            } => write!(
238                formatter,
239                "resource limit exceeded for {limit}: {actual} > {maximum}"
240            ),
241            Self::Unsupported { message } => write!(formatter, "unsupported: {message}"),
242            Self::Verification { path, message } => {
243                if let Some(path) = path {
244                    write!(
245                        formatter,
246                        "verification mismatch for {}: {message}",
247                        path.display()
248                    )
249                } else {
250                    write!(formatter, "verification mismatch: {message}")
251                }
252            }
253            Self::Cancelled => formatter.write_str("operation cancelled"),
254        }
255    }
256}
257
258impl StdError for Error {
259    fn source(&self) -> Option<&(dyn StdError + 'static)> {
260        match self {
261            Self::Io { source, .. } => Some(source),
262            _ => None,
263        }
264    }
265}
266
267/// Convenient result alias for BTPC core operations.
268pub type Result<T> = std::result::Result<T, Error>;