diff options
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | account.go | 9 | ||||
| -rw-r--r-- | account_test.go | 30 | ||||
| -rw-r--r-- | container.go | 20 | ||||
| -rw-r--r-- | container_test.go | 2 | ||||
| -rw-r--r-- | generated.go | 367 | ||||
| -rw-r--r-- | generated.go.in | 124 | ||||
| -rw-r--r-- | headers.go | 171 | ||||
| -rw-r--r-- | headers/base.go | 10 | ||||
| -rw-r--r-- | headers_test.go | 19 | ||||
| -rwxr-xr-x | util/render_template.go | 63 |
11 files changed, 625 insertions, 203 deletions
@@ -1,7 +1,18 @@ -all: +help: @echo 'Available targets:' + @echo ' make generate' @echo ' make test' +################################################################################ + +generate: generated.go + +%: %.in | util/render_template.go + @echo ./util/render_template.go < $< > $@ + @./util/render_template.go < $< > $@.new && mv $@.new $@ || (rm $@.new; false) + +################################################################################ + test: static-tests cover.html static-tests: FORCE @@ -96,8 +96,7 @@ func (a *Account) Headers() (AccountHeaders, error) { return AccountHeaders{}, err } - headers := NewAccountHeaders() - headers.FromHTTP(resp.Header) + headers := AccountHeaders(headersFromHTTP(resp.Header)) err = headers.Validate() if err != nil { return headers, err @@ -117,10 +116,9 @@ func (a *Account) Invalidate() { // //A successful POST request implies Invalidate() since it may change metadata. func (a *Account) Update(headers AccountHeaders, opts *RequestOptions) error { - ensureInitializedByReflection(headers) _, err := Request{ Method: "POST", - Headers: headers.ToHTTP(), + Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{204}, }.Do(a.client) @@ -138,10 +136,9 @@ func (a *Account) Update(headers AccountHeaders, opts *RequestOptions) error { // //A successful PUT request implies Invalidate() since it may change metadata. func (a *Account) Create(headers AccountHeaders, opts *RequestOptions) error { - ensureInitializedByReflection(headers) _, err := Request{ Method: "PUT", - Headers: headers.ToHTTP(), + Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{201, 202}, }.Do(a.client) diff --git a/account_test.go b/account_test.go index 6f44e89..29fc8ea 100644 --- a/account_test.go +++ b/account_test.go @@ -39,9 +39,9 @@ func TestAccountBasic(t *testing.T) { func TestAccountMetadata(t *testing.T) { testWithAccount(t, func(a *Account) { //test creating some metadata - hdr := NewAccountHeaders() - hdr.Metadata.Set("schwift-test1", "first") - hdr.Metadata.Set("schwift-test2", "second") + hdr := make(AccountHeaders) + hdr.Metadata().Set("schwift-test1", "first") + hdr.Metadata().Set("schwift-test2", "second") err := a.Update(hdr, nil) if !expectError(t, err, "") { t.FailNow() @@ -51,12 +51,12 @@ func TestAccountMetadata(t *testing.T) { if !expectError(t, err, "") { t.FailNow() } - expectString(t, hdr.Metadata.Get("schwift-test1"), "first") - expectString(t, hdr.Metadata.Get("schwift-test2"), "second") + expectString(t, hdr.Metadata().Get("schwift-test1"), "first") + expectString(t, hdr.Metadata().Get("schwift-test2"), "second") //test deleting some metadata - hdr = NewAccountHeaders() - hdr.Metadata.Clear("schwift-test1") + hdr = make(AccountHeaders) + hdr.Metadata().Clear("schwift-test1") err = a.Update(hdr, nil) if !expectError(t, err, "") { t.FailNow() @@ -66,14 +66,14 @@ func TestAccountMetadata(t *testing.T) { if !expectError(t, err, "") { t.FailNow() } - expectString(t, hdr.Metadata.Get("schwift-test1"), "") - expectString(t, hdr.Metadata.Get("schwift-test2"), "second") + expectString(t, hdr.Metadata().Get("schwift-test1"), "") + expectString(t, hdr.Metadata().Get("schwift-test2"), "second") //test updating some metadata - hdr = NewAccountHeaders() - hdr.Metadata.Set("schwift-test1", "will not be set") - hdr.Metadata.Del("schwift-test1") - hdr.Metadata.Set("schwift-test2", "changed") + hdr = make(AccountHeaders) + hdr.Metadata().Set("schwift-test1", "will not be set") + hdr.Metadata().Del("schwift-test1") + hdr.Metadata().Set("schwift-test2", "changed") err = a.Update(hdr, nil) if !expectError(t, err, "") { t.FailNow() @@ -83,8 +83,8 @@ func TestAccountMetadata(t *testing.T) { if !expectError(t, err, "") { t.FailNow() } - expectString(t, hdr.Metadata.Get("schwift-test1"), "") - expectString(t, hdr.Metadata.Get("schwift-test2"), "changed") + expectString(t, hdr.Metadata().Get("schwift-test1"), "") + expectString(t, hdr.Metadata().Get("schwift-test2"), "changed") }) } diff --git a/container.go b/container.go index cfc0eab..accae8e 100644 --- a/container.go +++ b/container.go @@ -79,8 +79,7 @@ func (c *Container) Headers() (ContainerHeaders, error) { return ContainerHeaders{}, err } - headers := NewContainerHeaders() - headers.FromHTTP(resp.Header) + headers := ContainerHeaders(headersFromHTTP(resp.Header)) err = headers.Validate() if err != nil { return headers, err @@ -96,11 +95,10 @@ func (c *Container) Headers() (ContainerHeaders, error) { // //A successful POST request implies Invalidate() since it may change metadata. func (c *Container) Update(headers ContainerHeaders, opts *RequestOptions) error { - ensureInitializedByReflection(headers) _, err := Request{ Method: "POST", ContainerName: c.name, - Headers: headers.ToHTTP(), + Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{204}, }.Do(c.a.client) @@ -117,11 +115,10 @@ func (c *Container) Update(headers ContainerHeaders, opts *RequestOptions) error // //A successful PUT request implies Invalidate() since it may change metadata. func (c *Container) Create(headers ContainerHeaders, opts *RequestOptions) error { - ensureInitializedByReflection(headers) _, err := Request{ Method: "PUT", ContainerName: c.name, - Headers: headers.ToHTTP(), + Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{201, 202}, }.Do(c.a.client) @@ -139,18 +136,11 @@ 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 { - if headers == nil { - h := NewContainerHeaders() - headers = &h - } else { - ensureInitializedByReflection(*headers) - } - +func (c *Container) Delete(headers ContainerHeaders, opts *RequestOptions) error { _, err := Request{ Method: "DELETE", ContainerName: c.name, - Headers: headers.ToHTTP(), + Headers: headersToHTTP(headers), Options: opts, ExpectStatusCodes: []int{204}, }.Do(c.a.client) diff --git a/container_test.go b/container_test.go index bbb2deb..c6e20f1 100644 --- a/container_test.go +++ b/container_test.go @@ -38,7 +38,7 @@ func TestContainerExistence(t *testing.T) { expectBool(t, Is(err, http.StatusNotFound), true) expectBool(t, Is(err, http.StatusNoContent), false) - err = c.Create(NewContainerHeaders(), nil) + err = c.Create(nil, nil) expectError(t, err, "") exists, err = c.Exists() diff --git a/generated.go b/generated.go new file mode 100644 index 0000000..a3dca80 --- /dev/null +++ b/generated.go @@ -0,0 +1,367 @@ +/******************************************************************************* +* +* THIS FILE IS AUTOGENERATED. +* +* Edit `generated.go.in` instead and run `make generate` to produce this file. +* +*******************************************************************************/ + +package schwift + +import ( + "net/textproto" + + "github.com/majewsky/schwift/headers" +) + +//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 methods on type Headers. +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)] = "" +} + +//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. +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 +} + +//Validate returns headers.MalformedHeaderError if the value of any well-known +//header does not conform to its data type. This is called automatically by +//Schwift when preparing an AccountHeaders instance from a GET/HEAD response, +//so you usually do not need to do it yourself. You will get the validation error +//from the Account method doing the request, e.g. Headers(). +func (h AccountHeaders) Validate() error { + if err := h.BytesUsed().Validate(); err != nil { + return err + } + if err := h.ContainerCount().Validate(); err != nil { + return err + } + if err := h.QuotaBytes().Validate(); err != nil { + return err + } + if err := h.ObjectCount().Validate(); err != nil { + return err + } + if err := h.Timestamp().Validate(); err != nil { + return err + } + return evadeGolintComplaint1() +} + +//BytesUsed provides type-safe access to X-Account-Bytes-Used headers. +func (h AccountHeaders) BytesUsed() headers.Uint64Readonly { + return headers.Uint64Readonly{ + Base: headers.Base{ + H: h, + K: "X-Account-Bytes-Used", + }, + } +} + +//ContainerCount provides type-safe access to X-Account-Container-Count headers. +func (h AccountHeaders) ContainerCount() headers.Uint64Readonly { + return headers.Uint64Readonly{ + Base: headers.Base{ + H: h, + K: "X-Account-Container-Count", + }, + } +} + +//Metadata provides type-safe access to X-Account-Meta- headers. +func (h AccountHeaders) Metadata() headers.Metadata { + return headers.Metadata{ + Base: headers.Base{ + H: h, + K: "X-Account-Meta-", + }, + } +} + +//QuotaBytes provides type-safe access to X-Account-Meta-Quota-Bytes headers. +func (h AccountHeaders) QuotaBytes() headers.Uint64 { + return headers.Uint64{ + Base: headers.Base{ + H: h, + K: "X-Account-Meta-Quota-Bytes", + }, + } +} + +//TempURLKey2 provides type-safe access to X-Account-Meta-Temp-URL-Key-2 headers. +func (h AccountHeaders) TempURLKey2() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Account-Meta-Temp-URL-Key-2", + }, + } +} + +//TempURLKey provides type-safe access to X-Account-Meta-Temp-URL-Key headers. +func (h AccountHeaders) TempURLKey() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Account-Meta-Temp-URL-Key", + }, + } +} + +//ObjectCount provides type-safe access to X-Account-Object-Count headers. +func (h AccountHeaders) ObjectCount() headers.Uint64Readonly { + return headers.Uint64Readonly{ + Base: headers.Base{ + H: h, + K: "X-Account-Object-Count", + }, + } +} + +//Timestamp provides type-safe access to X-Timestamp headers. +func (h AccountHeaders) Timestamp() headers.UnixTimeReadonly { + return headers.UnixTimeReadonly{ + Base: headers.Base{ + H: h, + K: "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 methods on type Headers. +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(), 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. +func (h ContainerHeaders) Del(key string) { + delete(h, textproto.CanonicalMIMEHeaderKey(key)) +} + +//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 +} + +//Validate returns headers.MalformedHeaderError if the value of any well-known +//header does not conform to its data type. This is called automatically by +//Schwift when preparing an ContainerHeaders instance from a GET/HEAD response, +//so you usually do not need to do it yourself. You will get the validation error +//from the Container method doing the request, e.g. Headers(). +func (h ContainerHeaders) Validate() error { + if err := h.BytesUsed().Validate(); err != nil { + return err + } + if err := h.BytesUsedQuota().Validate(); err != nil { + return err + } + if err := h.ObjectCountQuota().Validate(); err != nil { + return err + } + if err := h.ObjectCount().Validate(); err != nil { + return err + } + if err := h.Timestamp().Validate(); err != nil { + return err + } + return evadeGolintComplaint1() +} + +//BytesUsed provides type-safe access to X-Container-Bytes-Used headers. +func (h ContainerHeaders) BytesUsed() headers.Uint64Readonly { + return headers.Uint64Readonly{ + Base: headers.Base{ + H: h, + K: "X-Container-Bytes-Used", + }, + } +} + +//Metadata provides type-safe access to X-Container-Meta- headers. +func (h ContainerHeaders) Metadata() headers.Metadata { + return headers.Metadata{ + Base: headers.Base{ + H: h, + K: "X-Container-Meta-", + }, + } +} + +//BytesUsedQuota provides type-safe access to X-Container-Meta-Quota-Bytes headers. +func (h ContainerHeaders) BytesUsedQuota() headers.Uint64 { + return headers.Uint64{ + Base: headers.Base{ + H: h, + K: "X-Container-Meta-Quota-Bytes", + }, + } +} + +//ObjectCountQuota provides type-safe access to X-Container-Meta-Quota-Count headers. +func (h ContainerHeaders) ObjectCountQuota() headers.Uint64 { + return headers.Uint64{ + Base: headers.Base{ + H: h, + K: "X-Container-Meta-Quota-Count", + }, + } +} + +//TempURLKey2 provides type-safe access to X-Container-Meta-Temp-URL-Key-2 headers. +func (h ContainerHeaders) TempURLKey2() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Meta-Temp-URL-Key-2", + }, + } +} + +//TempURLKey provides type-safe access to X-Container-Meta-Temp-URL-Key headers. +func (h ContainerHeaders) TempURLKey() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Meta-Temp-URL-Key", + }, + } +} + +//ObjectCount provides type-safe access to X-Container-Object-Count headers. +func (h ContainerHeaders) ObjectCount() headers.Uint64Readonly { + return headers.Uint64Readonly{ + Base: headers.Base{ + H: h, + K: "X-Container-Object-Count", + }, + } +} + +//ReadACL provides type-safe access to X-Container-Read headers. +func (h ContainerHeaders) ReadACL() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Read", + }, + } +} + +//SyncKey provides type-safe access to X-Container-Sync-Key headers. +func (h ContainerHeaders) SyncKey() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Sync-Key", + }, + } +} + +//SyncTo provides type-safe access to X-Container-Sync-To headers. +func (h ContainerHeaders) SyncTo() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Sync-To", + }, + } +} + +//WriteACL provides type-safe access to X-Container-Write headers. +func (h ContainerHeaders) WriteACL() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Container-Write", + }, + } +} + +//HistoryLocation provides type-safe access to X-History-Location headers. +func (h ContainerHeaders) HistoryLocation() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-History-Location", + }, + } +} + +//StoragePolicy provides type-safe access to X-Storage-Policy headers. +func (h ContainerHeaders) StoragePolicy() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Storage-Policy", + }, + } +} + +//Timestamp provides type-safe access to X-Timestamp headers. +func (h ContainerHeaders) Timestamp() headers.UnixTimeReadonly { + return headers.UnixTimeReadonly{ + Base: headers.Base{ + H: h, + K: "X-Timestamp", + }, + } +} + +//VersionsLocation provides type-safe access to X-Versions-Location headers. +func (h ContainerHeaders) VersionsLocation() headers.String { + return headers.String{ + Base: headers.Base{ + H: h, + K: "X-Versions-Location", + }, + } +} + +func evadeGolintComplaint1() error { + return nil +} diff --git a/generated.go.in b/generated.go.in new file mode 100644 index 0000000..034a8e4 --- /dev/null +++ b/generated.go.in @@ -0,0 +1,124 @@ +{ + "Account": { + "Fields": [ + { "Header": "X-Account-Bytes-Used", "Attribute": "BytesUsed", "Type": "headers.Uint64Readonly" }, + { "Header": "X-Account-Container-Count", "Attribute": "ContainerCount", "Type": "headers.Uint64Readonly" }, + { "Header": "X-Account-Meta-", "Attribute": "Metadata", "Type": "headers.Metadata" }, + { "Header": "X-Account-Meta-Quota-Bytes", "Attribute": "QuotaBytes", "Type": "headers.Uint64" }, + { "Header": "X-Account-Meta-Temp-URL-Key-2", "Attribute": "TempURLKey2", "Type": "headers.String" }, + { "Header": "X-Account-Meta-Temp-URL-Key", "Attribute": "TempURLKey", "Type": "headers.String" }, + { "Header": "X-Account-Object-Count", "Attribute": "ObjectCount", "Type": "headers.Uint64Readonly" }, + { "Header": "X-Timestamp", "Attribute": "Timestamp", "Type": "headers.UnixTimeReadonly" } + ] + }, + "Container": { + "Fields": [ + { "Header": "X-Container-Bytes-Used", "Attribute": "BytesUsed", "Type": "headers.Uint64Readonly" }, + { "Header": "X-Container-Meta-", "Attribute": "Metadata", "Type": "headers.Metadata" }, + { "Header": "X-Container-Meta-Quota-Bytes", "Attribute": "BytesUsedQuota", "Type": "headers.Uint64" }, + { "Header": "X-Container-Meta-Quota-Count", "Attribute": "ObjectCountQuota", "Type": "headers.Uint64" }, + { "Header": "X-Container-Meta-Temp-URL-Key-2", "Attribute": "TempURLKey2", "Type": "headers.String" }, + { "Header": "X-Container-Meta-Temp-URL-Key", "Attribute": "TempURLKey", "Type": "headers.String" }, + { "Header": "X-Container-Object-Count", "Attribute": "ObjectCount", "Type": "headers.Uint64Readonly" }, + { "Header": "X-Container-Read", "Attribute": "ReadACL", "Type": "headers.String" }, + { "Header": "X-Container-Sync-Key", "Attribute": "SyncKey", "Type": "headers.String" }, + { "Header": "X-Container-Sync-To", "Attribute": "SyncTo", "Type": "headers.String" }, + { "Header": "X-Container-Write", "Attribute": "WriteACL", "Type": "headers.String" }, + { "Header": "X-History-Location", "Attribute": "HistoryLocation", "Type": "headers.String" }, + { "Header": "X-Storage-Policy", "Attribute": "StoragePolicy", "Type": "headers.String" }, + { "Header": "X-Timestamp", "Attribute": "Timestamp", "Type": "headers.UnixTimeReadonly" }, + { "Header": "X-Versions-Location", "Attribute": "VersionsLocation", "Type": "headers.String" } + ] + } +} +--- +/******************************************************************************* +* +* THIS FILE IS AUTOGENERATED. +* +* Edit `generated.go.in` instead and run `make generate` to produce this file. +* +*******************************************************************************/ + +package schwift + +import ( + "net/textproto" + + "github.com/majewsky/schwift/headers" +) + +{{- 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 methods on type Headers. +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)] = "" +} + +//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. +{{- if eq $htype "Object" }} +// +//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. +{{- 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)] +} + +//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 +} + +//Validate returns headers.MalformedHeaderError if the value of any well-known +//header does not conform to its data type. This is called automatically by +//Schwift when preparing an {{$htype}}Headers instance from a GET/HEAD response, +//so you usually do not need to do it yourself. You will get the validation error +//from the {{$htype}} method doing the request, e.g. Headers(). +func (h {{$htype}}Headers) Validate() error { +{{- range $field := $hmeta.Fields }}{{if not (eq $field.Type "headers.String" "headers.Metadata")}} + if err := h.{{$field.Attribute}}().Validate(); err != nil { + return err + } +{{- end }}{{ end }} + return evadeGolintComplaint1() +} + +{{- range $field := $hmeta.Fields }} + +//{{$field.Attribute}} provides type-safe access to {{$field.Header}} headers. +func (h {{$htype}}Headers) {{$field.Attribute}}() {{$field.Type}} { + return {{$field.Type}}{ + Base: headers.Base{ + H: h, + K: "{{$field.Header}}", + }, + } +} +{{- end }} +{{- end }} + +func evadeGolintComplaint1() error { + return nil +} @@ -19,167 +19,30 @@ package schwift import ( - "fmt" - "reflect" - - "github.com/majewsky/schwift/headers" + "net/http" + "net/textproto" ) -//AccountHeaders contains the headers for an account. The Headers attribute -//contains the actual set of headers that was returned from a HEAD or GET -//request on the account, and will be sent by a PUT or POST request. The other -//attributes allow type-safe access to well-known headers, as noted in the tags -//next to each field. -// -//Follow the link on the Headers attribute for the documentation of the Get(), -//Set(), Del(), Clear() methods on this type. -type AccountHeaders struct { - headers.Headers - BytesUsed headers.Uint64Readonly `schwift:"X-Account-Bytes-Used"` - ContainerCount headers.Uint64Readonly `schwift:"X-Account-Container-Count"` - Metadata headers.Metadata `schwift:"X-Account-Meta-"` - ObjectCount headers.Uint64Readonly `schwift:"X-Account-Object-Count"` - QuotaBytes headers.Uint64 `schwift:"X-Account-Meta-Quota-Bytes"` - TempURLKey headers.String `schwift:"X-Account-Meta-Temp-URL-Key"` - TempURLKey2 headers.String `schwift:"X-Account-Meta-Temp-URL-Key-2"` - Timestamp headers.UnixTimeReadonly `schwift:"X-Timestamp"` - //forbid initialization as struct literal (must use NewAccountHeaders) - initialized bool -} - -//NewAccountHeaders prepares a new AccountHeaders instance. -// -//WARNING: Always use this function to construct AccountHeaders instances. -//Failure to do so will result in uncontrolled crashes! -func NewAccountHeaders() AccountHeaders { - var ah AccountHeaders - ah.Headers = make(headers.Headers) - initializeByReflection(&ah) - ah.initialized = true - return ah -} - -//Validate returns headers.MalformedHeaderError if the value of any well-known -//header does not conform to its data type. This is called automatically by -//Schwift when preparing an AccountHeaders instance from a GET/HEAD response, -//so you usually do not need to do it yourself. You will get the validation error -//from the Account method doing the request, e.g. Headers() or List(). -func (ah AccountHeaders) Validate() error { - return validateByReflection(&ah) -} - -//ContainerHeaders contains the headers for a container. The Headers attribute -//contains the actual set of headers that was returned from a HEAD or GET -//request on the container, and will be sent by a PUT or POST request. The -//other attributes allow type-safe access to well-known headers, as noted in -//the tags next to each field. -// -//Follow the link on the Headers attribute for the documentation of the Get(), -//Set(), Del(), Clear() methods on this type. -type ContainerHeaders struct { - headers.Headers - BytesUsed headers.Uint64Readonly `schwift:"X-Container-Bytes-Used"` - BytesUsedQuota headers.Uint64 `schwift:"X-Container-Meta-Quota-Bytes"` - HistoryLocation headers.String `schwift:"X-History-Location"` - Metadata headers.Metadata `schwift:"X-Container-Meta-"` - ObjectCount headers.Uint64Readonly `schwift:"X-Container-Object-Count"` - ObjectCountQuota headers.Uint64 `schwift:"X-Container-Meta-Quota-Count"` - ReadACL headers.String `schwift:"X-Container-Read"` - //StoragePolicy can only be set in a PUT request. - StoragePolicy headers.String `schwift:"X-Storage-Policy"` - SyncKey headers.String `schwift:"X-Container-Sync-Key"` - SyncTo headers.String `schwift:"X-Container-Sync-To"` - TempURLKey2 headers.String `schwift:"X-Container-Meta-Temp-URL-Key-2"` - TempURLKey headers.String `schwift:"X-Container-Meta-Temp-URL-Key"` - Timestamp headers.UnixTimeReadonly `schwift:"X-Timestamp"` - VersionsLocation headers.String `schwift:"X-Versions-Location"` - WriteACL headers.String `schwift:"X-Container-Write"` - //TODO map X-Container-Meta-Access-Control-* (requires new data types) - //forbid initialization as struct literal (must use NewContainerHeaders) - initialized bool -} - -//NewContainerHeaders prepares a new ContainerHeaders instance. -// -//WARNING: Always use this function to construct ContainerHeaders instances. -//Failure to do so will result in uncontrolled crashes! -func NewContainerHeaders() ContainerHeaders { - var ch ContainerHeaders - ch.Headers = make(headers.Headers) - initializeByReflection(&ch) - ch.initialized = true - return ch -} - -//Validate returns headers.MalformedHeaderError if the value of any well-known -//header does not conform to its data type. This is called automatically by -//Schwift when preparing an ContainerHeaders instance from a GET/HEAD response, -//so you usually do not need to do it yourself. You will get the validation error -//from the Container method doing the request, e.g. Headers() or List(). -func (ch ContainerHeaders) Validate() error { - return validateByReflection(&ch) -} - -type fieldInfo struct { - FieldName string - HeaderName string -} - -func initializeByReflection(value interface{}) { - rv := reflect.ValueOf(value).Elem() - hdrs := rv.FieldByName("Headers").Interface().(headers.Headers) - - foreachTaggedField(value, func(fieldPtr interface{}, info fieldInfo) error { - base := reflect.ValueOf(fieldPtr).Elem().FieldByName("Base").Addr().Interface().(*headers.Base) - base.H = hdrs - base.K = info.HeaderName +func headersToHTTP(h map[string]string) http.Header { + if h == nil { return nil - }) -} - -type validator interface { - Validate() error + } + dest := make(http.Header, len(h)) + for k, v := range h { + dest.Set(k, v) + } + return dest } -func validateByReflection(value interface{}) error { - return foreachTaggedField(value, func(fieldPtr interface{}, info fieldInfo) error { - if validator, ok := fieldPtr.(validator); ok { - err := validator.Validate() - if err != nil { - return err - } - } +func headersFromHTTP(src http.Header) map[string]string { + if src == nil { return nil - }) -} - -func ensureInitializedByReflection(value interface{}) { - initialized := reflect.ValueOf(value).FieldByName("initialized").Bool() - if !initialized { - msg := "values of type %T MUST be initialized with the corresponding New...() function" - panic(fmt.Sprintf(msg, value, value)) } -} - -func foreachTaggedField(value interface{}, callback func(fieldPtr interface{}, info fieldInfo) error) error { - rv := reflect.ValueOf(value).Elem() - - //iterate over the struct fields - for idx := 0; idx < rv.NumField(); idx++ { - fieldType := rv.Type().Field(idx) - headerName := fieldType.Tag.Get("schwift") - - if headerName != "" { - fieldPtr := rv.Field(idx).Addr().Interface() - err := callback(fieldPtr, fieldInfo{ - FieldName: fieldType.Name, - HeaderName: headerName, - }) - if err != nil { - return err - } + h := make(map[string]string, len(src)) + for k, v := range src { + if len(v) > 0 { + h[textproto.CanonicalMIMEHeaderKey(k)] = v[0] } } - - return nil + return h } diff --git a/headers/base.go b/headers/base.go index 1357e82..3b5b3ec 100644 --- a/headers/base.go +++ b/headers/base.go @@ -20,6 +20,14 @@ package headers //Base is an implementation detail. type Base struct { - H Headers + H Interface K string } + +//Interface is an implementation detail. +type Interface interface { + Clear(string) + Del(string) + Get(string) string + Set(string, string) +} diff --git a/headers_test.go b/headers_test.go index 66a2c36..f8b604c 100644 --- a/headers_test.go +++ b/headers_test.go @@ -24,24 +24,23 @@ import ( ) func TestParseAccountHeadersSuccess(t *testing.T) { - headers := NewAccountHeaders() - headers.FromHTTP(http.Header{ + headers := AccountHeaders(headersFromHTTP(http.Header{ "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"}, - }) + })) expectError(t, headers.Validate(), "") - expectUint64(t, headers.BytesUsed.Get(), 1234) - expectUint64(t, headers.ContainerCount.Get(), 23) - expectUint64(t, headers.ObjectCount.Get(), 42) - expectUint64(t, headers.QuotaBytes.Get(), 1048576) + expectUint64(t, headers.BytesUsed().Get(), 1234) + expectUint64(t, headers.ContainerCount().Get(), 23) + expectUint64(t, headers.ObjectCount().Get(), 42) + expectUint64(t, headers.QuotaBytes().Get(), 1048576) - expectString(t, headers.Metadata.Get("foo"), "bar") - expectString(t, headers.Metadata.Get("Foo"), "bar") - expectString(t, headers.Metadata.Get("FOO"), "bar") + expectString(t, headers.Metadata().Get("foo"), "bar") + expectString(t, headers.Metadata().Get("Foo"), "bar") + expectString(t, headers.Metadata().Get("FOO"), "bar") } //TODO TestParseAccountHeadersError diff --git a/util/render_template.go b/util/render_template.go new file mode 100755 index 0000000..1db8840 --- /dev/null +++ b/util/render_template.go @@ -0,0 +1,63 @@ +///usr/bin/env go run "$0" "$@"; exit $! + +/****************************************************************************** +* +* Copyright 2018 Stefan Majewsky <majewsky@gmx.net> +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "io/ioutil" + "os" + "strings" +) + +func main() { + input, err := ioutil.ReadAll(os.Stdin) + failIfError(err) + + sections := strings.SplitN(string(input), "\n---\n", 2) + if len(sections) != 2 { + fail("missing separator between data and template") + } + dataStr, templateStr := sections[0], sections[1] + + data := make(map[string]interface{}) + failIfError(json.Unmarshal([]byte(dataStr), &data)) + + tmpl, err := template.New("tmpl").Parse(templateStr) + failIfError(err) + + failIfError(tmpl.Execute(os.Stdout, data)) +} + +func failIfError(err error) { + if err != nil { + fail(err.Error()) + } +} + +func fail(msg string, args ...interface{}) { + if len(args) > 0 { + msg = fmt.Sprintf(msg, args...) + } + fmt.Fprintln(os.Stderr, msg) + os.Exit(1) +} |
