forked from johannesboyne/gofakes3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
error.go
350 lines (282 loc) · 10.3 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
package gofakes3
import (
"fmt"
"net/http"
"time"
xml "github.com/JankariTech/gofakes3/xml"
)
// Error codes are documented here:
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
//
// If you add a code to this list, please also add it to ErrorCode.Status().
const (
ErrNone ErrorCode = ""
// The Content-MD5 you specified did not match what we received.
ErrBadDigest ErrorCode = "BadDigest"
ErrBucketAlreadyExists ErrorCode = "BucketAlreadyExists"
// Raised when attempting to delete a bucket that still contains items.
ErrBucketNotEmpty ErrorCode = "BucketNotEmpty"
// "Indicates that the versioning configuration specified in the request is invalid"
ErrIllegalVersioningConfiguration ErrorCode = "IllegalVersioningConfigurationException"
// You did not provide the number of bytes specified by the Content-Length
// HTTP header:
ErrIncompleteBody ErrorCode = "IncompleteBody"
// POST requires exactly one file upload per request.
ErrIncorrectNumberOfFilesInPostRequest ErrorCode = "IncorrectNumberOfFilesInPostRequest"
// InlineDataTooLarge occurs when using the PutObjectInline method of the
// SOAP interface
// (https://docs.aws.amazon.com/AmazonS3/latest/API/SOAPPutObjectInline.html).
// This is not documented on the errors page; the error is included here
// only for reference.
ErrInlineDataTooLarge ErrorCode = "InlineDataTooLarge"
ErrInvalidArgument ErrorCode = "InvalidArgument"
// https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules
ErrInvalidBucketName ErrorCode = "InvalidBucketName"
// The Content-MD5 you specified is not valid.
ErrInvalidDigest ErrorCode = "InvalidDigest"
ErrInvalidRange ErrorCode = "InvalidRange"
ErrInvalidToken ErrorCode = "InvalidToken"
ErrKeyTooLong ErrorCode = "KeyTooLongError" // This is not a typo: Error is part of the string, but redundant in the constant name
ErrMalformedPOSTRequest ErrorCode = "MalformedPOSTRequest"
// One or more of the specified parts could not be found. The part might
// not have been uploaded, or the specified entity tag might not have
// matched the part's entity tag.
ErrInvalidPart ErrorCode = "InvalidPart"
// The list of parts was not in ascending order. Parts list must be
// specified in order by part number.
ErrInvalidPartOrder ErrorCode = "InvalidPartOrder"
ErrInvalidURI ErrorCode = "InvalidURI"
ErrMetadataTooLarge ErrorCode = "MetadataTooLarge"
ErrMethodNotAllowed ErrorCode = "MethodNotAllowed"
ErrMalformedXML ErrorCode = "MalformedXML"
// You must provide the Content-Length HTTP header.
ErrMissingContentLength ErrorCode = "MissingContentLength"
// See BucketNotFound() for a helper function for this error:
ErrNoSuchBucket ErrorCode = "NoSuchBucket"
// See KeyNotFound() for a helper function for this error:
ErrNoSuchKey ErrorCode = "NoSuchKey"
// The specified multipart upload does not exist. The upload ID might be
// invalid, or the multipart upload might have been aborted or completed.
ErrNoSuchUpload ErrorCode = "NoSuchUpload"
ErrNoSuchVersion ErrorCode = "NoSuchVersion"
// No need to retransmit the object
ErrNotModified ErrorCode = "NotModified"
ErrRequestTimeTooSkewed ErrorCode = "RequestTimeTooSkewed"
ErrTooManyBuckets ErrorCode = "TooManyBuckets"
ErrNotImplemented ErrorCode = "NotImplemented"
ErrInternal ErrorCode = "InternalError"
)
// INTERNAL errors! These are not part of the S3 interface, they are codes
// we have declared ourselves. Should all map to a 500 status code:
const (
ErrInternalPageNotImplemented InternalErrorCode = "PaginationNotImplemented"
)
// errorResponse should be implemented by any type that needs to be handled by
// ensureErrorResponse.
type errorResponse interface {
Error
enrich(requestID string)
}
func ensureErrorResponse(err error, requestID string) Error {
switch err := err.(type) {
case errorResponse:
err.enrich(requestID)
return err
case ErrorCode:
return &ErrorResponse{
Code: err,
RequestID: requestID,
Message: string(err),
}
default:
return &ErrorResponse{
Code: ErrInternal,
Message: "Internal Error",
RequestID: requestID,
}
}
}
type Error interface {
error
ErrorCode() ErrorCode
}
// ErrorResponse is the base error type returned by S3 when any error occurs.
//
// Some errors contain their own additional fields in the response, for example
// ErrRequestTimeTooSkewed, which contains the server time and the skew limit.
// To create one of these responses, subclass it (but please don't export it):
//
// type notQuiteRightResponse struct {
// ErrorResponse
// ExtraField int
// }
//
// Next, create a constructor that populates the error. Interfaces won't work
// for this job as the error itself does double-duty as the XML response
// object. Fill the struct out however you please, but don't forget to assign
// Code and Message:
//
// func NotQuiteRight(at time.Time, max time.Duration) error {
// code := ErrNotQuiteRight
// return ¬QuiteRightResponse{
// ErrorResponse{Code: code, Message: code.Message()},
// 123456789,
// }
// }
type ErrorResponse struct {
XMLName xml.Name `xml:"Error"`
Code ErrorCode
Message string `xml:",omitempty"`
RequestID string `xml:"RequestId,omitempty"`
HostID string `xml:"HostId,omitempty"`
}
func (e *ErrorResponse) ErrorCode() ErrorCode { return e.Code }
func (e *ErrorResponse) Error() string {
return fmt.Sprintf("%s: %s", e.Code, e.Message)
}
func (r *ErrorResponse) enrich(requestID string) {
r.RequestID = requestID
}
func ErrorMessage(code ErrorCode, message string) error {
return &ErrorResponse{Code: code, Message: message}
}
func ErrorMessagef(code ErrorCode, message string, args ...interface{}) error {
return &ErrorResponse{Code: code, Message: fmt.Sprintf(message, args...)}
}
type ErrorInvalidArgumentResponse struct {
ErrorResponse
ArgumentName string `xml:"ArgumentName"`
ArgumentValue string `xml:"ArgumentValue"`
}
func ErrorInvalidArgument(name, value, message string) error {
return &ErrorInvalidArgumentResponse{
ErrorResponse: ErrorResponse{Code: ErrInvalidArgument, Message: message},
ArgumentName: name, ArgumentValue: value}
}
// ErrorCode represents an S3 error code, documented here:
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
type ErrorCode string
func (e ErrorCode) ErrorCode() ErrorCode { return e }
func (e ErrorCode) Error() string { return string(e) }
// InternalErrorCode represents an GoFakeS3 error code. It maps to ErrInternal
// when constructing a response.
type InternalErrorCode string
func (e InternalErrorCode) ErrorCode() ErrorCode { return ErrInternal }
func (e InternalErrorCode) Error() string { return string(ErrInternal) }
// Message tries to return the same string as S3 would return for the error
// response, when it is known, or nothing when it is not. If you see the status
// text for a code we don't have listed in here in the wild, please let us
// know!
func (e ErrorCode) Message() string {
switch e {
case ErrInvalidBucketName:
return `Bucket name must match the regex "^[a-zA-Z0-9.\-_]{1,255}$"`
case ErrNoSuchBucket:
return "The specified bucket does not exist"
case ErrRequestTimeTooSkewed:
return "The difference between the request time and the current time is too large"
case ErrMalformedXML:
return "The XML you provided was not well-formed or did not validate against our published schema"
default:
return ""
}
}
func (e ErrorCode) Status() int {
switch e {
case ErrBucketAlreadyExists,
ErrBucketNotEmpty:
return http.StatusConflict
case ErrBadDigest,
ErrIllegalVersioningConfiguration,
ErrIncompleteBody,
ErrIncorrectNumberOfFilesInPostRequest,
ErrInlineDataTooLarge,
ErrInvalidArgument,
ErrInvalidBucketName,
ErrInvalidDigest,
ErrInvalidPart,
ErrInvalidPartOrder,
ErrInvalidToken,
ErrInvalidURI,
ErrKeyTooLong,
ErrMetadataTooLarge,
ErrMethodNotAllowed,
ErrMalformedPOSTRequest,
ErrMalformedXML,
ErrTooManyBuckets:
return http.StatusBadRequest
case ErrRequestTimeTooSkewed:
return http.StatusForbidden
case ErrInvalidRange:
return http.StatusRequestedRangeNotSatisfiable
case ErrNoSuchBucket,
ErrNoSuchKey,
ErrNoSuchUpload,
ErrNoSuchVersion:
return http.StatusNotFound
case ErrNotImplemented:
return http.StatusNotImplemented
case ErrNotModified:
return http.StatusNotModified
case ErrMissingContentLength:
return http.StatusLengthRequired
case ErrInternal:
return http.StatusInternalServerError
}
return http.StatusInternalServerError
}
// HasErrorCode asserts that the error has a specific error code:
//
// if HasErrorCode(err, ErrNoSuchBucket) {
// // handle condition
// }
//
// If err is nil and code is ErrNone, HasErrorCode returns true.
func HasErrorCode(err error, code ErrorCode) bool {
if err == nil && code == "" {
return true
}
s3err, ok := err.(interface{ ErrorCode() ErrorCode })
if !ok {
return false
}
return s3err.ErrorCode() == code
}
// IsAlreadyExists asserts that the error is a kind that indicates the resource
// already exists, similar to os.IsExist.
func IsAlreadyExists(err error) bool {
return HasErrorCode(err, ErrBucketAlreadyExists)
}
type resourceErrorResponse struct {
ErrorResponse
Resource string
}
var _ errorResponse = &resourceErrorResponse{}
func ResourceError(code ErrorCode, resource string) error {
return &resourceErrorResponse{
ErrorResponse{Code: code, Message: code.Message()},
resource,
}
}
func BucketNotFound(bucket string) error { return ResourceError(ErrNoSuchBucket, bucket) }
func KeyNotFound(key string) error { return ResourceError(ErrNoSuchKey, key) }
type requestTimeTooSkewedResponse struct {
ErrorResponse
ServerTime time.Time
MaxAllowedSkewMilliseconds durationAsMilliseconds
}
var _ errorResponse = &requestTimeTooSkewedResponse{}
func requestTimeTooSkewed(at time.Time, max time.Duration) error {
code := ErrRequestTimeTooSkewed
return &requestTimeTooSkewedResponse{
ErrorResponse{Code: code, Message: code.Message()},
at, durationAsMilliseconds(max),
}
}
// durationAsMilliseconds tricks xml.Marshal into serialising a time.Duration as
// truncated milliseconds instead of nanoseconds.
type durationAsMilliseconds time.Duration
func (m durationAsMilliseconds) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
var s = fmt.Sprintf("%d", time.Duration(m)/time.Millisecond)
return e.EncodeElement(s, start)
}