asknothingx2_util/api/request/
error.rs

1use std::{fmt, io, path::PathBuf, time::Duration};
2
3
4#[derive(Debug, thiserror::Error)]
5pub enum StreamError {
6    #[error("IO error during {operation}: {source}")]
7    Io {
8        operation: String,
9        #[source]
10        source: io::Error,
11    },
12
13    #[error("File {operation} error for '{path}': {source}")]
14    File {
15        path: PathBuf,
16        operation: FileOperation,
17        #[source]
18        source: io::Error,
19    },
20
21    #[error("Network {operation} error for '{address}': {source}")]
22    Network {
23        address: String,
24        operation: NetworkOperation,
25        #[source]
26        source: io::Error,
27    },
28
29    #[error("Process {operation} error for '{command}': {source}")]
30    Process {
31        command: String,
32        operation: ProcessOperation,
33        #[source]
34        source: io::Error,
35    },
36
37    #[error("Stream {operation} error: {source}")]
38    Stream {
39        operation: StreamOperation,
40        #[source]
41        source: io::Error,
42    },
43
44    #[error("Codec '{codec_name}' error{}: {source}", position_display(.position))]
45    Codec {
46        codec_name: String,
47        position: Option<u64>,
48        #[source]
49        source: Box<dyn std::error::Error + Send + Sync>,
50    },
51
52    #[error("{limit_type} limit exceeded: {actual} > {max_allowed}")]
53    Limit {
54        limit_type: LimitType,
55        actual: u64,
56        max_allowed: u64,
57    },
58
59    #[error("Timeout during '{operation}' after {duration:?}")]
60    Timeout {
61        operation: String,
62        duration: Duration,
63    },
64
65    #[error("HTTP error: {source}")]
66    Http {
67        #[source]
68        source: reqwest::Error,
69    },
70
71    #[error("Content type detection failed{}{}: {reason}", 
72        path_display(.path), 
73        extension_display(.extension)
74    )]
75    ContentType {
76        path: Option<PathBuf>,
77        extension: Option<String>,
78        reason: String,
79    },
80
81    #[error("Failed to convert from '{from_type}' to '{to_type}': {reason}")]
82    Conversion {
83        from_type: String,
84        to_type: String,
85        reason: String,
86    },
87
88    #[error("{message}")]
89    Custom {
90        message: String,
91        #[source]
92        source: Option<Box<dyn std::error::Error + Send + Sync>>,
93    },
94
95    #[error("JSON error: {source}")]
96    Json {
97        #[source]
98        source: serde_json::Error,
99    },
100
101    #[error("URL parsing error: {source}")]
102    Url {
103        #[source]
104        source: url::ParseError,
105    },
106
107    #[error("UTF-8 conversion error: {source}")]
108    Utf8 {
109        #[source]
110        source: std::str::Utf8Error,
111    },
112
113    #[error("Stream ended unexpectedly")]
114    EndOfStream,
115
116    #[error("Stream operation was cancelled")]
117    Cancelled,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum FileOperation {
122    Open,
123    Read,
124    Write,
125    Seek,
126    Metadata,
127    Create,
128    Delete,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum NetworkOperation {
133    Connect,
134    Read,
135    Write,
136    Bind,
137    Listen,
138    Accept,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum ProcessOperation {
143    Spawn,
144    Wait,
145    Kill,
146    ReadStdout,
147    ReadStderr,
148    WriteStdin,
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum StreamOperation {
153    Read,
154    Write,
155    Transform,
156    Buffer,
157    Flush,
158    Close,
159    Yield,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum LimitType {
164    FileSize,
165    StreamLength,
166    BufferSize,
167    ChunkSize,
168    Duration,
169    Bandwidth,
170}
171
172
173impl fmt::Display for FileOperation {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        write!(f, "{}", self.as_str())
176    }
177}
178
179impl fmt::Display for NetworkOperation {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "{}", self.as_str())
182    }
183}
184
185impl fmt::Display for ProcessOperation {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(f, "{}", self.as_str())
188    }
189}
190
191impl fmt::Display for StreamOperation {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        write!(f, "{}", self.as_str())
194    }
195}
196
197impl fmt::Display for LimitType {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}", self.as_str())
200    }
201}
202
203impl FileOperation {
204    pub const fn as_str(&self) -> &'static str {
205        match self {
206            FileOperation::Open => "open",
207            FileOperation::Read => "read",
208            FileOperation::Write => "write",
209            FileOperation::Seek => "seek",
210            FileOperation::Metadata => "metadata",
211            FileOperation::Create => "create",
212            FileOperation::Delete => "delete",
213        }
214    }
215}
216
217impl NetworkOperation {
218    pub const fn as_str(&self) -> &'static str {
219        match self {
220            NetworkOperation::Connect => "connect",
221            NetworkOperation::Read => "read",
222            NetworkOperation::Write => "write",
223            NetworkOperation::Bind => "bind",
224            NetworkOperation::Listen => "listen",
225            NetworkOperation::Accept => "accept",
226        }
227    }
228}
229
230impl ProcessOperation {
231    pub const fn as_str(&self) -> &'static str {
232        match self {
233            ProcessOperation::Spawn => "spawn",
234            ProcessOperation::Wait => "wait",
235            ProcessOperation::Kill => "kill",
236            ProcessOperation::ReadStdout => "read_stdout",
237            ProcessOperation::ReadStderr => "read_stderr",
238            ProcessOperation::WriteStdin => "write_stdin",
239        }
240    }
241}
242
243impl StreamOperation {
244    pub const fn as_str(&self) -> &'static str {
245        match self {
246            StreamOperation::Read => "read",
247            StreamOperation::Write => "write",
248            StreamOperation::Transform => "transform",
249            StreamOperation::Buffer => "buffer",
250            StreamOperation::Flush => "flush",
251            StreamOperation::Close => "close",
252            StreamOperation::Yield => "yield",
253        }
254    }
255}
256
257impl LimitType {
258    pub const fn as_str(&self) -> &'static str {
259        match self {
260            LimitType::FileSize => "file size",
261            LimitType::StreamLength => "stream length",
262            LimitType::BufferSize => "buffer size",
263            LimitType::ChunkSize => "chunk size",
264            LimitType::Duration => "duration",
265            LimitType::Bandwidth => "bandwidth",
266        }
267    }
268}
269
270
271fn position_display(position: &Option<u64>) -> String {
272    match position {
273        Some(pos) => format!(" at position {pos}" ),
274        None => String::new(),
275    }
276}
277
278fn path_display(path: &Option<PathBuf>) -> String {
279    match path {
280        Some(p) => format!(" for '{}'", p.display()),
281        None => String::new(),
282    }
283}
284
285fn extension_display(extension: &Option<String>) -> String {
286    match extension {
287        Some(ext) => format!(" (extension: {ext})" ),
288        None => String::new(),
289    }
290}
291
292impl From<std::io::Error> for StreamError {
293    fn from(err: std::io::Error) -> Self {
294        StreamError::Io {
295            operation: "unknown".to_string(),
296            source: err,
297        }
298    }
299}
300
301impl From<reqwest::Error> for StreamError {
302    fn from(err: reqwest::Error) -> Self {
303        StreamError::Http { source: err }
304    }
305}
306
307impl From<serde_json::Error> for StreamError {
308    fn from(err: serde_json::Error) -> Self {
309        StreamError::Json { source: err }
310    }
311}
312
313impl From<url::ParseError> for StreamError {
314    fn from(err: url::ParseError) -> Self {
315        StreamError::Url { source: err }
316    }
317}
318
319impl From<std::str::Utf8Error> for StreamError {
320    fn from(err: std::str::Utf8Error) -> Self {
321        StreamError::Utf8 { source: err }
322    }
323}
324
325
326impl StreamError {
327    pub fn io_error<O: Into<String>>(operation: O, source: std::io::Error) -> Self {
328        Self::Io {
329            operation: operation.into(),
330            source,
331        }
332    }
333
334    pub fn file_error<P: Into<PathBuf>>(
335        path: P,
336        operation: FileOperation,
337        source: std::io::Error,
338    ) -> Self {
339        Self::File {
340            path: path.into(),
341            operation,
342            source,
343        }
344    }
345
346    pub fn network_error<A: Into<String> >(
347        address: A,
348        operation: NetworkOperation,
349        source: io::Error,
350    ) -> Self {
351        Self::Network {
352            address: address.into(),
353            operation,
354            source ,
355        }
356    }
357
358    pub fn timeout_error<O: Into<String>>(operation: O, duration: Duration) -> Self {
359        Self::Timeout {
360            operation: operation.into(),
361            duration,
362        }
363    }
364
365    pub fn custom<M: Into<String>>(message: M) -> Self {
366        Self::Custom {
367            message: message.into(),
368            source: None,
369        }
370    }
371
372    pub fn size_limit_error(limit_type: LimitType, actual: u64, max_allowed: u64) -> Self {
373        Self::Limit {
374            limit_type,
375            actual,
376            max_allowed,
377        }
378    }
379
380    pub fn is_recoverable(&self) -> bool {
381        match self {
382            StreamError::Timeout { .. } => true,
383            StreamError::Network { .. } => true,
384            StreamError::Http { source } => source.is_timeout() || source.is_connect(),
385            StreamError::Io { source, .. } => {
386                matches!(
387                    source.kind(),
388                    std::io::ErrorKind::Interrupted
389                        | std::io::ErrorKind::WouldBlock
390                        | std::io::ErrorKind::TimedOut
391                )
392            }
393            StreamError::Limit { .. } => false,
394            StreamError::Cancelled => true,
395            _ => false,
396        }
397    }
398
399    pub fn is_temporary(&self) -> bool {
400        match self {
401            StreamError::Timeout { .. } => true,
402            StreamError::Network { .. } => true,
403            StreamError::Http { source } => {
404                source.is_timeout() 
405                    || source.is_connect() 
406                    || source.status().is_some_and( |s| s.is_server_error())
407            }
408            StreamError::Io { source, .. } => {
409                matches!(
410                    source.kind(),
411                    std::io::ErrorKind::Interrupted
412                        | std::io::ErrorKind::WouldBlock
413                        | std::io::ErrorKind::TimedOut
414                        | std::io::ErrorKind::ConnectionRefused
415                        | std::io::ErrorKind::ConnectionReset
416                )
417            }
418            StreamError::Cancelled => true,
419            _ => false,
420        }
421    }
422}
423
424#[derive(Debug, thiserror::Error)]
425pub enum HeaderError {
426    #[error("Invalid header name '{name}': {reason}")]
427    InvalidHeaderName { name: String, reason: String },
428
429    #[error("Invalid header value for '{name}' = '{value}': {reason}")]
430    InvalidHeaderValue { 
431        name: String, 
432        value: String, 
433        reason: String 
434    },
435
436    #[error("Encoding failed for header '{name}': {reason}")]
437    EncodingFailed { name: String, reason: String },
438    #[error("Header value contains invalid UTF-8: {reason}")]
439    InvalidUtf8 { 
440        reason: String 
441    },
442}