1use std::error::Error as StdError;
2use std::fmt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8#[non_exhaustive]
9pub enum ErrorCategory {
10 Io,
12 BencodeSyntax,
14 BencodeCanonical,
16 Metainfo,
18 ResourceLimit,
20 Unsupported,
22 Verification,
24 Cancelled,
26}
27
28#[derive(Debug)]
30#[non_exhaustive]
31pub enum Error {
32 Io {
34 path: PathBuf,
36 source: io::Error,
38 },
39 BencodeSyntax {
41 offset: usize,
43 message: String,
45 },
46 BencodeCanonical {
48 offset: usize,
50 message: String,
52 },
53 Metainfo {
55 field: String,
57 message: String,
59 },
60 ResourceLimit {
62 limit: &'static str,
64 actual: usize,
66 maximum: usize,
68 },
69 Unsupported {
71 message: String,
73 },
74 Verification {
76 path: Option<PathBuf>,
78 message: String,
80 },
81 Cancelled,
83}
84
85impl Error {
86 pub fn io(path: impl Into<PathBuf>, source: io::Error) -> Self {
88 Self::Io {
89 path: path.into(),
90 source,
91 }
92 }
93
94 pub fn bencode_syntax(offset: usize, message: impl Into<String>) -> Self {
96 Self::BencodeSyntax {
97 offset,
98 message: message.into(),
99 }
100 }
101
102 pub fn bencode_canonical(offset: usize, message: impl Into<String>) -> Self {
104 Self::BencodeCanonical {
105 offset,
106 message: message.into(),
107 }
108 }
109
110 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 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 pub fn unsupported(message: impl Into<String>) -> Self {
129 Self::Unsupported {
130 message: message.into(),
131 }
132 }
133
134 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 #[must_use]
144 pub const fn cancelled() -> Self {
145 Self::Cancelled
146 }
147
148 #[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 #[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 #[must_use]
176 pub fn field(&self) -> Option<&str> {
177 match self {
178 Self::Metainfo { field, .. } => Some(field),
179 _ => None,
180 }
181 }
182
183 #[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 #[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 #[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
267pub type Result<T> = std::result::Result<T, Error>;