1use reqwest::StatusCode;
2use serde::{Deserialize, Deserializer};
3
4#[derive(Debug, thiserror::Error)]
6pub enum StorageError {
7 #[error("storage API error ({status}): {body}")]
9 Api {
10 status: StatusCode,
12 body: ApiErrorBody,
14 },
15 #[error("invalid storage path {path:?}: {reason}")]
17 InvalidPath {
18 path: String,
20 reason: PathError,
22 },
23 #[error("project URL cannot be a base URL")]
25 InvalidBaseUrl,
26 #[error("signed URL from the storage API has no token: {0:?}")]
28 MissingSignedToken(String),
29 #[error("storage API did not sign {path:?}: {message}")]
32 SignFailed {
33 path: String,
35 message: String,
37 },
38 #[error(transparent)]
40 Url(#[from] url::ParseError),
41 #[error(transparent)]
43 Http(#[from] reqwest::Error),
44 #[error(transparent)]
46 Json(#[from] serde_json::Error),
47 #[error(transparent)]
49 InvalidHeader(#[from] reqwest::header::InvalidHeaderValue),
50}
51
52impl StorageError {
53 #[must_use]
61 pub fn api_status(&self) -> Option<StatusCode> {
62 let Self::Api { status, body } = self else {
63 return None;
64 };
65 let reported = match body {
66 ApiErrorBody::Storage(StorageErrorBody {
67 status_code: Some(code),
68 ..
69 }) => code.parse::<u16>().ok(),
70 ApiErrorBody::Storage(_) | ApiErrorBody::Raw(_) => None,
71 };
72 Some(
73 reported
74 .and_then(|code| StatusCode::from_u16(code).ok())
75 .unwrap_or(*status),
76 )
77 }
78
79 #[must_use]
87 pub const fn code(&self) -> Option<&StorageErrorCode> {
88 let Self::Api {
89 body: ApiErrorBody::Storage(body),
90 ..
91 } = self
92 else {
93 return None;
94 };
95 body.code.as_ref()
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
101pub enum PathError {
102 #[error("path is empty")]
104 Empty,
105 #[error("path contains an empty segment")]
107 EmptySegment,
108 #[error("path contains a `.` or `..` segment")]
110 DotSegment,
111 #[error("path contains a tab, carriage return, or line feed")]
114 ControlCharacter,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
119pub enum ApiErrorBody {
120 Storage(StorageErrorBody),
122 Raw(String),
124}
125
126impl ApiErrorBody {
127 pub(crate) fn decode(text: String) -> Self {
129 match serde_json::from_str::<StorageErrorBody>(&text) {
130 Ok(body)
131 if body.status_code.is_some()
132 || body.code.is_some()
133 || body.error.is_some()
134 || body.message.is_some() =>
135 {
136 Self::Storage(body)
137 }
138 Ok(_) | Err(_) => Self::Raw(text),
139 }
140 }
141}
142
143impl core::fmt::Display for ApiErrorBody {
144 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145 match self {
146 Self::Storage(body) => {
147 let label = body
148 .code
149 .as_ref()
150 .map(StorageErrorCode::as_str)
151 .or(body.error.as_deref());
152 match (label, body.message.as_deref()) {
153 (Some(label), Some(message)) => write!(formatter, "{label}: {message}"),
154 (Some(text), None) | (None, Some(text)) => formatter.write_str(text),
155 (None, None) => Ok(()),
156 }
157 }
158 Self::Raw(text) => formatter.write_str(text),
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
167pub struct StorageErrorBody {
168 #[serde(rename = "statusCode", default, deserialize_with = "string_or_number")]
170 pub status_code: Option<String>,
171 #[serde(default)]
173 pub code: Option<StorageErrorCode>,
174 #[serde(default)]
176 pub error: Option<String>,
177 #[serde(default)]
179 pub message: Option<String>,
180}
181
182fn string_or_number<'de, D: Deserializer<'de>>(
183 deserializer: D,
184) -> Result<Option<String>, D::Error> {
185 #[derive(Deserialize)]
186 #[serde(untagged)]
187 enum Code {
188 Text(String),
189 Number(u64),
190 }
191 Ok(
192 Option::<Code>::deserialize(deserializer)?.map(|code| match code {
193 Code::Text(text) => text,
194 Code::Number(number) => number.to_string(),
195 }),
196 )
197}
198
199macro_rules! storage_error_codes {
201 ($($variant:ident => $wire:literal,)+) => {
202 #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
205 #[serde(from = "String")]
206 #[non_exhaustive]
207 pub enum StorageErrorCode {
208 $(
209 #[doc = concat!("`", $wire, "`")]
210 $variant,
211 )+
212 Other(String),
214 }
215
216 impl StorageErrorCode {
217 #[must_use]
219 pub fn as_str(&self) -> &str {
220 match self {
221 $(Self::$variant => $wire,)+
222 Self::Other(code) => code,
223 }
224 }
225 }
226
227 impl From<String> for StorageErrorCode {
228 fn from(code: String) -> Self {
229 match code.as_str() {
230 $($wire => Self::$variant,)+
231 _ => Self::Other(code),
232 }
233 }
234 }
235 };
236}
237
238storage_error_codes! {
239 NoSuchBucket => "NoSuchBucket",
240 NoSuchKey => "NoSuchKey",
241 NoSuchUpload => "NoSuchUpload",
242 InvalidJwt => "InvalidJWT",
243 InvalidRequest => "InvalidRequest",
244 InvalidArgument => "InvalidArgument",
245 TenantNotFound => "TenantNotFound",
246 EntityTooLarge => "EntityTooLarge",
247 EntityTooSmall => "EntityTooSmall",
248 InternalError => "InternalError",
249 ResourceAlreadyExists => "ResourceAlreadyExists",
250 ResourceNotEmpty => "ResourceNotEmpty",
251 InvalidBucketName => "InvalidBucketName",
252 InvalidKey => "InvalidKey",
253 InvalidRange => "InvalidRange",
254 InvalidMimeType => "InvalidMimeType",
255 InvalidUploadId => "InvalidUploadId",
256 KeyAlreadyExists => "KeyAlreadyExists",
257 BucketAlreadyExists => "BucketAlreadyExists",
258 DatabaseTimeout => "DatabaseTimeout",
259 DatabaseReadOnly => "DatabaseReadOnly",
260 InvalidSignature => "InvalidSignature",
261 ExpiredToken => "ExpiredToken",
262 SignatureDoesNotMatch => "SignatureDoesNotMatch",
263 AccessDenied => "AccessDenied",
264 ResourceLocked => "ResourceLocked",
265 ResourceReferenced => "ResourceReferenced",
266 DatabaseError => "DatabaseError",
267 MissingContentLength => "MissingContentLength",
268 MissingParameter => "MissingParameter",
269 InvalidParameter => "InvalidParameter",
270 InvalidUploadSignature => "InvalidUploadSignature",
271 LockTimeout => "LockTimeout",
272 PreconditionFailed => "PreconditionFailed",
273 InvalidChecksum => "InvalidChecksum",
274 SlowDown => "SlowDown",
275 FeatureNotEnabled => "FeatureNotEnabled",
276 NotSupported => "NotSupported",
277 UnknownError => "UnknownError",
278}
279
280impl core::fmt::Display for StorageErrorCode {
281 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282 formatter.write_str(self.as_str())
283 }
284}