Skip to main content

kget/
error.rs

1//! Strongly-typed error enum for the KGet public API.
2//!
3//! [`KgetError`] replaces `Box<dyn Error>` at every public boundary so callers
4//! can `match` on failure cases rather than downcasting opaque trait objects.
5
6use std::fmt;
7
8/// All errors that can arise from a KGet download operation.
9#[derive(Debug)]
10pub enum KgetError {
11    /// An HTTP/network transport error (wraps the reqwest message).
12    Network(String),
13
14    /// A local filesystem I/O error.
15    Io(std::io::Error),
16
17    /// The downloaded file's checksum does not match the expectation.
18    ChecksumMismatch {
19        /// Algorithm that was used (e.g. "sha256", "blake3").
20        algorithm: String,
21        /// The expected (user-supplied) hash.
22        expected: String,
23        /// The hash computed from the file on disk.
24        got: String,
25    },
26
27    /// A protocol-level error (e.g. bad URL scheme, FTP failure).
28    Protocol(String),
29
30    /// The download was explicitly cancelled.
31    Cancelled,
32
33    /// The remote resource returned HTTP 404 / was not found.
34    NotFound(String),
35
36    /// A checksum sidecar file could not be fetched or parsed.
37    SidecarError(String),
38
39    /// Catch-all for errors that don't fit a more specific variant.
40    Other(String),
41}
42
43impl fmt::Display for KgetError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            KgetError::Network(e) =>
47                write!(f, "Network error: {e}"),
48            KgetError::Io(e) =>
49                write!(f, "IO error: {e}"),
50            KgetError::ChecksumMismatch { algorithm, expected, got } =>
51                write!(f, "{algorithm} mismatch — expected {expected}, got {got}"),
52            KgetError::Protocol(e) =>
53                write!(f, "Protocol error: {e}"),
54            KgetError::Cancelled =>
55                write!(f, "Download cancelled"),
56            KgetError::NotFound(url) =>
57                write!(f, "Resource not found: {url}"),
58            KgetError::SidecarError(e) =>
59                write!(f, "Checksum sidecar error: {e}"),
60            KgetError::Other(e) =>
61                write!(f, "{e}"),
62        }
63    }
64}
65
66impl std::error::Error for KgetError {
67    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68        match self {
69            KgetError::Io(e) => Some(e),
70            _ => None,
71        }
72    }
73}
74
75// ── From impls ────────────────────────────────────────────────────────────────
76
77impl From<std::io::Error> for KgetError {
78    fn from(e: std::io::Error) -> Self { KgetError::Io(e) }
79}
80
81impl From<reqwest::Error> for KgetError {
82    fn from(e: reqwest::Error) -> Self {
83        if e.is_status() {
84            if let Some(s) = e.status() {
85                if s.as_u16() == 404 {
86                    let url = e.url().map(|u| u.to_string()).unwrap_or_default();
87                    return KgetError::NotFound(url);
88                }
89            }
90        }
91        KgetError::Network(e.to_string())
92    }
93}
94
95impl From<Box<dyn std::error::Error + Send + Sync>> for KgetError {
96    fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
97        let msg = e.to_string();
98        if msg.to_lowercase().contains("cancel") {
99            KgetError::Cancelled
100        } else {
101            KgetError::Other(msg)
102        }
103    }
104}
105
106impl From<String> for KgetError {
107    fn from(s: String) -> Self { KgetError::Other(s) }
108}
109
110impl From<&str> for KgetError {
111    fn from(s: &str) -> Self { KgetError::Other(s.to_string()) }
112}