diff options
| -rw-r--r-- | account.go | 19 | ||||
| -rw-r--r-- | bulk.go | 39 | ||||
| -rw-r--r-- | container.go | 15 | ||||
| -rw-r--r-- | container_iterator.go | 4 | ||||
| -rw-r--r-- | download.go | 16 | ||||
| -rw-r--r-- | errors.go | 2 | ||||
| -rw-r--r-- | field_metadata.go | 10 | ||||
| -rw-r--r-- | field_string.go | 10 | ||||
| -rw-r--r-- | field_time.go | 34 | ||||
| -rw-r--r-- | field_uint64.go | 12 | ||||
| -rw-r--r-- | generated.go | 174 | ||||
| -rw-r--r-- | generated.go.in | 50 | ||||
| -rw-r--r-- | headers.go | 76 | ||||
| -rw-r--r-- | iterator.go | 30 | ||||
| -rw-r--r-- | object.go | 55 | ||||
| -rw-r--r-- | object_iterator.go | 6 | ||||
| -rw-r--r-- | request.go | 34 | ||||
| -rw-r--r-- | tests/account_test.go | 6 | ||||
| -rw-r--r-- | tests/bulk_delete_test.go | 10 | ||||
| -rw-r--r-- | tests/bulk_upload_test.go | 6 | ||||
| -rw-r--r-- | tests/container_iterator_test.go | 2 | ||||
| -rw-r--r-- | tests/container_test.go | 8 | ||||
| -rw-r--r-- | tests/field_test.go | 46 | ||||
| -rw-r--r-- | tests/headers_test.go | 12 | ||||
| -rw-r--r-- | tests/object_iterator_test.go | 4 | ||||
| -rw-r--r-- | tests/object_test.go | 36 | ||||
| -rw-r--r-- | tests/shared_test.go | 4 |
27 files changed, 335 insertions, 385 deletions
@@ -98,7 +98,7 @@ func (a *Account) Headers() (AccountHeaders, error) { return AccountHeaders{}, err } - headers := AccountHeaders(headersFromHTTP(resp.Header)) + headers := AccountHeaders{headersFromHTTP(resp.Header)} err = headers.Validate() if err != nil { return headers, err @@ -113,15 +113,14 @@ func (a *Account) Invalidate() { a.headers = nil } -//Update updates the account using a POST request. To add URL parameters, pass -//a non-nil *RequestOptions. +//Update updates the account using a POST request. The headers in the headers +//attribute take precedence over those in opts.Headers. // //A successful POST request implies Invalidate() since it may change metadata. func (a *Account) Update(headers AccountHeaders, opts *RequestOptions) error { _, err := Request{ Method: "POST", - Headers: headersToHTTP(headers), - Options: opts, + Options: cloneRequestOptions(opts, headers.Headers), ExpectStatusCodes: []int{204}, }.Do(a.backend) if err == nil { @@ -130,17 +129,13 @@ func (a *Account) Update(headers AccountHeaders, opts *RequestOptions) error { return err } -//Create creates the account using a PUT request. To add URL parameters, pass -//a non-nil *RequestOptions. -// -//Note that this operation is only available to reseller admins, not to regular -//users. +//Create creates the account using a PUT request. This operation is only +//available to reseller admins, not to regular users. // //A successful PUT request implies Invalidate() since it may change metadata. -func (a *Account) Create(headers AccountHeaders, opts *RequestOptions) error { +func (a *Account) Create(opts *RequestOptions) error { _, err := Request{ Method: "PUT", - Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{201, 202}, DrainResponseBody: true, @@ -47,11 +47,11 @@ const ( //For example, when uploading an archive that contains the file "a/b/c": // // //This uploads the file into the container "a" as object "b/c". -// account.BulkUpload("", format, contents, nil, nil) +// account.BulkUpload("", format, contents, nil) // //This uploads the file into the container "foo" as object "a/b/c". -// account.BulkUpload("foo", format, contents, nil, nil) +// account.BulkUpload("foo", format, contents, nil) // //This uploads the file into the container "foo" as object "bar/baz/a/b/c". -// account.BulkUpload("foo/bar/baz", format, contents, nil, nil) +// account.BulkUpload("foo/bar/baz", format, contents, nil) // //The first return value indicates the number of files that have been created //on the server side. This may be lower than the number of files in the archive @@ -63,7 +63,7 @@ const ( // //This operation returns (0, ErrNotSupported) if the server does not support //bulk-uploading. -func (a *Account) BulkUpload(uploadPath string, format BulkUploadFormat, contents io.Reader, headers AccountHeaders, opts *RequestOptions) (int, error) { +func (a *Account) BulkUpload(uploadPath string, format BulkUploadFormat, contents io.Reader, opts *RequestOptions) (int, error) { caps, err := a.Capabilities() if err != nil { return 0, err @@ -75,11 +75,10 @@ func (a *Account) BulkUpload(uploadPath string, format BulkUploadFormat, content req := Request{ Method: "PUT", Body: contents, - Headers: headersToHTTP(headers), - Options: cloneRequestOptions(opts), + Options: cloneRequestOptions(opts, nil), ExpectStatusCodes: []int{200}, } - req.Headers.Set("Accept", "application/json") + req.Options.Headers.Set("Accept", "application/json") req.Options.Values.Set("extract-archive", string(format)) fields := strings.SplitN(strings.Trim(uploadPath, "/"), "/", 2) @@ -134,9 +133,8 @@ func makeBulkObjectError(fullName string, statusCode int) BulkObjectError { // numDeleted, numNotFound, err := container.Account().BulkDelete( // objects, []*schwift.Container{container}, nil, nil) // -//If the server does not support bulk-deletion, this function will just call -//Object.Delete() for each given object and Container.Delete() for each given -//container, and aggregate the result. +//If the server does not support bulk-deletion, this function falls back to +//deleting each object and container individually, and aggregates the result. // //If not nil, the error return value is *usually* an instance of //BulkError. @@ -144,7 +142,7 @@ func makeBulkObjectError(fullName string, statusCode int) BulkObjectError { //The objects may be located in multiple containers, but they and the //containers must all be located in the given account. (Otherwise, //ErrAccountMismatch is returned.) -func (a *Account) BulkDelete(objects []*Object, containers []*Container, headers AccountHeaders, opts *RequestOptions) (numDeleted int, numNotFound int, deleteError error) { +func (a *Account) BulkDelete(objects []*Object, containers []*Container, opts *RequestOptions) (numDeleted int, numNotFound int, deleteError error) { //validate that all given objects are in this account for _, obj := range objects { other := obj.Container().Account() @@ -165,7 +163,7 @@ func (a *Account) BulkDelete(objects []*Object, containers []*Container, headers return 0, 0, err } if caps.BulkDelete == nil { - return a.bulkDeleteSingle(objects, containers, headers, opts) + return a.bulkDeleteSingle(objects, containers, opts) } chunkSize := int(caps.BulkDelete.MaximumDeletesPerRequest) @@ -193,7 +191,7 @@ func (a *Account) BulkDelete(objects []*Object, containers []*Container, headers chunk := names[0:chunkSize] names = names[chunkSize:] - numDeletedNow, numNotFoundNow, err := a.bulkDelete(chunk, headers, opts) + numDeletedNow, numNotFoundNow, err := a.bulkDelete(chunk, opts) numDeleted += numDeletedNow numNotFound += numNotFoundNow if err != nil { @@ -206,7 +204,7 @@ func (a *Account) BulkDelete(objects []*Object, containers []*Container, headers //Implementation of BulkDelete() for servers that *do not* support bulk //deletion. -func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, headers AccountHeaders, opts *RequestOptions) (int, int, error) { +func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, opts *RequestOptions) (int, int, error) { var ( numDeleted = 0 numNotFound = 0 @@ -235,7 +233,7 @@ func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, h } for _, obj := range objects { - err := obj.Delete(ObjectHeaders(headers), opts) //this implies Invalidate() + err := obj.Delete(opts) //this implies Invalidate() err = handleSingleError(obj.Container().Name(), obj.Name(), err) if err != nil { return numDeleted, numNotFound, err @@ -243,7 +241,7 @@ func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, h } for _, container := range containers { - err := container.Delete(ContainerHeaders(headers), opts) //this implies Invalidate() + err := container.Delete(opts) //this implies Invalidate() err = handleSingleError(container.Name(), "", err) if err != nil { return numDeleted, numNotFound, err @@ -263,16 +261,15 @@ func (a *Account) bulkDeleteSingle(objects []*Object, containers []*Container, h //Implementation of BulkDelete() for servers that *do* support bulk deletion. //This function is called *after* chunking, so `len(names) <= //account.Capabilities.BulkDelete.MaximumDeletesPerRequest`. -func (a *Account) bulkDelete(names []string, headers AccountHeaders, opts *RequestOptions) (int, int, error) { +func (a *Account) bulkDelete(names []string, opts *RequestOptions) (int, int, error) { req := Request{ Method: "DELETE", Body: strings.NewReader(strings.Join(names, "\n") + "\n"), - Headers: headersToHTTP(headers), - Options: cloneRequestOptions(opts), + Options: cloneRequestOptions(opts, nil), ExpectStatusCodes: []int{200}, } - req.Headers.Set("Accept", "application/json") - req.Headers.Set("Content-Type", "text/plain") + req.Options.Headers.Set("Accept", "application/json") + req.Options.Headers.Set("Content-Type", "text/plain") req.Options.Values.Set("bulk-delete", "true") resp, err := req.Do(a.backend) if err != nil { diff --git a/container.go b/container.go index 741350e..1d310aa 100644 --- a/container.go +++ b/container.go @@ -36,7 +36,7 @@ type Container struct { //container's existence, or chain this function with the EnsureExists() //function like so: // -// container, err := account.Container("documents").EnsureExists() +// container, err := account.Container("documents").EnsureExists() func (a *Account) Container(name string) *Container { return &Container{a: a, name: name} } @@ -81,7 +81,7 @@ func (c *Container) Headers() (ContainerHeaders, error) { return ContainerHeaders{}, err } - headers := ContainerHeaders(headersFromHTTP(resp.Header)) + headers := ContainerHeaders{headersFromHTTP(resp.Header)} err = headers.Validate() if err != nil { return headers, err @@ -100,8 +100,7 @@ func (c *Container) Update(headers ContainerHeaders, opts *RequestOptions) error _, err := Request{ Method: "POST", ContainerName: c.name, - Headers: headersToHTTP(headers), - Options: opts, + Options: cloneRequestOptions(opts, headers.Headers), ExpectStatusCodes: []int{204}, }.Do(c.a.backend) if err == nil { @@ -116,11 +115,10 @@ func (c *Container) Update(headers ContainerHeaders, opts *RequestOptions) error //This function can be used regardless of whether the container exists or not. // //A successful PUT request implies Invalidate() since it may change metadata. -func (c *Container) Create(headers ContainerHeaders, opts *RequestOptions) error { +func (c *Container) Create(opts *RequestOptions) error { _, err := Request{ Method: "PUT", ContainerName: c.name, - Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{201, 202}, DrainResponseBody: true, @@ -139,11 +137,10 @@ func (c *Container) Create(headers ContainerHeaders, opts *RequestOptions) error //This operation fails with http.StatusNotFound if the container does not exist. // //A successful DELETE request implies Invalidate(). -func (c *Container) Delete(headers ContainerHeaders, opts *RequestOptions) error { +func (c *Container) Delete(opts *RequestOptions) error { _, err := Request{ Method: "DELETE", ContainerName: c.name, - Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{204}, }.Do(c.a.backend) @@ -165,7 +162,7 @@ func (c *Container) Invalidate() { //This function returns the same container again, because its intended use is //with freshly constructed Container instances like so: // -// container, err := account.Container("documents").EnsureExists() +// container, err := account.Container("documents").EnsureExists() func (c *Container) EnsureExists() (*Container, error) { _, err := Request{ Method: "PUT", diff --git a/container_iterator.go b/container_iterator.go index c428335..debe475 100644 --- a/container_iterator.go +++ b/container_iterator.go @@ -64,9 +64,7 @@ type ContainerIterator struct { //When Prefix is set, only containers whose name starts with this string are //returned. Prefix string - //Headers may contain additional headers to include with the GET request. - Headers map[string]string - //Options may contain additional query parameters for the GET request. + //Options may contain additional headers and query parameters for the GET request. Options *RequestOptions base *iteratorBase diff --git a/download.go b/download.go index 6515bbf..dfd9a07 100644 --- a/download.go +++ b/download.go @@ -30,28 +30,26 @@ import ( // var obj *swift.Object // // //Do NOT do this! -// reader, err := obj.Download(nil, nil).AsReadCloser() +// reader, err := obj.Download(nil).AsReadCloser() // bytes, err := ioutil.ReadAll(reader) // err := reader.Close() // str := string(bytes) // // //Do this instead: -// str, err := obj.Download(nil, nil).AsString() +// str, err := obj.Download(nil).AsString() // -//Since the AsByteSlice and AsString method consume only the unread portion of -//the ReadCloser, and since they drain the ReadCloser irreversibly, the -//idiomatic way of using DownloadedObject is to call one of its members -//immediately, without storing the DownloadedObject instance in a variable -//first. +//Since all methods on DownloadedObject are irreversible, the idiomatic way of +//using DownloadedObject is to call one of its members immediately, without +//storing the DownloadedObject instance in a variable first. // // var obj *swift.Object // // //Do NOT do this! -// downloaded := obj.Download(nil, nil) +// downloaded := obj.Download(nil) // reader, err := downloaded.AsReadCloser() // // //Do this instead: -// reader, err := obj.Download(nil, nil).AsReadCloser() +// reader, err := obj.Download(nil).AsReadCloser() type DownloadedObject struct { r io.ReadCloser err error @@ -116,7 +116,7 @@ func (e BulkError) Error() string { //Is checks if the given error is an UnexpectedStatusCodeError for that status //code. For example: // -// err := container.Delete(nil, nil) +// err := container.Delete(nil) // if err != nil { // if schwift.Is(err, http.StatusNotFound) { // //container does not exist -> just what we wanted diff --git a/field_metadata.go b/field_metadata.go index 05dee32..90993d4 100644 --- a/field_metadata.go +++ b/field_metadata.go @@ -22,12 +22,12 @@ package schwift //in a headers instance. It cannot be directly constructed, but each headers //type has a method "Metadata" returning this type. For example: // -// hdr := make(ObjectHeaders) -// //the following two statements are equivalent -// hdr["X-Object-Meta-Access"] = "strictly confidential" -// hdr.Metadata().Set("Access", "strictly confidential") +// hdr := NewObjectHeaders() +// //the following two statements are equivalent +// hdr["X-Object-Meta-Access"] = "strictly confidential" +// hdr.Metadata().Set("Access", "strictly confidential") type FieldMetadata struct { - h headerInterface + h Headers k string } diff --git a/field_string.go b/field_string.go index f12d2d5..e0007e9 100644 --- a/field_string.go +++ b/field_string.go @@ -22,12 +22,12 @@ package schwift //whose value is a string. It cannot be directly constructed, but methods on //the Headers types return this type. For example: // -// hdr := make(AccountHeaders) -// //the following two statements are equivalent: -// hdr["X-Container-Read"] = ".r:*,.rlistings" -// hdr.ReadACL().Set(".r:*,.rlistings") +// hdr := NewAccountHeaders() +// //the following two statements are equivalent: +// hdr["X-Container-Read"] = ".r:*,.rlistings" +// hdr.ReadACL().Set(".r:*,.rlistings") type FieldString struct { - h headerInterface + h Headers k string } diff --git a/field_time.go b/field_time.go index 2506f3c..d367f68 100644 --- a/field_time.go +++ b/field_time.go @@ -28,25 +28,25 @@ import ( //FieldHTTPTimeReadonly is a helper type that provides type-safe access to a //readonly Swift header whose value is a HTTP timestamp like this: // -// Mon, 02 Jan 2006 15:04:05 GMT +// Mon, 02 Jan 2006 15:04:05 GMT // //It cannot be directly constructed, but methods on the Headers types return //this type. For example: // -// //suppose you have: -// hdr, err := obj.Headers() +// //suppose you have: +// hdr, err := obj.Headers() // -// //you could do this: -// time, err := time.Parse(time.RFC1123, hdr.Get("Last-Modified")) +// //you could do this: +// time, err := time.Parse(time.RFC1123, hdr.Get("Last-Modified")) // -// //or you can just: -// time := hdr.UpdatedAt().Get() +// //or you can just: +// time := hdr.UpdatedAt().Get() // //Don't worry about the missing `err` in the last line. When the header fails //to parse, Object.Headers() already returns the corresponding //MalformedHeaderError. type FieldHTTPTimeReadonly struct { - h headerInterface + h Headers k string } @@ -83,21 +83,21 @@ func (f FieldHTTPTimeReadonly) validate() error { //header whose value is a UNIX timestamp. It cannot be directly constructed, //but methods on the Headers types return this type. For example: // -// //suppose you have: -// hdr, err := obj.Headers() +// //suppose you have: +// hdr, err := obj.Headers() // -// //you could do all this: -// sec, err := strconv.ParseFloat(hdr.Get("X-Delete-At"), 64) -// time := time.Unix(0, int64(1e9 * sec)) +// //you could do all this: +// sec, err := strconv.ParseFloat(hdr.Get("X-Delete-At"), 64) +// time := time.Unix(0, int64(1e9 * sec)) // -// //or you can just: -// time := hdr.ExpiresAt().Get() +// //or you can just: +// time := hdr.ExpiresAt().Get() // //Don't worry about the missing `err` in the last line. When the header fails //to parse, Object.Headers() already returns the corresponding //MalformedHeaderError. type FieldUnixTime struct { - h headerInterface + h Headers k string } @@ -151,7 +151,7 @@ func (f FieldUnixTime) validate() error { //FieldUnixTimeReadonly is a readonly variant of FieldUnixTime. It is used for //fields that cannot be set by the client. type FieldUnixTimeReadonly struct { - h headerInterface + h Headers k string } diff --git a/field_uint64.go b/field_uint64.go index a14f558..2a542ae 100644 --- a/field_uint64.go +++ b/field_uint64.go @@ -26,12 +26,12 @@ import ( //whose value is an unsigned integer. It cannot be directly constructed, but //methods on the Headers types return this type. For example: // -// hdr := make(AccountHeaders) -// //the following two statements are equivalent: -// hdr["X-Account-Meta-Quota-Bytes"] = "1048576" -// hdr.QuotaBytes().Set(1 << 20) +// hdr := NewAccountHeaders() +// //the following two statements are equivalent: +// hdr["X-Account-Meta-Quota-Bytes"] = "1048576" +// hdr.BytesUsedQuota().Set(1 << 20) type FieldUint64 struct { - h headerInterface + h Headers k string } @@ -85,7 +85,7 @@ func (f FieldUint64) validate() error { //FieldUint64Readonly is a readonly variant of FieldUint64. It is used for //fields that cannot be set by the client. type FieldUint64Readonly struct { - h headerInterface + h Headers k string } diff --git a/generated.go b/generated.go index 2c109c1..abc4f90 100644 --- a/generated.go +++ b/generated.go @@ -8,38 +8,18 @@ package schwift -import "net/textproto" - //AccountHeaders contains the headers for a schwift.Account instance. // //To read and write well-known headers, use the methods on this type. -//To read and write arbitary headers, use the http.Header-like methods Get(), -//Set(), Clear(), Del(). -type AccountHeaders map[string]string - -//Clear sets the value for the specified header to the empty string. When the -//Headers instance is then sent to the server with Update(), the server will -//delete the value for that header; cf. Del(). -func (h AccountHeaders) Clear(key string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = "" +//To read and write arbitary headers, use the methods on the Headers supertype. +type AccountHeaders struct { + Headers } -//Del deletes a key from the Headers instance. When the Headers instance is -//then sent to the server with Update(), a key which has been deleted with -//Del() will remain unchanged on the server. -func (h AccountHeaders) Del(key string) { - delete(h, textproto.CanonicalMIMEHeaderKey(key)) -} - -//Get returns the value for the specified header. -func (h AccountHeaders) Get(key string) string { - return h[textproto.CanonicalMIMEHeaderKey(key)] -} - -//Set sets a new value for the specified header. Any existing value will be -//overwritten. -func (h AccountHeaders) Set(key, value string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = value +//NewAccountHeaders creates a new AccountHeaders instance. The return value +//will have the Headers attribute initialized to a non-nil map. +func NewAccountHeaders() AccountHeaders { + return AccountHeaders{make(Headers)} } //Validate returns MalformedHeaderError if the value of any well-known header @@ -77,74 +57,56 @@ func (h AccountHeaders) Validate() error { //BytesUsed provides type-safe access to X-Account-Bytes-Used headers. func (h AccountHeaders) BytesUsed() FieldUint64Readonly { - return FieldUint64Readonly{h, "X-Account-Bytes-Used"} + return FieldUint64Readonly{h.Headers, "X-Account-Bytes-Used"} } //ContainerCount provides type-safe access to X-Account-Container-Count headers. func (h AccountHeaders) ContainerCount() FieldUint64Readonly { - return FieldUint64Readonly{h, "X-Account-Container-Count"} + return FieldUint64Readonly{h.Headers, "X-Account-Container-Count"} } //Metadata provides type-safe access to X-Account-Meta- headers. func (h AccountHeaders) Metadata() FieldMetadata { - return FieldMetadata{h, "X-Account-Meta-"} + return FieldMetadata{h.Headers, "X-Account-Meta-"} } //BytesUsedQuota provides type-safe access to X-Account-Meta-Quota-Bytes headers. func (h AccountHeaders) BytesUsedQuota() FieldUint64 { - return FieldUint64{h, "X-Account-Meta-Quota-Bytes"} + return FieldUint64{h.Headers, "X-Account-Meta-Quota-Bytes"} } //TempURLKey2 provides type-safe access to X-Account-Meta-Temp-URL-Key-2 headers. func (h AccountHeaders) TempURLKey2() FieldString { - return FieldString{h, "X-Account-Meta-Temp-URL-Key-2"} + return FieldString{h.Headers, "X-Account-Meta-Temp-URL-Key-2"} } //TempURLKey provides type-safe access to X-Account-Meta-Temp-URL-Key headers. func (h AccountHeaders) TempURLKey() FieldString { - return FieldString{h, "X-Account-Meta-Temp-URL-Key"} + return FieldString{h.Headers, "X-Account-Meta-Temp-URL-Key"} } //ObjectCount provides type-safe access to X-Account-Object-Count headers. func (h AccountHeaders) ObjectCount() FieldUint64Readonly { - return FieldUint64Readonly{h, "X-Account-Object-Count"} + return FieldUint64Readonly{h.Headers, "X-Account-Object-Count"} } //CreatedAt provides type-safe access to X-Timestamp headers. func (h AccountHeaders) CreatedAt() FieldUnixTimeReadonly { - return FieldUnixTimeReadonly{h, "X-Timestamp"} + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} } //ContainerHeaders contains the headers for a schwift.Container instance. // //To read and write well-known headers, use the methods on this type. -//To read and write arbitary headers, use the http.Header-like methods Get(), -//Set(), Clear(), Del(). -type ContainerHeaders map[string]string - -//Clear sets the value for the specified header to the empty string. When the -//Headers instance is then sent to the server with Update(), the server will -//delete the value for that header; cf. Del(). -func (h ContainerHeaders) Clear(key string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = "" -} - -//Del deletes a key from the Headers instance. When the Headers instance is -//then sent to the server with Update(), a key which has been deleted with -//Del() will remain unchanged on the server. -func (h ContainerHeaders) Del(key string) { - delete(h, textproto.CanonicalMIMEHeaderKey(key)) +//To read and write arbitary headers, use the methods on the Headers supertype. +type ContainerHeaders struct { + Headers } -//Get returns the value for the specified header. -func (h ContainerHeaders) Get(key string) string { - return h[textproto.CanonicalMIMEHeaderKey(key)] -} - -//Set sets a new value for the specified header. Any existing value will be -//overwritten. -func (h ContainerHeaders) Set(key, value string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = value +//NewContainerHeaders creates a new ContainerHeaders instance. The return value +//will have the Headers attribute initialized to a non-nil map. +func NewContainerHeaders() ContainerHeaders { + return ContainerHeaders{make(Headers)} } //Validate returns MalformedHeaderError if the value of any well-known header @@ -203,117 +165,91 @@ func (h ContainerHeaders) Validate() error { //BytesUsed provides type-safe access to X-Container-Bytes-Used headers. func (h ContainerHeaders) BytesUsed() FieldUint64Readonly { - return FieldUint64Readonly{h, "X-Container-Bytes-Used"} + return FieldUint64Readonly{h.Headers, "X-Container-Bytes-Used"} } //Metadata provides type-safe access to X-Container-Meta- headers. func (h ContainerHeaders) Metadata() FieldMetadata { - return FieldMetadata{h, "X-Container-Meta-"} + return FieldMetadata{h.Headers, "X-Container-Meta-"} } //BytesUsedQuota provides type-safe access to X-Container-Meta-Quota-Bytes headers. func (h ContainerHeaders) BytesUsedQuota() FieldUint64 { - return FieldUint64{h, "X-Container-Meta-Quota-Bytes"} + return FieldUint64{h.Headers, "X-Container-Meta-Quota-Bytes"} } //ObjectCountQuota provides type-safe access to X-Container-Meta-Quota-Count headers. func (h ContainerHeaders) ObjectCountQuota() FieldUint64 { - return FieldUint64{h, "X-Container-Meta-Quota-Count"} + return FieldUint64{h.Headers, "X-Container-Meta-Quota-Count"} } //TempURLKey2 provides type-safe access to X-Container-Meta-Temp-URL-Key-2 headers. func (h ContainerHeaders) TempURLKey2() FieldString { - return FieldString{h, "X-Container-Meta-Temp-URL-Key-2"} + return FieldString{h.Headers, "X-Container-Meta-Temp-URL-Key-2"} } //TempURLKey provides type-safe access to X-Container-Meta-Temp-URL-Key headers. func (h ContainerHeaders) TempURLKey() FieldString { - return FieldString{h, "X-Container-Meta-Temp-URL-Key"} + return FieldString{h.Headers, "X-Container-Meta-Temp-URL-Key"} } //ObjectCount provides type-safe access to X-Container-Object-Count headers. func (h ContainerHeaders) ObjectCount() FieldUint64Readonly { - return FieldUint64Readonly{h, "X-Container-Object-Count"} + return FieldUint64Readonly{h.Headers, "X-Container-Object-Count"} } //ReadACL provides type-safe access to X-Container-Read headers. func (h ContainerHeaders) ReadACL() FieldString { - return FieldString{h, "X-Container-Read"} + return FieldString{h.Headers, "X-Container-Read"} } //SyncKey provides type-safe access to X-Container-Sync-Key headers. func (h ContainerHeaders) SyncKey() FieldString { - return FieldString{h, "X-Container-Sync-Key"} + return FieldString{h.Headers, "X-Container-Sync-Key"} } //SyncTo provides type-safe access to X-Container-Sync-To headers. func (h ContainerHeaders) SyncTo() FieldString { - return FieldString{h, "X-Container-Sync-To"} + return FieldString{h.Headers, "X-Container-Sync-To"} } //WriteACL provides type-safe access to X-Container-Write headers. func (h ContainerHeaders) WriteACL() FieldString { - return FieldString{h, "X-Container-Write"} + return FieldString{h.Headers, "X-Container-Write"} } //HistoryLocation provides type-safe access to X-History-Location headers. func (h ContainerHeaders) HistoryLocation() FieldString { - return FieldString{h, "X-History-Location"} + return FieldString{h.Headers, "X-History-Location"} } //StoragePolicy provides type-safe access to X-Storage-Policy headers. func (h ContainerHeaders) StoragePolicy() FieldString { - return FieldString{h, "X-Storage-Policy"} + return FieldString{h.Headers, "X-Storage-Policy"} } //CreatedAt provides type-safe access to X-Timestamp headers. func (h ContainerHeaders) CreatedAt() FieldUnixTimeReadonly { - return FieldUnixTimeReadonly{h, "X-Timestamp"} + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} } //VersionsLocation provides type-safe access to X-Versions-Location headers. func (h ContainerHeaders) VersionsLocation() FieldString { - return FieldString{h, "X-Versions-Location"} + return FieldString{h.Headers, "X-Versions-Location"} } //ObjectHeaders contains the headers for a schwift.Object instance. // //To read and write well-known headers, use the methods on this type. -//To read and write arbitary headers, use the http.Header-like methods Get(), -//Set(), Clear(), Del(). -type ObjectHeaders map[string]string - -//Clear sets the value for the specified header to the empty string. When the -//Headers instance is then sent to the server with Update(), the server will -//delete the value for that header; cf. Del(). -func (h ObjectHeaders) Clear(key string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = "" -} - -//Del deletes a key from the Headers instance. When the Headers instance -//is then sent to the server with Update(), Del() has different effects -//depending on context because of Swift's inconsistent API: -// -//For most writable attributes, a key which has been deleted with Del() will -//remain unchanged on the server. To remove the key on the server, use Clear() -//instead. -// -//For object metadata (but not other object attributes), deleting a key will -//cause that key to be deleted on the server. Del() is identical to Clear() in -//this case. -func (h ObjectHeaders) Del(key string) { - delete(h, textproto.CanonicalMIMEHeaderKey(key)) -} - -//Get returns the value for the specified header. -func (h ObjectHeaders) Get(key string) string { - return h[textproto.CanonicalMIMEHeaderKey(key)] +//To read and write arbitary headers, use the methods on the Headers supertype. +type ObjectHeaders struct { + Headers } -//Set sets a new value for the specified header. Any existing value will be -//overwritten. -func (h ObjectHeaders) Set(key, value string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = value +//NewObjectHeaders creates a new ObjectHeaders instance. The return value +//will have the Headers attribute initialized to a non-nil map. +func NewObjectHeaders() ObjectHeaders { + return ObjectHeaders{make(Headers)} } //Validate returns MalformedHeaderError if the value of any well-known header @@ -360,57 +296,57 @@ func (h ObjectHeaders) Validate() error { //ContentDisposition provides type-safe access to Content-Disposition headers. func (h ObjectHeaders) ContentDisposition() FieldString { - return FieldString{h, "Content-Disposition"} + return FieldString{h.Headers, "Content-Disposition"} } //ContentEncoding provides type-safe access to Content-Encoding headers. func (h ObjectHeaders) ContentEncoding() FieldString { - return FieldString{h, "Content-Encoding"} + return FieldString{h.Headers, "Content-Encoding"} } //SizeBytes provides type-safe access to Content-Length headers. func (h ObjectHeaders) SizeBytes() FieldUint64 { - return FieldUint64{h, "Content-Length"} + return FieldUint64{h.Headers, "Content-Length"} } //ContentType provides type-safe access to Content-Type headers. func (h ObjectHeaders) ContentType() FieldString { - return FieldString{h, "Content-Type"} + return FieldString{h.Headers, "Content-Type"} } //Etag provides type-safe access to Etag headers. func (h ObjectHeaders) Etag() FieldString { - return FieldString{h, "Etag"} + return FieldString{h.Headers, "Etag"} } //UpdatedAt provides type-safe access to Last-Modified headers. func (h ObjectHeaders) UpdatedAt() FieldHTTPTimeReadonly { - return FieldHTTPTimeReadonly{h, "Last-Modified"} + return FieldHTTPTimeReadonly{h.Headers, "Last-Modified"} } //ExpiresAt provides type-safe access to X-Delete-At headers. func (h ObjectHeaders) ExpiresAt() FieldUnixTime { - return FieldUnixTime{h, "X-Delete-At"} + return FieldUnixTime{h.Headers, "X-Delete-At"} } //Metadata provides type-safe access to X-Object-Meta- headers. func (h ObjectHeaders) Metadata() FieldMetadata { - return FieldMetadata{h, "X-Object-Meta-"} + return FieldMetadata{h.Headers, "X-Object-Meta-"} } //SymlinkTargetAccount provides type-safe access to X-Symlink-Target-Account headers. func (h ObjectHeaders) SymlinkTargetAccount() FieldString { - return FieldString{h, "X-Symlink-Target-Account"} + return FieldString{h.Headers, "X-Symlink-Target-Account"} } //SymlinkTarget provides type-safe access to X-Symlink-Target headers. func (h ObjectHeaders) SymlinkTarget() FieldString { - return FieldString{h, "X-Symlink-Target"} + return FieldString{h.Headers, "X-Symlink-Target"} } //CreatedAt provides type-safe access to X-Timestamp headers. func (h ObjectHeaders) CreatedAt() FieldUnixTimeReadonly { - return FieldUnixTimeReadonly{h, "X-Timestamp"} + return FieldUnixTimeReadonly{h.Headers, "X-Timestamp"} } func evadeGolintComplaint1() error { diff --git a/generated.go.in b/generated.go.in index 85c257a..2858bcb 100644 --- a/generated.go.in +++ b/generated.go.in @@ -57,54 +57,20 @@ package schwift -import "net/textproto" - {{- range $htype, $hmeta := . }} //{{$htype}}Headers contains the headers for a schwift.{{$htype}} instance. // //To read and write well-known headers, use the methods on this type. -//To read and write arbitary headers, use the http.Header-like methods Get(), -//Set(), Clear(), Del(). -type {{$htype}}Headers map[string]string - -//Clear sets the value for the specified header to the empty string. When the -//Headers instance is then sent to the server with Update(), the server will -//delete the value for that header; cf. Del(). -func (h {{$htype}}Headers) Clear(key string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = "" -} -{{/* */}} -{{- if eq $htype "Object" }} -//Del deletes a key from the Headers instance. When the Headers instance -//is then sent to the server with Update(), Del() has different effects -//depending on context because of Swift's inconsistent API: -// -//For most writable attributes, a key which has been deleted with Del() will -//remain unchanged on the server. To remove the key on the server, use Clear() -//instead. -// -//For object metadata (but not other object attributes), deleting a key will -//cause that key to be deleted on the server. Del() is identical to Clear() in -//this case. -{{- else }} -//Del deletes a key from the Headers instance. When the Headers instance is -//then sent to the server with Update(), a key which has been deleted with -//Del() will remain unchanged on the server. -{{- end }} -func (h {{$htype}}Headers) Del(key string) { - delete(h, textproto.CanonicalMIMEHeaderKey(key)) -} - -//Get returns the value for the specified header. -func (h {{$htype}}Headers) Get(key string) string { - return h[textproto.CanonicalMIMEHeaderKey(key)] +//To read and write arbitary headers, use the methods on the Headers supertype. +type {{$htype}}Headers struct { + Headers } -//Set sets a new value for the specified header. Any existing value will be -//overwritten. -func (h {{$htype}}Headers) Set(key, value string) { - h[textproto.CanonicalMIMEHeaderKey(key)] = value +//New{{$htype}}Headers creates a new {{$htype}}Headers instance. The return value +//will have the Headers attribute initialized to a non-nil map. +func New{{$htype}}Headers() {{$htype}}Headers { + return {{$htype}}Headers{make(Headers)} } //Validate returns MalformedHeaderError if the value of any well-known header @@ -125,7 +91,7 @@ func (h {{$htype}}Headers) Validate() error { //{{$field.Attribute}} provides type-safe access to {{$field.Header}} headers. func (h {{$htype}}Headers) {{$field.Attribute}}() Field{{$field.Type}} { - return Field{{$field.Type}}{h, "{{$field.Header}}"} + return Field{{$field.Type}}{h.Headers, "{{$field.Header}}"} } {{- end }} {{- end }} @@ -23,7 +23,56 @@ import ( "net/textproto" ) -func headersToHTTP(h map[string]string) http.Header { +//Headers represents a set of request headers or response headers. +// +//Users will typically use one of the subtypes (AccountHeaders, +//ContainerHeaders, ObjectHeaders) instead, which provide type-safe access to +//well-known headers. The http.Header-like interface on this type can be used +//read and write arbitary headers. For example, the following calls are +//equivalent: +// +// h := make(AccountHeaders) +// h.Headers.Set("X-Account-Meta-Quota-Bytes", "1048576") +// h.BytesUsedQuota().Set(1048576) +// +type Headers map[string]string + +//Clear sets the value for the specified header to the empty string. When the +//Headers instance is then sent to the server with Update(), the server will +//delete the value for that header; cf. Del(). +func (h Headers) Clear(key string) { + h[textproto.CanonicalMIMEHeaderKey(key)] = "" +} + +//Del deletes a key from the Headers instance. When the Headers instance is +//then sent to the server with Update(), a key which has been deleted with +//Del() will remain unchanged on the server. +// +//For most writable attributes, a key which has been deleted with Del() will +//remain unchanged on the server. To remove the key on the server, use Clear() +//instead. +// +//For object metadata (but not other object attributes), deleting a key will +//cause that key to be deleted on the server. Del() is identical to Clear() in +//this case. +func (h Headers) Del(key string) { + delete(h, textproto.CanonicalMIMEHeaderKey(key)) +} + +//Get returns the value for the specified header. +func (h Headers) Get(key string) string { + return h[textproto.CanonicalMIMEHeaderKey(key)] +} + +//Set sets a new value for the specified header. Any existing value will be +//overwritten. +func (h Headers) Set(key, value string) { + h[textproto.CanonicalMIMEHeaderKey(key)] = value +} + +//ToHTTP converts this Headers instance into the equivalent http.Header +//instance. The return value is guaranteed to be non-nil. +func (h Headers) ToHTTP() http.Header { dest := make(http.Header, len(h)) for k, v := range h { dest.Set(k, v) @@ -31,19 +80,24 @@ func headersToHTTP(h map[string]string) http.Header { return dest } -func headersFromHTTP(src http.Header) map[string]string { - h := make(map[string]string, len(src)) +//ToOpts wraps this Headers instance into a RequestOpts instance, so that it +//can be passed to Schwift's various request methods. +// +// hdr := NewObjectHeaders() +// hdr.ContentType().Set("image/png") +// hdr.Metadata().Set("color", "blue") +// obj.Upload(content, hdr.ToOpts()) +// +func (h Headers) ToOpts() *RequestOptions { + return &RequestOptions{Headers: h} +} + +func headersFromHTTP(src http.Header) Headers { + h := make(Headers, len(src)) for k, v := range src { if len(v) > 0 { - h[textproto.CanonicalMIMEHeaderKey(k)] = v[0] + h.Set(k, v[0]) } } return h } - -type headerInterface interface { - Clear(string) - Del(string) - Get(string) string - Set(string, string) -} diff --git a/iterator.go b/iterator.go index e460edd..04fb492 100644 --- a/iterator.go +++ b/iterator.go @@ -31,21 +31,19 @@ type iteratorInterface interface { getAccount() *Account getContainerName() string getPrefix() string - getHeaders() map[string]string getOptions() *RequestOptions //putHeader initializes the AccountHeaders/ContainerHeaders field of the //Account/Container using the response headers from the GET request. putHeader(http.Header) error } -func (i ContainerIterator) getAccount() *Account { return i.Account } -func (i ContainerIterator) getContainerName() string { return "" } -func (i ContainerIterator) getPrefix() string { return i.Prefix } -func (i ContainerIterator) getHeaders() map[string]string { return i.Headers } -func (i ContainerIterator) getOptions() *RequestOptions { return i.Options } +func (i ContainerIterator) getAccount() *Account { return i.Account } +func (i ContainerIterator) getContainerName() string { return "" } +func (i ContainerIterator) getPrefix() string { return i.Prefix } +func (i ContainerIterator) getOptions() *RequestOptions { return i.Options } func (i ContainerIterator) putHeader(hdr http.Header) error { - headers := AccountHeaders(headersFromHTTP(hdr)) + headers := AccountHeaders{headersFromHTTP(hdr)} if err := headers.Validate(); err != nil { return err } @@ -53,14 +51,13 @@ func (i ContainerIterator) putHeader(hdr http.Header) error { return nil } -func (i ObjectIterator) getAccount() *Account { return i.Container.Account() } -func (i ObjectIterator) getContainerName() string { return i.Container.Name() } -func (i ObjectIterator) getPrefix() string { return i.Prefix } -func (i ObjectIterator) getHeaders() map[string]string { return i.Headers } -func (i ObjectIterator) getOptions() *RequestOptions { return i.Options } +func (i ObjectIterator) getAccount() *Account { return i.Container.Account() } +func (i ObjectIterator) getContainerName() string { return i.Container.Name() } +func (i ObjectIterator) getPrefix() string { return i.Prefix } +func (i ObjectIterator) getOptions() *RequestOptions { return i.Options } func (i ObjectIterator) putHeader(hdr http.Header) error { - headers := ContainerHeaders(headersFromHTTP(hdr)) + headers := ContainerHeaders{headersFromHTTP(hdr)} if err := headers.Validate(); err != nil { return err } @@ -79,8 +76,7 @@ func (b *iteratorBase) request(limit int, detailed bool) Request { r := Request{ Method: "GET", ContainerName: b.i.getContainerName(), - Headers: headersToHTTP(b.i.getHeaders()), - Options: cloneRequestOptions(b.i.getOptions()), + Options: cloneRequestOptions(b.i.getOptions(), nil), } if prefix := b.i.getPrefix(); prefix != "" { @@ -100,11 +96,11 @@ func (b *iteratorBase) request(limit int, detailed bool) Request { } if detailed { - r.Headers.Set("Accept", "application/json") + r.Options.Headers.Set("Accept", "application/json") r.Options.Values.Set("format", "json") r.ExpectStatusCodes = []int{200} } else { - r.Headers.Set("Accept", "text/plain") + r.Options.Headers.Set("Accept", "text/plain") r.Options.Values.Set("format", "plain") r.ExpectStatusCodes = []int{200, 204} } @@ -104,7 +104,7 @@ func (o *Object) Headers() (ObjectHeaders, error) { return ObjectHeaders{}, err } - headers := ObjectHeaders(headersFromHTTP(resp.Header)) + headers := ObjectHeaders{headersFromHTTP(resp.Header)} err = headers.Validate() if err != nil { return headers, err @@ -124,8 +124,7 @@ func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error { Method: "POST", ContainerName: o.c.name, ObjectName: o.name, - Headers: headersToHTTP(headers), - Options: opts, + Options: cloneRequestOptions(opts, headers.Headers), ExpectStatusCodes: []int{202}, }.Do(o.c.a.backend) if err == nil { @@ -134,18 +133,17 @@ func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error { return err } -//Upload creates the object using a PUT request. To add URL parameters, pass -//a non-nil *RequestOptions. +//Upload creates the object using a PUT request. // //If you do not have an io.Reader, but you have a []byte or string instance //containing the object, wrap it in a *bytes.Reader instance like so: // // var buffer []byte -// o.Upload(bytes.NewReader(buffer), headers, opts) +// o.Upload(bytes.NewReader(buffer), opts) // // //or... // var buffer string -// o.Upload(bytes.NewReader([]byte(buffer)), headers, opts) +// o.Upload(bytes.NewReader([]byte(buffer)), opts) // //If you have neither an io.Reader nor a []byte or string, but you have a //function that generates the object's content into an io.Writer, use @@ -166,16 +164,15 @@ func (o *Object) Update(headers ObjectHeaders, opts *RequestOptions) error { //This function can be used regardless of whether the object exists or not. // //A successful PUT request implies Invalidate() since it may change metadata. -func (o *Object) Upload(content io.Reader, headers ObjectHeaders, opts *RequestOptions) error { - if headers == nil { - headers = make(ObjectHeaders) - } - tryComputeContentLength(content, headers) - tryComputeEtag(content, headers) +func (o *Object) Upload(content io.Reader, opts *RequestOptions) error { + opts = cloneRequestOptions(opts, nil) + hdr := ObjectHeaders{opts.Headers} + tryComputeContentLength(content, hdr) + tryComputeEtag(content, hdr) //could not compute Etag in advance -> need to check on the fly var hasher hash.Hash - if !headers.Etag().Exists() { + if !hdr.Etag().Exists() { hasher = md5.New() if content != nil { content = io.TeeReader(content, hasher) @@ -186,7 +183,6 @@ func (o *Object) Upload(content io.Reader, headers ObjectHeaders, opts *RequestO Method: "PUT", ContainerName: o.c.name, ObjectName: o.name, - Headers: headersToHTTP(headers), Options: opts, Body: content, ExpectStatusCodes: []int{201}, @@ -253,7 +249,7 @@ func tryComputeEtag(content io.Reader, headers ObjectHeaders) { // } // // obj := container.Object("greeting-for-susan-and-jeffrey") -// err := obj.UploadWithWriter(nil, nil, func(w io.Writer) error { +// err := obj.UploadWithWriter(nil, func(w io.Writer) error { // err := greeting(w, "Susan") // if err == nil { // err = greeting(w, "Jeffrey") @@ -262,11 +258,11 @@ func tryComputeEtag(content io.Reader, headers ObjectHeaders) { // }) // //If you do not need an io.Writer, always use Upload instead. -func (o *Object) UploadWithWriter(headers ObjectHeaders, opts *RequestOptions, callback func(io.Writer) error) error { +func (o *Object) UploadWithWriter(opts *RequestOptions, callback func(io.Writer) error) error { reader, writer := io.Pipe() errChan := make(chan error) go func() { - err := o.Upload(reader, headers, opts) + err := o.Upload(reader, opts) reader.CloseWithError(err) //stop the writer if it is still writing errChan <- err }() @@ -280,12 +276,11 @@ func (o *Object) UploadWithWriter(headers ObjectHeaders, opts *RequestOptions, c //This operation fails with http.StatusNotFound if the object does not exist. // //A successful DELETE request implies Invalidate(). -func (o *Object) Delete(headers ObjectHeaders, opts *RequestOptions) error { +func (o *Object) Delete(opts *RequestOptions) error { _, err := Request{ Method: "DELETE", ContainerName: o.c.name, ObjectName: o.name, - Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{204}, }.Do(o.c.a.backend) @@ -313,18 +308,17 @@ func (o *Object) Invalidate() { // str, err := object.Download(nil, nil).AsString() // //See struct DownloadedObject for details. -func (o *Object) Download(headers ObjectHeaders, opts *RequestOptions) DownloadedObject { +func (o *Object) Download(opts *RequestOptions) DownloadedObject { resp, err := Request{ Method: "GET", ContainerName: o.c.name, ObjectName: o.name, - Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{200}, }.Do(o.c.a.backend) var body io.ReadCloser if err == nil { - newHeaders := ObjectHeaders(headersFromHTTP(resp.Header)) + newHeaders := ObjectHeaders{headersFromHTTP(resp.Header)} err = newHeaders.Validate() if err == nil { o.headers = &newHeaders @@ -347,18 +341,17 @@ func (o *Object) Download(headers ObjectHeaders, opts *RequestOptions) Downloade // //A successful COPY implies target.Invalidate() since it may change the //target's metadata. -func (o *Object) CopyTo(target *Object, headers ObjectHeaders, opts *RequestOptions) error { - hdr := headersToHTTP(headers) - hdr.Set("Destination", target.FullName()) +func (o *Object) CopyTo(target *Object, opts *RequestOptions) error { + opts = cloneRequestOptions(opts, nil) + opts.Headers.Set("Destination", target.FullName()) if o.c.a.name != target.c.a.name { - hdr.Set("Destination-Account", target.c.a.name) + opts.Headers.Set("Destination-Account", target.c.a.name) } _, err := Request{ Method: "COPY", ContainerName: o.c.name, ObjectName: o.name, - Headers: hdr, Options: opts, ExpectStatusCodes: []int{201}, DrainResponseBody: true, @@ -373,10 +366,10 @@ func (o *Object) CopyTo(target *Object, headers ObjectHeaders, opts *RequestOpti //a DELETE request on the source object. // //A successful move implies Invalidate() on both the source and target side. -func (o *Object) MoveTo(target *Object, headers ObjectHeaders, opts *RequestOptions) error { - err := o.CopyTo(target, headers, opts) +func (o *Object) MoveTo(target *Object, copyOpts *RequestOptions, deleteOpts *RequestOptions) error { + err := o.CopyTo(target, copyOpts) if err != nil { return err } - return o.Delete(nil, nil) + return o.Delete(deleteOpts) } diff --git a/object_iterator.go b/object_iterator.go index 6d93ccf..3df03dd 100644 --- a/object_iterator.go +++ b/object_iterator.go @@ -65,12 +65,10 @@ type ObjectIterator struct { //When Prefix is set, only objects whose name starts with this string are //returned. Prefix string - //Headers may contain additional headers to include with the GET request. - Headers map[string]string - //Options may contain additional query parameters for the GET request. + //Options may contain additional headers and query parameters for the GET request. Options *RequestOptions - //TODO: Delimter field (and check if other stuff is missing) + //TODO: Delimiter field (and check if other stuff is missing) base *iteratorBase } @@ -26,20 +26,39 @@ import ( "strings" ) -//RequestOptions contains additional headers and values for a request. +//RequestOptions is used to pass additional headers and values to aa request. +// +//When preparing a RequestOptions instance with additional headers, the +//preferred way is to create an AccountHeaders, ContainerHeaders and +//ObjectHeaders instance and use the type-safe API on these types. Then use the +//ToOpts() method on that instance. For example: +// +// hdr := NewObjectHeaders() +// hdr.ContentType().Set("image/png") +// hdr.Metadata().Set("color", "blue") +// opts := hdr.ToOpts() //type *schwift.RequestOptions +// type RequestOptions struct { - Values url.Values + Headers Headers + Values url.Values } -func cloneRequestOptions(orig *RequestOptions) *RequestOptions { +func cloneRequestOptions(orig *RequestOptions, additional Headers) *RequestOptions { result := RequestOptions{ - Values: make(url.Values), + Headers: make(Headers), + Values: make(url.Values), } if orig != nil { + for k, v := range orig.Headers { + result.Headers[k] = v + } for k, v := range orig.Values { result.Values[k] = v } } + for k, v := range additional { + result.Headers[k] = v + } return &result } @@ -48,7 +67,6 @@ type Request struct { Method string //"GET", "HEAD", "PUT", "POST" or "DELETE" ContainerName string //empty for requests on accounts ObjectName string //empty for requests on accounts/containers - Headers http.Header Options *RequestOptions Body io.Reader //ExpectStatusCodes can be left empty to disable this check, otherwise @@ -102,8 +120,10 @@ func (r Request) Do(backend Backend) (*http.Response, error) { return nil, err } - for k, v := range r.Headers { - req.Header[k] = v + if r.Options != nil { + for k, v := range r.Options.Headers { + req.Header[k] = []string{v} + } } if r.Body != nil { req.Header.Set("Expect", "100-continue") diff --git a/tests/account_test.go b/tests/account_test.go index 310d4ce..2885bfa 100644 --- a/tests/account_test.go +++ b/tests/account_test.go @@ -41,7 +41,7 @@ func TestAccountBasic(t *testing.T) { func TestAccountMetadata(t *testing.T) { testWithAccount(t, func(a *schwift.Account) { //test creating some metadata - hdr := make(schwift.AccountHeaders) + hdr := schwift.NewAccountHeaders() hdr.Metadata().Set("schwift-test1", "first") hdr.Metadata().Set("schwift-test2", "second") err := a.Update(hdr, nil) @@ -57,7 +57,7 @@ func TestAccountMetadata(t *testing.T) { expectString(t, hdr.Metadata().Get("schwift-test2"), "second") //test deleting some metadata - hdr = make(schwift.AccountHeaders) + hdr = schwift.NewAccountHeaders() hdr.Metadata().Clear("schwift-test1") err = a.Update(hdr, nil) if !expectSuccess(t, err) { @@ -72,7 +72,7 @@ func TestAccountMetadata(t *testing.T) { expectString(t, hdr.Metadata().Get("schwift-test2"), "second") //test updating some metadata - hdr = make(schwift.AccountHeaders) + hdr = schwift.NewAccountHeaders() hdr.Metadata().Set("schwift-test1", "will not be set") hdr.Metadata().Del("schwift-test1") hdr.Metadata().Set("schwift-test2", "changed") diff --git a/tests/bulk_delete_test.go b/tests/bulk_delete_test.go index 52294ba..c498419 100644 --- a/tests/bulk_delete_test.go +++ b/tests/bulk_delete_test.go @@ -33,13 +33,13 @@ func TestBulkDeleteSuccess(t *testing.T) { objs, err := createTestObjects(c) expectSuccess(t, err) - numDeleted, numNotFound, err := c.Account().BulkDelete(objs, nil, nil, nil) + numDeleted, numNotFound, err := c.Account().BulkDelete(objs, nil, nil) expectSuccess(t, err) expectInt(t, numDeleted, len(objs)) expectInt(t, numNotFound, 0) expectContainerExistence(t, c, true) - numDeleted, numNotFound, err = c.Account().BulkDelete(objs, nil, nil, nil) + numDeleted, numNotFound, err = c.Account().BulkDelete(objs, nil, nil) expectSuccess(t, err) expectInt(t, numDeleted, 0) expectInt(t, numNotFound, len(objs)) @@ -49,7 +49,7 @@ func TestBulkDeleteSuccess(t *testing.T) { expectSuccess(t, err) cs := []*schwift.Container{c} - numDeleted, numNotFound, err = c.Account().BulkDelete(objs, cs, nil, nil) + numDeleted, numNotFound, err = c.Account().BulkDelete(objs, cs, nil) expectSuccess(t, err) expectInt(t, numDeleted, len(objs)+1) expectInt(t, numNotFound, 0) @@ -65,7 +65,7 @@ func TestBulkDeleteError(t *testing.T) { cs := []*schwift.Container{c} //not deleting all objects should lead to 409 Conflict when deleting the Container - numDeleted, numNotFound, err := c.Account().BulkDelete(objs, cs, nil, nil) + numDeleted, numNotFound, err := c.Account().BulkDelete(objs, cs, nil) expectInt(t, numDeleted, len(objs)) expectInt(t, numNotFound, 0) expectError(t, err, "400 Bad Request (+1 object errors)") @@ -77,7 +77,7 @@ func createTestObjects(c *schwift.Container) ([]*schwift.Object, error) { var objs []*schwift.Object for idx := 1; idx <= 5; idx++ { obj := c.Object(fmt.Sprintf("object%d", idx)) - err := obj.Upload(strings.NewReader("example"), nil, nil) + err := obj.Upload(strings.NewReader("example"), nil) if err != nil { return nil, err } diff --git a/tests/bulk_upload_test.go b/tests/bulk_upload_test.go index 68cb791..6cbae42 100644 --- a/tests/bulk_upload_test.go +++ b/tests/bulk_upload_test.go @@ -40,7 +40,7 @@ func TestBulkUploadSuccess(t *testing.T) { "", //upload path schwift.BulkUploadTar, bytes.NewReader(archive), - nil, nil, + nil, ) expectInt(t, n, 2) expectSuccess(t, err) @@ -58,7 +58,7 @@ func TestBulkUploadArchiveError(t *testing.T) { c.Name(), //upload path schwift.BulkUploadTar, strings.NewReader("This is not the TAR archive you're looking for."), - nil, nil, + nil, ) expectInt(t, n, 0) expectError(t, err, "400 Bad Request: Invalid Tar File: truncated header") @@ -83,7 +83,7 @@ func TestBulkUploadObjectError(t *testing.T) { c.Name(), //upload path schwift.BulkUploadTar, bytes.NewReader(archive), - nil, nil, + nil, ) expectInt(t, n, 1) expectError(t, err, "400 Bad Request (+1 object errors)") diff --git a/tests/container_iterator_test.go b/tests/container_iterator_test.go index b9813db..d682a59 100644 --- a/tests/container_iterator_test.go +++ b/tests/container_iterator_test.go @@ -126,7 +126,7 @@ func TestContainerIterator(t *testing.T) { iter = a.Containers() iter.Prefix = "schwift-test-listing" expectSuccess(t, iter.Foreach(func(c *schwift.Container) error { - return c.Delete(nil, nil) + return c.Delete(nil) })) }) } diff --git a/tests/container_test.go b/tests/container_test.go index c90a339..0efc4e6 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -46,17 +46,17 @@ func TestContainerLifecycle(t *testing.T) { //DELETE should be idempotent and not return success on non-existence, but //OpenStack LOVES to be inconsistent with everything (including, notably, itself) - err = c.Delete(nil, nil) + err = c.Delete(nil) expectError(t, err, "expected 204 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>") - err = c.Create(nil, nil) + err = c.Create(nil) expectSuccess(t, err) exists, err = c.Exists() expectSuccess(t, err) expectBool(t, exists, true) - err = c.Delete(nil, nil) + err = c.Delete(nil) expectSuccess(t, err) }) } @@ -69,7 +69,7 @@ func TestContainerUpdate(t *testing.T) { expectBool(t, hdr.ObjectCount().Exists(), true) expectUint64(t, hdr.ObjectCount().Get(), 0) - hdr = make(schwift.ContainerHeaders) + hdr = schwift.NewContainerHeaders() hdr.ObjectCountQuota().Set(23) hdr.BytesUsedQuota().Set(42) diff --git a/tests/field_test.go b/tests/field_test.go index f3c03ca..8166f2d 100644 --- a/tests/field_test.go +++ b/tests/field_test.go @@ -27,33 +27,33 @@ import ( ) func TestFieldString(t *testing.T) { - hdr := make(schwift.AccountHeaders) + hdr := schwift.NewAccountHeaders() expectBool(t, hdr.TempURLKey().Exists(), false) expectString(t, hdr.TempURLKey().Get(), "") expectSuccess(t, hdr.Validate()) - hdr["X-Account-Meta-Temp-Url-Key"] = "" + hdr.Headers["X-Account-Meta-Temp-Url-Key"] = "" expectBool(t, hdr.TempURLKey().Exists(), false) expectString(t, hdr.TempURLKey().Get(), "") expectSuccess(t, hdr.Validate()) - hdr["X-Account-Meta-Temp-Url-Key"] = "foo" + hdr.Headers["X-Account-Meta-Temp-Url-Key"] = "foo" expectBool(t, hdr.TempURLKey().Exists(), true) expectString(t, hdr.TempURLKey().Get(), "foo") expectSuccess(t, hdr.Validate()) hdr.TempURLKey().Set("bar") - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Temp-Url-Key": "bar", }) hdr.TempURLKey().Clear() - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Temp-Url-Key": "", }) hdr.TempURLKey().Del() - expectHeaders(t, hdr, nil) + expectHeaders(t, hdr.Headers, nil) hdr.TempURLKey().Clear() - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Temp-Url-Key": "", }) } @@ -70,16 +70,16 @@ func TestFieldTimestamp(t *testing.T) { expectBool(t, hdr.CreatedAt().Exists(), true) actual := float64(hdr.CreatedAt().Get().UnixNano()) / 1e9 - expected, _ := strconv.ParseFloat(hdr["X-Timestamp"], 64) + expected, _ := strconv.ParseFloat(hdr.Headers["X-Timestamp"], 64) expectFloat64(t, actual, expected) }) - hdr := make(schwift.AccountHeaders) + hdr := schwift.NewAccountHeaders() expectBool(t, hdr.CreatedAt().Exists(), false) expectBool(t, hdr.CreatedAt().Get().IsZero(), true) expectSuccess(t, hdr.Validate()) - hdr["X-Timestamp"] = "wtf" + hdr.Headers["X-Timestamp"] = "wtf" expectBool(t, hdr.CreatedAt().Exists(), true) expectBool(t, hdr.CreatedAt().Get().IsZero(), true) expectError(t, hdr.Validate(), `Bad header X-Timestamp: strconv.ParseFloat: parsing "wtf": invalid syntax`) @@ -88,7 +88,7 @@ func TestFieldTimestamp(t *testing.T) { func TestFieldHTTPTimestamp(t *testing.T) { testWithContainer(t, func(c *schwift.Container) { obj := c.Object("test") - err := obj.Upload(nil, nil, nil) + err := obj.Upload(nil, nil) if !expectSuccess(t, err) { return } @@ -104,12 +104,12 @@ func TestFieldHTTPTimestamp(t *testing.T) { expectInt64(t, actual.Unix(), expected.Unix()) }) - hdr := make(schwift.ObjectHeaders) + hdr := schwift.NewObjectHeaders() expectBool(t, hdr.UpdatedAt().Exists(), false) expectBool(t, hdr.UpdatedAt().Get().IsZero(), true) expectSuccess(t, hdr.Validate()) - hdr["Last-Modified"] = "wtf" + hdr.Headers["Last-Modified"] = "wtf" expectBool(t, hdr.UpdatedAt().Exists(), true) expectBool(t, hdr.UpdatedAt().Get().IsZero(), true) expectError(t, hdr.Validate(), `Bad header Last-Modified: parsing time "wtf" as "Mon Jan _2 15:04:05 2006": cannot parse "wtf" as "Mon"`) @@ -118,49 +118,49 @@ func TestFieldHTTPTimestamp(t *testing.T) { //////////////////////////////////////////////////////////////////////////////// func TestFieldUint64(t *testing.T) { - hdr := make(schwift.AccountHeaders) + hdr := schwift.NewAccountHeaders() expectBool(t, hdr.BytesUsedQuota().Exists(), false) expectUint64(t, hdr.BytesUsedQuota().Get(), 0) expectSuccess(t, hdr.Validate()) - hdr["X-Account-Meta-Quota-Bytes"] = "23" + hdr.Headers["X-Account-Meta-Quota-Bytes"] = "23" expectBool(t, hdr.BytesUsedQuota().Exists(), true) expectUint64(t, hdr.BytesUsedQuota().Get(), 23) expectSuccess(t, hdr.Validate()) - hdr["X-Account-Meta-Quota-Bytes"] = "-23" + hdr.Headers["X-Account-Meta-Quota-Bytes"] = "-23" expectBool(t, hdr.BytesUsedQuota().Exists(), true) expectUint64(t, hdr.BytesUsedQuota().Get(), 0) expectError(t, hdr.Validate(), `Bad header X-Account-Meta-Quota-Bytes: strconv.ParseUint: parsing "-23": invalid syntax`) hdr.BytesUsedQuota().Set(9001) - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Quota-Bytes": "9001", }) hdr.BytesUsedQuota().Clear() - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Quota-Bytes": "", }) hdr.BytesUsedQuota().Del() - expectHeaders(t, hdr, nil) + expectHeaders(t, hdr.Headers, nil) hdr.BytesUsedQuota().Clear() - expectHeaders(t, hdr, map[string]string{ + expectHeaders(t, hdr.Headers, map[string]string{ "X-Account-Meta-Quota-Bytes": "", }) } func TestFieldUint64Readonly(t *testing.T) { - hdr := make(schwift.AccountHeaders) + hdr := schwift.NewAccountHeaders() expectBool(t, hdr.BytesUsed().Exists(), false) expectUint64(t, hdr.BytesUsed().Get(), 0) expectSuccess(t, hdr.Validate()) - hdr["X-Account-Bytes-Used"] = "23" + hdr.Headers["X-Account-Bytes-Used"] = "23" expectBool(t, hdr.BytesUsed().Exists(), true) expectUint64(t, hdr.BytesUsed().Get(), 23) expectSuccess(t, hdr.Validate()) - hdr["X-Account-Bytes-Used"] = "-23" + hdr.Headers["X-Account-Bytes-Used"] = "-23" expectBool(t, hdr.BytesUsed().Exists(), true) expectUint64(t, hdr.BytesUsed().Get(), 0) expectError(t, hdr.Validate(), `Bad header X-Account-Bytes-Used: strconv.ParseUint: parsing "-23": invalid syntax`) diff --git a/tests/headers_test.go b/tests/headers_test.go index 8067ef4..6b229ae 100644 --- a/tests/headers_test.go +++ b/tests/headers_test.go @@ -26,11 +26,13 @@ import ( func TestParseAccountHeadersSuccess(t *testing.T) { headers := schwift.AccountHeaders{ - "X-Account-Bytes-Used": "1234", - "X-Account-Object-Count": "42", - "X-Account-Container-Count": "23", - "X-Account-Meta-Quota-Bytes": "1048576", - "X-Account-Meta-Foo": "bar", + Headers: schwift.Headers{ + "X-Account-Bytes-Used": "1234", + "X-Account-Object-Count": "42", + "X-Account-Container-Count": "23", + "X-Account-Meta-Quota-Bytes": "1048576", + "X-Account-Meta-Foo": "bar", + }, } expectSuccess(t, headers.Validate()) diff --git a/tests/object_iterator_test.go b/tests/object_iterator_test.go index 5112324..009189d 100644 --- a/tests/object_iterator_test.go +++ b/tests/object_iterator_test.go @@ -37,9 +37,9 @@ func TestObjectIterator(t *testing.T) { //create test objects that can be listed for idx := 1; idx <= 4; idx++ { - hdr := make(schwift.ObjectHeaders) + hdr := schwift.NewObjectHeaders() hdr.ContentType().Set("application/json") - err := c.Object(oname(idx)).Upload(bytes.NewReader(objectExampleContent), hdr, nil) + err := c.Object(oname(idx)).Upload(bytes.NewReader(objectExampleContent), hdr.ToOpts()) expectSuccess(t, err) } diff --git a/tests/object_test.go b/tests/object_test.go index 090fc48..b42294a 100644 --- a/tests/object_test.go +++ b/tests/object_test.go @@ -47,15 +47,15 @@ func TestObjectLifecycle(t *testing.T) { //DELETE should be idempotent and not return success on non-existence, but //OpenStack LOVES to be inconsistent with everything (including, notably, itself) - err = o.Delete(nil, nil) + err = o.Delete(nil) expectError(t, err, "expected 204 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>") - err = o.Upload(bytes.NewReader([]byte("test")), nil, nil) + err = o.Upload(bytes.NewReader([]byte("test")), nil) expectSuccess(t, err) expectObjectExistence(t, o, true) - err = o.Delete(nil, nil) + err = o.Delete(nil) expectSuccess(t, err) }) } @@ -65,25 +65,25 @@ func TestObjectUpload(t *testing.T) { //test upload with bytes.Reader obj := c.Object("upload1") - err := obj.Upload(bytes.NewReader(objectExampleContent), nil, nil) + err := obj.Upload(bytes.NewReader(objectExampleContent), nil) expectSuccess(t, err) expectObjectContent(t, obj, objectExampleContent) //test upload with bytes.Buffer obj = c.Object("upload2") - err = obj.Upload(bytes.NewBuffer(objectExampleContent), nil, nil) + err = obj.Upload(bytes.NewBuffer(objectExampleContent), nil) expectSuccess(t, err) expectObjectContent(t, obj, objectExampleContent) //test upload with opaque io.Reader obj = c.Object("upload3") - err = obj.Upload(opaqueReader{bytes.NewReader(objectExampleContent)}, nil, nil) + err = obj.Upload(opaqueReader{bytes.NewReader(objectExampleContent)}, nil) expectSuccess(t, err) expectObjectContent(t, obj, objectExampleContent) //test upload with io.Writer obj = c.Object("upload4") - err = obj.UploadWithWriter(nil, nil, func(w io.Writer) error { + err = obj.UploadWithWriter(nil, func(w io.Writer) error { _, err := w.Write(objectExampleContent) return err }) @@ -92,13 +92,13 @@ func TestObjectUpload(t *testing.T) { //test upload with empty reader (should create zero-byte-sized object) obj = c.Object("upload5") - err = obj.Upload(eofReader{}, nil, nil) + err = obj.Upload(eofReader{}, nil) expectSuccess(t, err) expectObjectContent(t, obj, nil) //test upload without reader (should create zero-byte-sized object) obj = c.Object("upload6") - err = obj.Upload(nil, nil, nil) + err = obj.Upload(nil, nil) expectSuccess(t, err) expectObjectContent(t, obj, nil) }) @@ -122,21 +122,21 @@ func TestObjectDownload(t *testing.T) { testWithContainer(t, func(c *schwift.Container) { //upload example object obj := c.Object("example") - err := obj.Upload(bytes.NewReader(objectExampleContent), nil, nil) + err := obj.Upload(bytes.NewReader(objectExampleContent), nil) expectSuccess(t, err) //test download as string - str, err := obj.Download(nil, nil).AsString() + str, err := obj.Download(nil).AsString() expectSuccess(t, err) expectString(t, str, string(objectExampleContent)) //test download as byte slice - buf, err := obj.Download(nil, nil).AsByteSlice() + buf, err := obj.Download(nil).AsByteSlice() expectSuccess(t, err) expectString(t, string(buf), string(objectExampleContent)) //test download as io.ReadCloser slice - reader, err := obj.Download(nil, nil).AsReadCloser() + reader, err := obj.Download(nil).AsReadCloser() expectSuccess(t, err) buf = make([]byte, 4) _, err = reader.Read(buf) @@ -156,14 +156,14 @@ func TestObjectUpdate(t *testing.T) { obj := c.Object("example") //test that metadata update fails for non-existing object - newHeaders := make(schwift.ObjectHeaders) + newHeaders := schwift.NewObjectHeaders() newHeaders.ContentType().Set("application/json") err := obj.Update(newHeaders, nil) expectBool(t, schwift.Is(err, http.StatusNotFound), true) expectError(t, err, "expected 202 response, got 404 instead: <html><h1>Not Found</h1><p>The resource could not be found.</p></html>") //create object - err = obj.Upload(nil, nil, nil) + err = obj.Upload(nil, nil) expectSuccess(t, err) hdr, err := obj.Headers() @@ -183,12 +183,12 @@ func TestObjectUpdate(t *testing.T) { func TestObjectCopyMove(t *testing.T) { testWithContainer(t, func(c *schwift.Container) { obj1 := c.Object("location1") - err := obj1.Upload(bytes.NewReader(objectExampleContent), nil, nil) + err := obj1.Upload(bytes.NewReader(objectExampleContent), nil) expectSuccess(t, err) expectObjectExistence(t, obj1, true) obj2 := c.Object("location2") - expectSuccess(t, obj1.CopyTo(obj2, nil, nil)) + expectSuccess(t, obj1.CopyTo(obj2, nil)) expectObjectExistence(t, obj1, true) expectObjectExistence(t, obj2, true) expectObjectContent(t, obj2, objectExampleContent) @@ -212,7 +212,7 @@ func expectObjectExistence(t *testing.T, obj *schwift.Object, expectedExists boo func expectObjectContent(t *testing.T, obj *schwift.Object, expected []byte) { t.Helper() - str, err := obj.Download(nil, nil).AsString() + str, err := obj.Download(nil).AsString() expectSuccess(t, err) expectString(t, str, string(expected)) obj.Invalidate() diff --git a/tests/shared_test.go b/tests/shared_test.go index 187d0b4..c35951f 100644 --- a/tests/shared_test.go +++ b/tests/shared_test.go @@ -99,9 +99,9 @@ func testWithContainer(t *testing.T, testCode func(c *schwift.Container)) { expectSuccess(t, err) if exists { expectSuccess(t, container.Objects().Foreach(func(o *schwift.Object) error { - return o.Delete(nil, nil) + return o.Delete(nil) })) - err = container.Delete(nil, nil) + err = container.Delete(nil) expectSuccess(t, err) } }) |
