1use crate::search::SearchError;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
9pub enum Error {
10 #[error("invalid input: {message}")]
12 InvalidInput {
13 message: String,
15 },
16
17 #[error("not found: {resource}")]
19 NotFound {
20 resource: String,
22 },
23
24 #[error("payload too large: {size} bytes (limit {limit})")]
26 PayloadTooLarge {
27 size: u64,
29 limit: u64,
31 },
32
33 #[error("bucket name too long: {name} ({len} chars, max 63)")]
35 BucketNameTooLong {
36 name: String,
38 len: usize,
40 },
41
42 #[error("configuration error: {message}")]
44 Config {
45 message: String,
47 },
48
49 #[error(transparent)]
51 Storage(StorageError),
52
53 #[error("malformed Range header: {0}")]
55 MalformedRange(String),
56
57 #[error("not modified")]
59 NotModified,
60
61 #[error("precondition failed")]
63 PreconditionFailed,
64
65 #[error("range not satisfiable (object size: {complete_length})")]
68 RangeNotSatisfiable {
69 complete_length: u64,
71 },
72}
73
74#[derive(Debug)]
77pub enum StorageError {
78 NotFound {
80 key: String,
82 },
83
84 BucketNotFound {
86 bucket: String,
88 },
89
90 BackendUnavailable {
92 message: String,
94 },
95
96 Other {
98 source: Box<dyn std::error::Error + Send + Sync>,
100 },
101
102 NotModified,
104
105 PreconditionFailed,
107
108 RangeNotSatisfiable {
111 complete_length: u64,
113 },
114}
115
116impl std::fmt::Display for StorageError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match self {
119 Self::NotFound { key } => write!(f, "object not found: {key}"),
120 Self::BucketNotFound { bucket } => write!(f, "bucket not found: {bucket}"),
121 Self::BackendUnavailable { message } => write!(f, "backend unavailable: {message}"),
122 Self::Other { source } => write!(f, "storage error: {source}"),
123 Self::NotModified => write!(f, "not modified"),
124 Self::PreconditionFailed => write!(f, "precondition failed"),
125 Self::RangeNotSatisfiable { complete_length } => {
126 write!(f, "range not satisfiable (object size: {complete_length})")
127 }
128 }
129 }
130}
131
132impl std::error::Error for StorageError {
133 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
134 if let Self::Other { source } = self {
135 Some(source.as_ref())
136 } else {
137 None
138 }
139 }
140}
141
142impl StorageError {
143 pub fn is_not_found(&self) -> bool {
145 matches!(self, Self::NotFound { .. } | Self::BucketNotFound { .. })
146 }
147}
148
149impl From<StorageError> for Error {
150 fn from(err: StorageError) -> Self {
151 match err {
152 StorageError::NotModified => Error::NotModified,
153 StorageError::PreconditionFailed => Error::PreconditionFailed,
154 StorageError::RangeNotSatisfiable { complete_length } => {
155 Error::RangeNotSatisfiable { complete_length }
156 }
157 other => Error::Storage(other),
158 }
159 }
160}
161
162impl From<SearchError> for Error {
163 fn from(e: SearchError) -> Self {
164 match e {
165 SearchError::InvalidInput { message } => Error::InvalidInput { message },
166 SearchError::UnknownKb { slug } => Error::NotFound {
167 resource: format!("knowledgebase '{slug}'"),
168 },
169 SearchError::BackendUnavailable { message } => {
170 Error::Storage(StorageError::BackendUnavailable { message })
171 }
172 SearchError::Internal { message } => Error::Config { message },
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_error_invalid_input_display() {
183 let e = Error::InvalidInput {
184 message: "bad".into(),
185 };
186 let s = e.to_string();
187 assert!(s.contains("bad"), "Display should contain the message: {s}");
188 }
189
190 #[test]
191 fn test_error_payload_too_large_display() {
192 let e = Error::PayloadTooLarge {
193 size: 17_000_000_u64,
194 limit: 16_777_216_u64,
195 };
196 let s = e.to_string();
197 assert!(
198 s.contains("17000000") || s.contains("17_000_000"),
199 "Display should contain size: {s}"
200 );
201 assert!(
202 s.contains("16777216") || s.contains("16_777_216"),
203 "Display should contain limit: {s}"
204 );
205 }
206
207 #[test]
208 fn test_storage_error_not_found_display() {
209 let e = StorageError::NotFound { key: "foo".into() };
210 let s = e.to_string();
211 assert!(s.contains("foo"), "Display should contain the key: {s}");
212 }
213
214 #[test]
215 fn test_storage_error_is_not_found_true_for_not_found() {
216 let e = StorageError::NotFound { key: "bar".into() };
217 assert!(e.is_not_found());
218 }
219
220 #[test]
221 fn test_storage_error_is_not_found_true_for_bucket_not_found() {
222 let e = StorageError::BucketNotFound {
223 bucket: "my-bucket".into(),
224 };
225 assert!(e.is_not_found());
226 }
227
228 #[test]
229 fn test_storage_error_is_not_found_false_for_backend_unavailable() {
230 let e = StorageError::BackendUnavailable {
231 message: "connection refused".into(),
232 };
233 assert!(!e.is_not_found());
234 }
235
236 #[test]
237 fn test_from_storage_error_for_error() {
238 let se = StorageError::NotFound { key: "obj".into() };
239 let e: Error = Error::from(se);
240 assert!(matches!(e, Error::Storage(_)));
241 }
242
243 #[test]
244 fn test_error_implements_std_error() {
245 fn assert_std_error<T: std::error::Error>(_: &T) {}
246 let e = Error::InvalidInput {
247 message: "test".into(),
248 };
249 assert_std_error(&e);
250 }
251
252 #[test]
253 fn test_storage_error_implements_std_error() {
254 fn assert_std_error<T: std::error::Error>(_: &T) {}
255 let e = StorageError::BucketNotFound { bucket: "b".into() };
256 assert_std_error(&e);
257 }
258
259 #[test]
260 fn test_error_not_found_display() {
261 let e = Error::NotFound {
262 resource: "kb:my-notes".into(),
263 };
264 let s = e.to_string();
265 assert!(
266 s.contains("my-notes"),
267 "Display should contain resource: {s}"
268 );
269 }
270
271 #[test]
272 fn test_error_bucket_name_too_long_fields() {
273 let e = Error::BucketNameTooLong {
274 name: "nt-toolong-name".into(),
275 len: 15_usize,
276 };
277 if let Error::BucketNameTooLong { name, len } = &e {
278 assert_eq!(name, "nt-toolong-name");
279 assert_eq!(*len, 15);
280 } else {
281 panic!("Wrong variant");
282 }
283 }
284
285 #[test]
286 fn storage_error_range_not_satisfiable_converts_to_error() {
287 let storage_err = StorageError::RangeNotSatisfiable {
288 complete_length: 100,
289 };
290 let err: Error = Error::from(storage_err);
291 assert!(matches!(
292 err,
293 Error::RangeNotSatisfiable {
294 complete_length: 100
295 }
296 ));
297 }
298
299 #[test]
300 fn storage_error_not_modified_converts_to_error() {
301 let storage_err = StorageError::NotModified;
302 let err: Error = Error::from(storage_err);
303 assert!(matches!(err, Error::NotModified));
304 }
305
306 #[test]
307 fn storage_error_precondition_failed_converts_to_error() {
308 let storage_err = StorageError::PreconditionFailed;
309 let err: Error = Error::from(storage_err);
310 assert!(matches!(err, Error::PreconditionFailed));
311 }
312}