1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum Error {
8 #[error("HTTP request failed: {0}")]
10 Http(#[from] reqwest::Error),
11
12 #[error("Request timeout after {duration_ms}ms")]
14 Timeout {
15 duration_ms: u64,
17 },
18
19 #[error("All CDN hosts exhausted for {resource}")]
21 CdnExhausted {
22 resource: String,
24 },
25
26 #[error("Invalid CDN host: {host}")]
28 InvalidHost {
29 host: String,
31 },
32
33 #[error("Invalid content hash: {hash}")]
35 InvalidHash {
36 hash: String,
38 },
39
40 #[error("Content not found: {hash}")]
42 ContentNotFound {
43 hash: String,
45 },
46
47 #[error("Content verification failed for {hash}: expected {expected}, got {actual}")]
49 VerificationFailed {
50 hash: String,
52 expected: String,
54 actual: String,
56 },
57
58 #[error("Invalid response from CDN: {reason}")]
60 InvalidResponse {
61 reason: String,
63 },
64
65 #[error("Rate limit exceeded: retry after {retry_after_secs} seconds")]
67 RateLimited {
68 retry_after_secs: u64,
70 },
71
72 #[error("IO error: {0}")]
74 Io(#[from] std::io::Error),
75
76 #[error("Invalid URL: {url}")]
78 InvalidUrl {
79 url: String,
81 },
82
83 #[error("Content size mismatch: expected {expected} bytes, got {actual} bytes")]
85 SizeMismatch {
86 expected: u64,
88 actual: u64,
90 },
91
92 #[error("CDN server does not support partial content (range requests)")]
94 PartialContentNotSupported,
95}
96
97pub type Result<T> = std::result::Result<T, Error>;
99
100impl Error {
102 pub fn cdn_exhausted_with_resource(resource: impl Into<String>) -> Self {
104 Self::CdnExhausted {
105 resource: resource.into(),
106 }
107 }
108
109 pub fn cdn_exhausted() -> Self {
111 Self::CdnExhausted {
112 resource: "requested content".to_string(),
113 }
114 }
115
116 pub fn invalid_host(host: impl Into<String>) -> Self {
118 Self::InvalidHost { host: host.into() }
119 }
120
121 pub fn invalid_hash(hash: impl Into<String>) -> Self {
123 Self::InvalidHash { hash: hash.into() }
124 }
125
126 pub fn content_not_found(hash: impl Into<String>) -> Self {
128 Self::ContentNotFound { hash: hash.into() }
129 }
130
131 pub fn verification_failed(
133 hash: impl Into<String>,
134 expected: impl Into<String>,
135 actual: impl Into<String>,
136 ) -> Self {
137 Self::VerificationFailed {
138 hash: hash.into(),
139 expected: expected.into(),
140 actual: actual.into(),
141 }
142 }
143
144 pub fn invalid_response(reason: impl Into<String>) -> Self {
146 Self::InvalidResponse {
147 reason: reason.into(),
148 }
149 }
150
151 pub fn rate_limited(retry_after_secs: u64) -> Self {
153 Self::RateLimited { retry_after_secs }
154 }
155
156 pub fn invalid_url(url: impl Into<String>) -> Self {
158 Self::InvalidUrl { url: url.into() }
159 }
160
161 pub fn size_mismatch(expected: u64, actual: u64) -> Self {
163 Self::SizeMismatch { expected, actual }
164 }
165}