aboutsummaryrefslogtreecommitdiff
path: root/headers
diff options
context:
space:
mode:
Diffstat (limited to 'headers')
-rw-r--r--headers/base.go25
-rw-r--r--headers/errors.go31
-rw-r--r--headers/headers.go91
-rw-r--r--headers/headers_test.go97
-rw-r--r--headers/metadata.go52
-rw-r--r--headers/string.go60
-rw-r--r--headers/uint64.go120
7 files changed, 476 insertions, 0 deletions
diff --git a/headers/base.go b/headers/base.go
new file mode 100644
index 0000000..1357e82
--- /dev/null
+++ b/headers/base.go
@@ -0,0 +1,25 @@
+/******************************************************************************
+*
+* 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 headers
+
+//Base is an implementation detail.
+type Base struct {
+ H Headers
+ K string
+}
diff --git a/headers/errors.go b/headers/errors.go
new file mode 100644
index 0000000..6c1f5ab
--- /dev/null
+++ b/headers/errors.go
@@ -0,0 +1,31 @@
+/******************************************************************************
+*
+* 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 headers
+
+//MalformedHeaderError is generated when a response from Swift contains a
+//malformed header.
+type MalformedHeaderError struct {
+ Key string
+ ParseError error
+}
+
+//Error implements the builtin/error interface.
+func (e MalformedHeaderError) Error() string {
+ return "Bad header " + e.Key + ": " + e.ParseError.Error()
+}
diff --git a/headers/headers.go b/headers/headers.go
new file mode 100644
index 0000000..33127a8
--- /dev/null
+++ b/headers/headers.go
@@ -0,0 +1,91 @@
+/******************************************************************************
+*
+* 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 headers contains helper types for the type-safe representation of
+//headers on Swift accounts/containers/objects.
+package headers
+
+import (
+ "net/http"
+ "net/textproto"
+)
+
+//Headers works like http.Header, but does not allow multiple values per key.
+//
+//If you write the map directly, without using the provided methods, you must
+//normalize all keys with textproto.CanonicalMIMEHeaderKey(). Otherwise, the
+//results are undefined.
+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.Set(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 Headers) Del(key string) {
+ k := textproto.CanonicalMIMEHeaderKey(key)
+ delete(h, k)
+}
+
+//Get returns the value for the specified header.
+func (h Headers) Get(key string) string {
+ if h == nil {
+ return ""
+ }
+ k := textproto.CanonicalMIMEHeaderKey(key)
+ return h[k]
+}
+
+//Set sets a new value for the specified header, possibly overwriting a
+//previous value.
+func (h Headers) Set(key, value string) {
+ k := textproto.CanonicalMIMEHeaderKey(key)
+ h[k] = value
+}
+
+//ToHTTP converts this map into a http.Header.
+func (h Headers) ToHTTP() http.Header {
+ dest := make(http.Header, len(h))
+ for k, v := range h {
+ dest.Set(k, v)
+ }
+ return dest
+}
+
+//FromHTTP populates this map with the headers in the given http.Header. When a
+//header has multiple values, every value but the first one will be discarded.
+func (h Headers) FromHTTP(src http.Header) {
+ for k, v := range src {
+ if len(v) > 0 {
+ h.Set(k, v[0])
+ }
+ }
+}
diff --git a/headers/headers_test.go b/headers/headers_test.go
new file mode 100644
index 0000000..9724435
--- /dev/null
+++ b/headers/headers_test.go
@@ -0,0 +1,97 @@
+/******************************************************************************
+*
+* 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 headers
+
+import "testing"
+
+func TestHeaders(t *testing.T) {
+ h := make(Headers)
+ h.Set("first", "value1")
+ h.Set("second-thing", "value2")
+
+ expectHeaders(t, h, map[string]string{
+ "First": "value1",
+ "Second-Thing": "value2",
+ })
+
+ expectString(t, h.Get("first"), "value1")
+ expectString(t, h.Get("First"), "value1")
+ expectString(t, h.Get("FIRST"), "value1")
+
+ h.Set("first", "changed")
+ h.Set("third", "")
+
+ expectHeaders(t, h, map[string]string{
+ "First": "changed",
+ "Second-Thing": "value2",
+ "Third": "",
+ })
+
+ h.Clear("second-thing")
+ h.Clear("fourth-thing")
+
+ expectHeaders(t, h, map[string]string{
+ "First": "changed",
+ "Second-Thing": "",
+ "Third": "",
+ "Fourth-Thing": "",
+ })
+
+ h.Del("FIRST")
+ h.Del("second-Thing")
+
+ expectHeaders(t, h, map[string]string{
+ "Third": "",
+ "Fourth-Thing": "",
+ })
+
+}
+
+func expectString(t *testing.T, actual string, expected string) {
+ t.Helper()
+ if actual != expected {
+ t.Errorf("expected value %q, got %q instead\n", expected, actual)
+ }
+}
+
+func expectHeaders(t *testing.T, actual Headers, expected map[string]string) {
+ t.Helper()
+ reported := make(map[string]bool)
+
+ for k, av := range actual {
+ ev, exists := expected[k]
+ if !exists {
+ ev = "<not set>"
+ }
+ if av != ev {
+ t.Errorf(`expected "%s: %s", got "%s: %s" instead`, k, ev, k, av)
+ reported[k] = true
+ }
+ }
+
+ for k, ev := range expected {
+ av, exists := actual[k]
+ if !exists {
+ av = "<not set>"
+ }
+ if av != ev && !reported[k] {
+ t.Errorf(`expected "%s: %s", got "%s: %s" instead`, k, ev, k, av)
+ }
+ }
+}
diff --git a/headers/metadata.go b/headers/metadata.go
new file mode 100644
index 0000000..8f07e89
--- /dev/null
+++ b/headers/metadata.go
@@ -0,0 +1,52 @@
+/******************************************************************************
+*
+* 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 headers
+
+//Metadata is a helper type that provides safe access to the metadata headers
+//in a schwift.Headers instance. It cannot be directly constructed, but each
+//subtype of schwift.Headers has a field "Metadata" of this type. For example:
+//
+// var hdr ObjectHeaders
+// //the following two statements are equivalent
+// hdr.Set("X-Object-Meta-Access", "strictly confidential")
+// hdr.Metadata.Set("Access", "strictly confidential")
+// //because hdr.Metadata is a headers.Metadata instance
+type Metadata struct {
+ Base
+}
+
+//Clear works like Headers.Clear(), but prepends the metadata prefix to the key.
+func (m Metadata) Clear(key string) {
+ m.H.Clear(m.K + key)
+}
+
+//Del works like Headers.Del(), but prepends the metadata prefix to the key.
+func (m Metadata) Del(key string) {
+ m.H.Del(m.K + key)
+}
+
+//Get works like Headers.Get(), but prepends the metadata prefix to the key.
+func (m Metadata) Get(key string) string {
+ return m.H.Get(m.K + key)
+}
+
+//Set works like Headers.Set(), but prepends the metadata prefix to the key.
+func (m Metadata) Set(key, value string) {
+ m.H.Set(m.K+key, value)
+}
diff --git a/headers/string.go b/headers/string.go
new file mode 100644
index 0000000..9979aef
--- /dev/null
+++ b/headers/string.go
@@ -0,0 +1,60 @@
+/******************************************************************************
+*
+* 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 headers
+
+//String is a helper type that provides type-safe access to a Swift header key
+//whose value is a string. It cannot be directly constructed, but some subtypes
+//of schwift.Headers have fields of this type. For example:
+//
+// var hdr AccountHeaders
+// //the following two statements are equivalent:
+// hdr.Set("X-Container-Read", ".r:*,.rlistings")
+// hdr.ReadACL.Set(".r:*,.rlistings")
+// //because hdr.ReadACL is a headers.String instance
+type String struct {
+ Base
+}
+
+//Exists checks whether there is a value for this header.
+func (f String) Exists() bool {
+ return f.H.Get(f.K) != ""
+}
+
+//Get returns the value for this header, or the empty string if there is no value.
+func (f String) Get() string {
+ return f.H.Get(f.K)
+}
+
+//Set writes a new value for this header into the corresponding schwift.Headers
+//instance.
+func (f String) Set(value string) {
+ f.H.Set(f.K, value)
+}
+
+//Del removes this key from the original schwift.Headers instance, so that the
+//key will remain unchanged on the server during Update().
+func (f String) Del() {
+ f.H.Del(f.K)
+}
+
+//Clear sets this key to an empty string in the original schwift.Headers
+//instance, so that the key will be removed on the server during Update().
+func (f String) Clear() {
+ f.H.Clear(f.K)
+}
diff --git a/headers/uint64.go b/headers/uint64.go
new file mode 100644
index 0000000..6e8668b
--- /dev/null
+++ b/headers/uint64.go
@@ -0,0 +1,120 @@
+/******************************************************************************
+*
+* 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 headers
+
+import (
+ "strconv"
+)
+
+//Uint64 is a helper type that provides type-safe access to a Swift header
+//whose value is an unsigned integer. It cannot be directly constructed, but
+//some subtypes of schwift.Headers have fields of this type. For example:
+//
+// var hdr AccountHeaders
+// //the following two statements are equivalent:
+// hdr.Set("X-Account-Meta-Quota-Bytes", "1048576")
+// hdr.QuotaBytes.Set(1 << 20)
+// //because hdr.QuotaBytes is a headers.Uint64 instance
+type Uint64 struct {
+ Base
+}
+
+//Exists checks whether there is a value for this header.
+func (f Uint64) Exists() bool {
+ return f.H.Get(f.K) != ""
+}
+
+//Get returns the value for this header, or 0 if there is no value (or if it is
+//not a valid uint64).
+func (f Uint64) Get() uint64 {
+ v, err := strconv.ParseUint(f.H.Get(f.K), 10, 64)
+ if err != nil {
+ return 0
+ }
+ return v
+}
+
+//Set writes a new value for this header into the corresponding schwift.Headers
+//instance.
+func (f Uint64) Set(value uint64) {
+ f.H.Set(f.K, strconv.FormatUint(value, 10))
+}
+
+//Del removes this key from the original schwift.Headers instance, so that the
+//key will remain unchanged on the server during Update().
+func (f Uint64) Del() {
+ f.H.Del(f.K)
+}
+
+//Clear sets this key to an empty string in the original schwift.Headers
+//instance, so that the key will be removed on the server during Update().
+func (f Uint64) Clear() {
+ f.H.Clear(f.K)
+}
+
+//Validate is only used internally, but needs to be exported to cross package
+//boundaries.
+func (f Uint64) Validate() error {
+ val := f.H.Get(f.K)
+ if val == "" {
+ return nil
+ }
+ _, err := strconv.ParseUint(val, 10, 64)
+ if err == nil {
+ return nil
+ }
+ return MalformedHeaderError{f.K, err}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+//Uint64Readonly is a readonly variant of Uint64. It is used for fields that
+//cannot be set by the client.
+type Uint64Readonly struct {
+ Base
+}
+
+//Exists checks whether there is a value for this header.
+func (f Uint64Readonly) Exists() bool {
+ return f.H.Get(f.K) != ""
+}
+
+//Get returns the value for this header, or 0 if there is no value (or if it is
+//not a valid uint64).
+func (f Uint64Readonly) Get() uint64 {
+ v, err := strconv.ParseUint(f.H.Get(f.K), 10, 64)
+ if err != nil {
+ return 0
+ }
+ return v
+}
+
+//Validate is only used internally, but needs to be exported to cross package
+//boundaries.
+func (f Uint64Readonly) Validate() error {
+ val := f.H.Get(f.K)
+ if val == "" {
+ return nil
+ }
+ _, err := strconv.ParseUint(val, 10, 64)
+ if err == nil {
+ return nil
+ }
+ return MalformedHeaderError{f.K, err}
+}