forked from johannesboyne/gofakes3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
messages.go
540 lines (446 loc) · 16.6 KB
/
messages.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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
package gofakes3
import (
"encoding/xml"
"fmt"
"sort"
"strings"
"time"
)
type Storage struct {
XMLName xml.Name `xml:"ListAllMyBucketsResult"`
Xmlns string `xml:"xmlns,attr"`
Owner *UserInfo `xml:"Owner,omitempty"`
Buckets Buckets `xml:"Buckets>Bucket"`
}
type UserInfo struct {
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName"`
}
type Buckets []BucketInfo
// Names is a deterministic convenience function returning a sorted list of bucket names.
func (b Buckets) Names() []string {
out := make([]string, len(b))
for i, v := range b {
out[i] = v.Name
}
sort.Strings(out)
return out
}
// BucketInfo represents a single bucket returned by the ListBuckets response.
type BucketInfo struct {
Name string `xml:"Name"`
// CreationDate is required; without it, boto returns the error "('String
// does not contain a date:', '')"
CreationDate ContentTime `xml:"CreationDate"`
}
// CommonPrefix is used in Bucket.CommonPrefixes to list partial delimited keys
// that represent pseudo-directories.
type CommonPrefix struct {
Prefix string `xml:"Prefix"`
}
type CompletedPart struct {
PartNumber int `xml:"PartNumber"`
ETag string `xml:"ETag"`
}
type CompleteMultipartUploadRequest struct {
Parts []CompletedPart `xml:"Part"`
}
func (c CompleteMultipartUploadRequest) partsAreSorted() bool {
return sort.IntsAreSorted(c.partIDs())
}
func (c CompleteMultipartUploadRequest) partIDs() []int {
inParts := make([]int, 0, len(c.Parts))
for _, inputPart := range c.Parts {
inParts = append(inParts, inputPart.PartNumber)
}
sort.Ints(inParts)
return inParts
}
type CompleteMultipartUploadResult struct {
Location string `xml:"Location"`
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
ETag string `xml:"ETag"`
}
type Content struct {
Key string `xml:"Key"`
LastModified ContentTime `xml:"LastModified"`
ETag string `xml:"ETag"`
Size int64 `xml:"Size"`
StorageClass StorageClass `xml:"StorageClass,omitempty"`
Owner *UserInfo `xml:"Owner,omitempty"`
}
type ContentTime struct {
time.Time
}
func NewContentTime(t time.Time) ContentTime {
return ContentTime{t}
}
func (c ContentTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
// This is the format expected by the aws xml code, not the default.
if !c.IsZero() {
var s = c.Format("2006-01-02T15:04:05.999Z")
return e.EncodeElement(s, start)
}
return nil
}
type DeleteRequest struct {
Objects []ObjectID `xml:"Object"`
// Element to enable quiet mode for the request. When you add this element,
// you must set its value to true.
//
// By default, the operation uses verbose mode in which the response
// includes the result of deletion of each key in your request. In quiet
// mode the response includes only keys where the delete operation
// encountered an error. For a successful deletion, the operation does not
// return any information about the delete in the response body.
Quiet bool `xml:"Quiet"`
}
// MultiDeleteResult contains the response from a multi delete operation.
type MultiDeleteResult struct {
XMLName xml.Name `xml:"DeleteResult"`
Deleted []ObjectID `xml:"Deleted"`
Error []ErrorResult `xml:",omitempty"`
}
func (d MultiDeleteResult) AsError() error {
if len(d.Error) == 0 {
return nil
}
var strs = make([]string, 0, len(d.Error))
for _, er := range d.Error {
strs = append(strs, er.String())
}
return fmt.Errorf("gofakes3: multi delete failed:\n%s", strings.Join(strs, "\n"))
}
type ErrorResult struct {
XMLName xml.Name `xml:"Error"`
Key string `xml:"Key,omitempty"`
Code ErrorCode `xml:"Code,omitempty"`
Message string `xml:"Message,omitempty"`
Resource string `xml:"Resource,omitempty"`
RequestID string `xml:"RequestId,omitempty"`
}
func ErrorResultFromError(err error) ErrorResult {
switch err := err.(type) {
case *resourceErrorResponse:
return ErrorResult{
Resource: err.Resource,
RequestID: err.RequestID,
Message: err.Message,
Code: err.Code,
}
case *ErrorResponse:
return ErrorResult{
RequestID: err.RequestID,
Message: err.Message,
Code: err.Code,
}
case Error:
return ErrorResult{Code: err.ErrorCode()}
default:
return ErrorResult{Code: ErrInternal}
}
}
func (er ErrorResult) String() string {
return fmt.Sprintf("%s: [%s] %s", er.Key, er.Code, er.Message)
}
type InitiateMultipartUpload struct {
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
UploadID UploadID `xml:"UploadId"`
}
type ListBucketResultBase struct {
XMLName xml.Name `xml:"ListBucketResult"`
Xmlns string `xml:"xmlns,attr"`
// Name of the bucket.
Name string `xml:"Name"`
// Specifies whether (true) or not (false) all of the results were
// returned. If the number of results exceeds that specified by MaxKeys,
// all of the results might not be returned.
IsTruncated bool `xml:"IsTruncated"`
// Causes keys that contain the same string between the prefix and the
// first occurrence of the delimiter to be rolled up into a single result
// element in the CommonPrefixes collection. These rolled-up keys are not
// returned elsewhere in the response.
//
// NOTE: Each rolled-up result in CommonPrefixes counts as only one return
// against the MaxKeys value. (BW: been waiting to find some confirmation of
// that for a while!)
Delimiter string `xml:"Delimiter,omitempty"`
Prefix string `xml:"Prefix"`
MaxKeys int64 `xml:"MaxKeys,omitempty"`
CommonPrefixes []CommonPrefix `xml:"CommonPrefixes,omitempty"`
Contents []*Content `xml:"Contents"`
}
type GetBucketLocation struct {
XMLName xml.Name `xml:"LocationConstraint"`
Xmlns string `xml:"xmlns,attr"`
LocationConstraint string `xml:",chardata"`
}
type ListBucketResult struct {
ListBucketResultBase
// Indicates where in the bucket listing begins. Marker is included in the
// response if it was sent with the request.
Marker string `xml:"Marker"`
// When the response is truncated (that is, the IsTruncated element value
// in the response is true), you can use the key name in this field as a
// marker in the subsequent request to get next set of objects. Amazon S3
// lists objects in UTF-8 character encoding in lexicographical order.
//
// NOTE: This element is returned only if you specify a delimiter request
// parameter. If the response does not include the NextMarker and it is
// truncated, you can use the value of the last Key in the response as the
// marker in the subsequent request to get the next set of object keys.
NextMarker string `xml:"NextMarker,omitempty"`
}
type ListBucketResultV2 struct {
ListBucketResultBase
// If ContinuationToken was sent with the request, it is included in the
// response.
ContinuationToken string `xml:"ContinuationToken,omitempty"`
// Returns the number of keys included in the response. The value is always
// less than or equal to the MaxKeys value.
KeyCount int64 `xml:"KeyCount,omitempty"`
// If the response is truncated, Amazon S3 returns this parameter with a
// continuation token. You can specify the token as the continuation-token
// in your next request to retrieve the next set of keys.
NextContinuationToken string `xml:"NextContinuationToken,omitempty"`
// If StartAfter was sent with the request, it is included in the response.
StartAfter string `xml:"StartAfter,omitempty"`
}
type DeleteMarker struct {
XMLName xml.Name `xml:"DeleteMarker"`
Key string `xml:"Key"`
VersionID VersionID `xml:"VersionId"`
IsLatest bool `xml:"IsLatest"`
LastModified ContentTime `xml:"LastModified,omitempty"`
Owner *UserInfo `xml:"Owner,omitempty"`
}
var _ VersionItem = &DeleteMarker{}
func (d DeleteMarker) GetVersionID() VersionID { return d.VersionID }
func (d *DeleteMarker) setVersionID(i VersionID) { d.VersionID = i }
type Version struct {
XMLName xml.Name `xml:"Version"`
Key string `xml:"Key"`
VersionID VersionID `xml:"VersionId"`
IsLatest bool `xml:"IsLatest"`
LastModified ContentTime `xml:"LastModified,omitempty"`
Size int64 `xml:"Size"`
// According to the S3 docs, this is always STANDARD for a Version:
StorageClass StorageClass `xml:"StorageClass"`
ETag string `xml:"ETag"`
Owner *UserInfo `xml:"Owner,omitempty"`
}
var _ VersionItem = &Version{}
func (v Version) GetVersionID() VersionID { return v.VersionID }
func (v *Version) setVersionID(i VersionID) { v.VersionID = i }
type VersionItem interface {
GetVersionID() VersionID
setVersionID(v VersionID)
}
type ListBucketVersionsResult struct {
XMLName xml.Name `xml:"ListBucketVersionsResult"`
Xmlns string `xml:"xmlns,attr"`
Name string `xml:"Name"`
Delimiter string `xml:"Delimiter,omitempty"`
Prefix string `xml:"Prefix,omitempty"`
CommonPrefixes []CommonPrefix `xml:"CommonPrefixes,omitempty"`
IsTruncated bool `xml:"IsTruncated"`
MaxKeys int64 `xml:"MaxKeys"`
// Marks the last Key returned in a truncated response.
KeyMarker string `xml:"KeyMarker,omitempty"`
// When the number of responses exceeds the value of MaxKeys, NextKeyMarker
// specifies the first key not returned that satisfies the search criteria.
// Use this value for the key-marker request parameter in a subsequent
// request.
NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
// Marks the last version of the Key returned in a truncated response.
VersionIDMarker VersionID `xml:"VersionIdMarker,omitempty"`
// When the number of responses exceeds the value of MaxKeys,
// NextVersionIdMarker specifies the first object version not returned that
// satisfies the search criteria. Use this value for the version-id-marker
// request parameter in a subsequent request.
NextVersionIDMarker VersionID `xml:"NextVersionIdMarker,omitempty"`
// AWS responds with a list of either <Version> or <DeleteMarker> objects. The order
// needs to be preserved and they need to be direct of ListBucketVersionsResult:
// <ListBucketVersionsResult>
// <DeleteMarker ... />
// <Version ... />
// <DeleteMarker ... />
// <Version ... />
// </ListBucketVersionsResult>
Versions []VersionItem
// prefixes maintains an index of prefixes that have already been seen.
// This is a convenience for backend implementers like s3bolt and s3mem,
// which operate on a full, flat list of keys.
prefixes map[string]bool
}
func NewListBucketVersionsResult(
bucketName string,
prefix *Prefix,
page *ListBucketVersionsPage,
) *ListBucketVersionsResult {
result := &ListBucketVersionsResult{
Xmlns: "http://s3.amazonaws.com/doc/2006-03-01/",
Name: bucketName,
}
if prefix != nil {
result.Prefix = prefix.Prefix
result.Delimiter = prefix.Delimiter
}
if page != nil {
result.MaxKeys = page.MaxKeys
result.KeyMarker = page.KeyMarker
result.VersionIDMarker = page.VersionIDMarker
}
return result
}
func (b *ListBucketVersionsResult) AddPrefix(prefix string) {
if b.prefixes == nil {
b.prefixes = map[string]bool{}
} else if b.prefixes[prefix] {
return
}
b.prefixes[prefix] = true
b.CommonPrefixes = append(b.CommonPrefixes, CommonPrefix{Prefix: prefix})
}
type ListMultipartUploadsResult struct {
Bucket string `xml:"Bucket"`
// Together with upload-id-marker, this parameter specifies the multipart upload
// after which listing should begin.
KeyMarker string `xml:"KeyMarker,omitempty"`
// Together with key-marker, specifies the multipart upload after which listing
// should begin. If key-marker is not specified, the upload-id-marker parameter
// is ignored.
UploadIDMarker UploadID `xml:"UploadIdMarker,omitempty"`
NextKeyMarker string `xml:"NextKeyMarker,omitempty"`
NextUploadIDMarker UploadID `xml:"NextUploadIdMarker,omitempty"`
// Sets the maximum number of multipart uploads, from 1 to 1,000, to return
// in the response body. 1,000 is the maximum number of uploads that can be
// returned in a response.
MaxUploads int64 `xml:"MaxUploads,omitempty"`
Delimiter string `xml:"Delimiter,omitempty"`
// Lists in-progress uploads only for those keys that begin with the specified
// prefix.
Prefix string `xml:"Prefix,omitempty"`
CommonPrefixes []CommonPrefix `xml:"CommonPrefixes,omitempty"`
IsTruncated bool `xml:"IsTruncated,omitempty"`
Uploads []ListMultipartUploadItem `xml:"Upload"`
}
type ListMultipartUploadItem struct {
Key string `xml:"Key"`
UploadID UploadID `xml:"UploadId"`
Initiator *UserInfo `xml:"Initiator,omitempty"`
Owner *UserInfo `xml:"Owner,omitempty"`
StorageClass StorageClass `xml:"StorageClass,omitempty"`
Initiated ContentTime `xml:"Initiated,omitempty"`
}
type ListMultipartUploadPartsResult struct {
XMLName xml.Name `xml:"ListPartsResult"`
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
UploadID UploadID `xml:"UploadId"`
StorageClass StorageClass `xml:"StorageClass,omitempty"`
Initiator *UserInfo `xml:"Initiator,omitempty"`
Owner *UserInfo `xml:"Owner,omitempty"`
PartNumberMarker int `xml:"PartNumberMarker"`
NextPartNumberMarker int `xml:"NextPartNumberMarker"`
MaxParts int64 `xml:"MaxParts"`
IsTruncated bool `xml:"IsTruncated,omitempty"`
Parts []ListMultipartUploadPartItem `xml:"Part"`
}
type ListMultipartUploadPartItem struct {
PartNumber int `xml:"PartNumber"`
LastModified ContentTime `xml:"LastModified,omitempty"`
ETag string `xml:"ETag,omitempty"`
Size int64 `xml:"Size"`
}
// CopyObjectResult contains the response from a CopyObject operation.
type CopyObjectResult struct {
XMLName xml.Name `xml:"CopyObjectResult"`
ETag string `xml:"ETag,omitempty"`
LastModified ContentTime `xml:"LastModified,omitempty"`
}
// MFADeleteStatus is used by VersioningConfiguration.
type MFADeleteStatus string
func (v MFADeleteStatus) Enabled() bool { return v == MFADeleteEnabled }
func (v *MFADeleteStatus) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
if err := d.DecodeElement(&s, &start); err != nil {
// FIXME: this doesn't seem to detect or report errors if the element is the wrong type.
return err
}
s = strings.ToLower(strings.TrimSpace(s))
if s == "enabled" {
*v = MFADeleteEnabled
} else if s == "disabled" {
*v = MFADeleteDisabled
} else {
return ErrorMessagef(ErrIllegalVersioningConfiguration, "unexpected value %q for MFADeleteStatus, expected 'Enabled' or 'Disabled'", s)
}
return nil
}
const (
MFADeleteNone MFADeleteStatus = ""
MFADeleteEnabled MFADeleteStatus = "Enabled"
MFADeleteDisabled MFADeleteStatus = "Disabled"
)
type ObjectID struct {
Key string `xml:"Key"`
// Versions not supported in GoFakeS3 yet.
VersionID string `xml:"VersionId,omitempty" json:"VersionId,omitempty"`
}
type StorageClass string
func (s StorageClass) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if s == "" {
s = StorageStandard
}
return e.EncodeElement(string(s), start)
}
const (
StorageStandard StorageClass = "STANDARD"
)
// UploadID uses a string as the underlying type, but the string should only
// represent a decimal integer. See uploader.uploadID for details.
type UploadID string
type VersionID string
type VersioningConfiguration struct {
XMLName xml.Name `xml:"VersioningConfiguration"`
Status VersioningStatus `xml:"Status"`
// When enabled, the bucket owner must include the x-amz-mfa request header
// in requests to change the versioning state of a bucket and to
// permanently delete a versioned object.
MFADelete MFADeleteStatus `xml:"MfaDelete"`
}
func (v *VersioningConfiguration) Enabled() bool {
return v.Status == VersioningEnabled
}
func (v *VersioningConfiguration) SetEnabled(enabled bool) {
if enabled {
v.Status = VersioningEnabled
} else {
v.Status = VersioningSuspended
}
}
type VersioningStatus string
func (v *VersioningStatus) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
if err := d.DecodeElement(&s, &start); err != nil {
// FIXME: this doesn't seem to detect or report errors if the element is the wrong type.
return err
}
s = strings.ToLower(strings.TrimSpace(s))
if s == "enabled" {
*v = VersioningEnabled
} else if s == "suspended" {
*v = VersioningSuspended
} else {
return ErrorMessagef(ErrIllegalVersioningConfiguration, "unexpected value %q for Status, expected 'Enabled' or 'Suspended'", s)
}
return nil
}
const (
VersioningNone VersioningStatus = ""
VersioningEnabled VersioningStatus = "Enabled"
VersioningSuspended VersioningStatus = "Suspended"
)