minio/s3/
error.rs

1// MinIO Rust Library for Amazon S3 Compatible Cloud Storage
2// Copyright 2022 MinIO, Inc.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Error definitions for S3 operations
17
18extern crate alloc;
19use crate::s3::utils::get_default_text;
20use bytes::{Buf, Bytes};
21use http::HeaderMap;
22use std::fmt;
23use xmltree::Element;
24
25#[derive(Clone, Debug, Default, PartialEq)]
26pub enum ErrorCode {
27    #[default]
28    NoError,
29
30    PermanentRedirect,
31    Redirect,
32    BadRequest,
33    RetryHead,
34    NoSuchBucket,
35    NoSuchBucketPolicy,
36    ReplicationConfigurationNotFoundError,
37    ServerSideEncryptionConfigurationNotFoundError,
38    NoSuchTagSet,
39    NoSuchObjectLockConfiguration,
40    NoSuchLifecycleConfiguration,
41    NoSuchKey,
42    ResourceNotFound,
43    MethodNotAllowed,
44    ResourceConflict,
45    AccessDenied,
46    NotSupported,
47    BucketNotEmpty,
48    BucketAlreadyOwnedByYou,
49    InvalidWriteOffset,
50
51    OtherError(String),
52}
53
54impl ErrorCode {
55    pub fn parse(s: &str) -> Self {
56        match s.to_lowercase().as_str() {
57            "permanentredirect" => ErrorCode::PermanentRedirect,
58            "redirect" => ErrorCode::Redirect,
59            "badrequest" => ErrorCode::BadRequest,
60            "retryhead" => ErrorCode::RetryHead,
61            "nosuchbucket" => ErrorCode::NoSuchBucket,
62            "nosuchbucketpolicy" => ErrorCode::NoSuchBucketPolicy,
63            "replicationconfigurationnotfounderror" => {
64                ErrorCode::ReplicationConfigurationNotFoundError
65            }
66            "serversideencryptionconfigurationnotfounderror" => {
67                ErrorCode::ServerSideEncryptionConfigurationNotFoundError
68            }
69            "nosuchtagset" => ErrorCode::NoSuchTagSet,
70            "nosuchobjectlockconfiguration" => ErrorCode::NoSuchObjectLockConfiguration,
71            "nosuchlifecycleconfiguration" => ErrorCode::NoSuchLifecycleConfiguration,
72            "nosuchkey" => ErrorCode::NoSuchKey,
73            "resourcenotfound" => ErrorCode::ResourceNotFound,
74            "methodnotallowed" => ErrorCode::MethodNotAllowed,
75            "resourceconflict" => ErrorCode::ResourceConflict,
76            "accessdenied" => ErrorCode::AccessDenied,
77            "notsupported" => ErrorCode::NotSupported,
78            "bucketnotempty" => ErrorCode::BucketNotEmpty,
79            "bucketalreadyownedbyyou" => ErrorCode::BucketAlreadyOwnedByYou,
80            "invalidwriteoffset" => ErrorCode::InvalidWriteOffset,
81
82            v => ErrorCode::OtherError(v.to_owned()),
83        }
84    }
85}
86
87#[derive(Clone, Debug, Default)]
88/// Error response for S3 operations
89pub struct ErrorResponse {
90    /// Headers as returned by the server.
91    pub headers: HeaderMap,
92    pub code: ErrorCode,
93    pub message: String,
94    pub resource: String,
95    pub request_id: String,
96    pub host_id: String,
97    pub bucket_name: String,
98    pub object_name: String,
99}
100
101impl ErrorResponse {
102    pub fn parse(body: Bytes, headers: HeaderMap) -> Result<Self, Error> {
103        let root = match Element::parse(body.reader()) {
104            Ok(v) => v,
105            Err(e) => return Err(Error::XmlParseError(e)),
106        };
107
108        Ok(Self {
109            headers,
110            code: ErrorCode::parse(&get_default_text(&root, "Code")),
111            message: get_default_text(&root, "Message"),
112            resource: get_default_text(&root, "Resource"),
113            request_id: get_default_text(&root, "RequestId"),
114            host_id: get_default_text(&root, "HostId"),
115            bucket_name: get_default_text(&root, "BucketName"),
116            object_name: get_default_text(&root, "Key"),
117        })
118    }
119}
120
121/// Error definitions
122#[derive(Debug)]
123pub enum Error {
124    TimeParseError(chrono::ParseError),
125    InvalidUrl(http::uri::InvalidUri),
126    IOError(std::io::Error),
127    XmlParseError(xmltree::ParseError),
128    HttpError(reqwest::Error),
129    StrError(reqwest::header::ToStrError),
130    IntError(std::num::ParseIntError),
131    BoolError(std::str::ParseBoolError),
132    Utf8Error(alloc::string::FromUtf8Error),
133    JsonError(serde_json::Error),
134    XmlError(String),
135    InvalidBucketName(String),
136    InvalidBaseUrl(String),
137    UrlBuildError(String),
138    RegionMismatch(String, String),
139    S3Error(ErrorResponse),
140    InvalidResponse(u16, String),
141    ServerError(u16),
142    InvalidObjectName(String),
143    InvalidUploadId(String),
144    InvalidPartNumber(String),
145    InvalidUserMetadata(String),
146    EmptyParts(String),
147    InvalidRetentionMode(String),
148    InvalidRetentionConfig(String),
149    InvalidMinPartSize(u64),
150    InvalidMaxPartSize(u64),
151    InvalidObjectSize(u64),
152    MissingPartSize,
153    InvalidPartCount(u64, u64, u16),
154    TooManyParts,
155    SseTlsRequired(Option<String>),
156    TooMuchData(u64),
157    InsufficientData(u64, u64),
158    InvalidLegalHold(String),
159    InvalidSelectExpression(String),
160    InvalidHeaderValueType(u8),
161    CrcMismatch(String, u32, u32),
162    UnknownEventType(String),
163    SelectError(String, String),
164    UnsupportedApi(String),
165    InvalidComposeSource(String),
166    InvalidComposeSourceOffset(String, String, Option<String>, u64, u64),
167    InvalidComposeSourceLength(String, String, Option<String>, u64, u64),
168    InvalidComposeSourceSize(String, String, Option<String>, u64, u64),
169    InvalidComposeSourcePartSize(String, String, Option<String>, u64, u64),
170    InvalidComposeSourceMultipart(String, String, Option<String>, u64, u64),
171    InvalidDirective(String),
172    InvalidCopyDirective(String),
173    InvalidMultipartCount(u16),
174    MissingLifecycleAction,
175    InvalidExpiredObjectDeleteMarker,
176    InvalidDateAndDays(String),
177    InvalidLifecycleRuleId,
178    InvalidFilter,
179    InvalidVersioningStatus(String),
180    PostPolicyError(String),
181    InvalidObjectLockConfig(String),
182    NoClientProvided,
183    TagDecodingError(String, String),
184    ContentLengthUnknown,
185}
186
187impl std::error::Error for Error {}
188
189impl fmt::Display for Error {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match self {
192            Error::TimeParseError(e) => write!(f, "{e}"),
193            Error::InvalidUrl(e) => write!(f, "{e}"),
194            Error::IOError(e) => write!(f, "{e}"),
195            Error::XmlParseError(e) => write!(f, "{e}"),
196            Error::HttpError(e) => write!(f, "{e}"),
197            Error::StrError(e) => write!(f, "{e}"),
198            Error::IntError(e) => write!(f, "{e}"),
199            Error::BoolError(e) => write!(f, "{e}"),
200            Error::Utf8Error(e) => write!(f, "{e}"),
201            Error::JsonError(e) => write!(f, "{e}"),
202            Error::XmlError(m) => write!(f, "{m}"),
203            Error::InvalidBucketName(m) => write!(f, "{m}"),
204            Error::InvalidObjectName(m) => write!(f, "{m}"),
205            Error::InvalidUploadId(m) => write!(f, "{m}"),
206            Error::InvalidPartNumber(m) => write!(f, "{m}"),
207            Error::InvalidUserMetadata(m) => write!(f, "{m}"),
208            Error::EmptyParts(m) => write!(f, "{m}"),
209            Error::InvalidRetentionMode(m) => write!(f, "invalid retention mode {m}"),
210            Error::InvalidRetentionConfig(m) => write!(f, "invalid retention configuration; {m}"),
211            Error::InvalidMinPartSize(s) => {
212                write!(f, "part size {s} is not supported; minimum allowed 5MiB")
213            }
214            Error::InvalidMaxPartSize(s) => {
215                write!(f, "part size {s} is not supported; maximum allowed 5GiB")
216            }
217            Error::InvalidObjectSize(s) => {
218                write!(f, "object size {s} is not supported; maximum allowed 5TiB",)
219            }
220            Error::MissingPartSize => write!(
221                f,
222                "valid part size must be provided when object size is unknown"
223            ),
224            Error::InvalidPartCount(os, ps, pc) => write!(
225                f,
226                "object size {os} and part size {ps} make more than {pc} parts for upload"
227            ),
228            Error::TooManyParts => write!(f, "too many parts for upload"),
229            Error::SseTlsRequired(m) => write!(
230                f,
231                "{}SSE operation must be performed over a secure connection",
232                m.as_ref().map_or(String::new(), |v| v.clone())
233            ),
234            Error::TooMuchData(s) => write!(f, "too much data in the stream - exceeds {s} bytes"),
235            Error::InsufficientData(expected, got) => write!(
236                f,
237                "not enough data in the stream; expected: {expected}, got: {got} bytes",
238            ),
239            Error::InvalidBaseUrl(m) => write!(f, "{m}"),
240            Error::UrlBuildError(m) => write!(f, "{m}"),
241            Error::InvalidLegalHold(s) => write!(f, "invalid legal hold {s}"),
242            Error::RegionMismatch(br, r) => write!(f, "region must be {br}, but passed {r}"),
243            Error::S3Error(er) => write!(
244                f,
245                "s3 operation failed; code: {:?}, message: {}, resource: {}, request_id: {}, host_id: {}, bucket_name: {}, object_name: {}",
246                er.code,
247                er.message,
248                er.resource,
249                er.request_id,
250                er.host_id,
251                er.bucket_name,
252                er.object_name,
253            ),
254            Error::InvalidResponse(sc, ct) => write!(
255                f,
256                "invalid response received; status code: {sc}; content-type: {ct}"
257            ),
258            Error::ServerError(sc) => write!(f, "server failed with HTTP status code {sc}"),
259            Error::InvalidSelectExpression(m) => write!(f, "{m}"),
260            Error::InvalidHeaderValueType(v) => write!(f, "invalid header value type {v}"),
261            Error::CrcMismatch(t, e, g) => {
262                write!(f, "{t} CRC mismatch; expected: {e}, got: {g}")
263            }
264            Error::UnknownEventType(et) => write!(f, "unknown event type {et}"),
265            Error::SelectError(ec, em) => write!(f, "error code: {ec}, error message: {em}"),
266            Error::UnsupportedApi(a) => write!(f, "{a} API is not supported in Amazon AWS S3"),
267            Error::InvalidComposeSource(m) => write!(f, "{m}"),
268            Error::InvalidComposeSourceOffset(b, o, v, of, os) => write!(
269                f,
270                "source {}/{}{}: offset {} is beyond object size {}",
271                b,
272                o,
273                v.as_ref()
274                    .map_or(String::new(), |v| String::from("?versionId=") + v),
275                of,
276                os
277            ),
278            Error::InvalidComposeSourceLength(b, o, v, l, os) => write!(
279                f,
280                "source {}/{}{}: length {} is beyond object size {}",
281                b,
282                o,
283                v.as_ref()
284                    .map_or(String::new(), |v| String::from("?versionId=") + v),
285                l,
286                os
287            ),
288            Error::InvalidComposeSourceSize(b, o, v, cs, os) => write!(
289                f,
290                "source {}/{}{}: compose size {} is beyond object size {}",
291                b,
292                o,
293                v.as_ref()
294                    .map_or(String::new(), |v| String::from("?versionId=") + v),
295                cs,
296                os
297            ),
298            Error::InvalidDirective(m) => write!(f, "{m}"),
299            Error::InvalidCopyDirective(m) => write!(f, "{m}"),
300            Error::InvalidComposeSourcePartSize(b, o, v, s, es) => write!(
301                f,
302                "source {}/{}{}: size {} must be greater than {}",
303                b,
304                o,
305                v.as_ref()
306                    .map_or(String::new(), |v| String::from("?versionId=") + v),
307                s,
308                es
309            ),
310            Error::InvalidComposeSourceMultipart(b, o, v, s, es) => write!(
311                f,
312                "source {}/{}{}: size {} for multipart split upload of {}, last part size is less than {}",
313                b,
314                o,
315                v.as_ref()
316                    .map_or(String::new(), |v| String::from("?versionId=") + v),
317                s,
318                s,
319                es
320            ),
321            Error::InvalidMultipartCount(c) => write!(
322                f,
323                "Compose sources create more than allowed multipart count {c}",
324            ),
325            Error::MissingLifecycleAction => write!(
326                f,
327                "at least one of action (AbortIncompleteMultipartUpload, Expiration, NoncurrentVersionExpiration, NoncurrentVersionTransition or Transition) must be specified in a rule"
328            ),
329            Error::InvalidExpiredObjectDeleteMarker => write!(
330                f,
331                "ExpiredObjectDeleteMarker must not be provided along with Date and Days"
332            ),
333            Error::InvalidDateAndDays(m) => {
334                write!(f, "Only one of date or days of {m} must be set")
335            }
336            Error::InvalidLifecycleRuleId => write!(f, "id must be exceed 255 characters"),
337            Error::InvalidFilter => write!(f, "only one of And, Prefix or Tag must be provided"),
338            Error::InvalidVersioningStatus(m) => write!(f, "{m}"),
339            Error::PostPolicyError(m) => write!(f, "{m}"),
340            Error::InvalidObjectLockConfig(m) => write!(f, "{m}"),
341            Error::NoClientProvided => write!(f, "no client provided"),
342            Error::TagDecodingError(input, error_message) => {
343                write!(f, "tag decoding failed: {error_message} on input '{input}'")
344            }
345            Error::ContentLengthUnknown => write!(f, "content length is unknown"),
346        }
347    }
348}
349
350impl From<chrono::ParseError> for Error {
351    fn from(err: chrono::ParseError) -> Self {
352        Error::TimeParseError(err)
353    }
354}
355
356impl From<http::uri::InvalidUri> for Error {
357    fn from(err: http::uri::InvalidUri) -> Self {
358        Error::InvalidUrl(err)
359    }
360}
361
362impl From<std::io::Error> for Error {
363    fn from(err: std::io::Error) -> Self {
364        Error::IOError(err)
365    }
366}
367
368impl From<xmltree::ParseError> for Error {
369    fn from(err: xmltree::ParseError) -> Self {
370        Error::XmlParseError(err)
371    }
372}
373
374impl From<reqwest::Error> for Error {
375    fn from(err: reqwest::Error) -> Self {
376        Error::HttpError(err)
377    }
378}
379
380impl From<reqwest::header::ToStrError> for Error {
381    fn from(err: reqwest::header::ToStrError) -> Self {
382        Error::StrError(err)
383    }
384}
385
386impl From<std::num::ParseIntError> for Error {
387    fn from(err: std::num::ParseIntError) -> Self {
388        Error::IntError(err)
389    }
390}
391
392impl From<std::str::ParseBoolError> for Error {
393    fn from(err: std::str::ParseBoolError) -> Self {
394        Error::BoolError(err)
395    }
396}
397
398impl From<alloc::string::FromUtf8Error> for Error {
399    fn from(err: alloc::string::FromUtf8Error) -> Self {
400        Error::Utf8Error(err)
401    }
402}
403
404impl From<serde_json::Error> for Error {
405    fn from(err: serde_json::Error) -> Self {
406        Error::JsonError(err)
407    }
408}