aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2018-02-07 19:33:36 +0100
committerStefan Majewsky <majewsky@gmx.net>2018-02-07 19:33:36 +0100
commit801b5207dcbf3438e7612e1f7edc9de32ce0780c (patch)
tree6f0454ec49bf769e361cfa393290efc68583ca67
parent8bddda344201d9f034b5e9c2e9c37a25caeb80cb (diff)
downloadgo-schwift-801b5207dcbf3438e7612e1f7edc9de32ce0780c.tar.gz
switch from reflection to code generation
This allows me to make the API much simpler. More simplification forthcoming in the following commit; I just want to make a cut since `make test` is happy right now.
-rw-r--r--Makefile13
-rw-r--r--account.go9
-rw-r--r--account_test.go30
-rw-r--r--container.go20
-rw-r--r--container_test.go2
-rw-r--r--generated.go367
-rw-r--r--generated.go.in124
-rw-r--r--headers.go171
-rw-r--r--headers/base.go10
-rw-r--r--headers_test.go19
-rwxr-xr-xutil/render_template.go63
11 files changed, 625 insertions, 203 deletions
diff --git a/Makefile b/Makefile
index 35b939b..e1d88d2 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/account.go b/account.go
index f4e8c07..5586a16 100644
--- a/account.go
+++ b/account.go
@@ -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
+}
diff --git a/headers.go b/headers.go
index fe8fa38..9661ec0 100644
--- a/headers.go
+++ b/headers.go
@@ -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)
+}