aboutsummaryrefslogtreecommitdiff
path: root/headers.go
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2018-02-05 21:30:33 +0100
committerStefan Majewsky <majewsky@gmx.net>2018-02-05 21:44:35 +0100
commit3a24741929cd12ffee5e54d0e9a9afb83c5069b3 (patch)
tree6a91b159d25814e873fcfae08cd40cba9b1dcc14 /headers.go
parent7de32502590995ee8d7cc8b681b0f723ca35ccb0 (diff)
downloadgo-schwift-3a24741929cd12ffee5e54d0e9a9afb83c5069b3.tar.gz
redesign the header API
I'm quite satisfied with this right now (though this doesn't say anything about how I feel about it tomorrow), but it's ugly that some guts (headers.Base) are exposed in the public API.
Diffstat (limited to 'headers.go')
-rw-r--r--headers.go322
1 files changed, 90 insertions, 232 deletions
diff --git a/headers.go b/headers.go
index 7f8cfd1..44d0607 100644
--- a/headers.go
+++ b/headers.go
@@ -19,268 +19,126 @@
package schwift
import (
- "fmt"
- "net/http"
- "net/textproto"
"reflect"
- "strconv"
- "strings"
+
+ "github.com/majewsky/schwift/headers"
)
-//AccountHeaders contains the headers for an account. The Raw attribute
-//contains the original set of headers returned from a HEAD or GET request on
-//the account. The other attributes contain the parsed values of common
-//headers, as noted in the tags next to each field. Well-known metadata headers
-//can be accessed in a type-safe way using the methods on this type.
+//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.
type AccountHeaders struct {
- BytesUsed uint64 `schwift:"ro,X-Account-Bytes-Used"`
- ContainerCount uint64 `schwift:"ro,X-Account-Container-Count"`
- ObjectCount uint64 `schwift:"ro,X-Account-Object-Count"`
- Metadata Metadata `schwift:"rw,X-Account-Meta-"`
- Raw http.Header
-}
-
-//QuotaBytes returns a handle to read or write the X-Account-Meta-Quota-Bytes field.
-func (a AccountHeaders) QuotaBytes() UnsignedIntField {
- return UnsignedIntField{
- a.Metadata,
- "X-Account-Meta-", "Quota-Bytes",
- }
-}
-
-//TempURLKey returns a handle to read or write the X-Account-Meta-Temp-URL-Key field.
-func (a AccountHeaders) TempURLKey() StringField {
- return StringField{
- a.Metadata,
- "X-Account-Meta-", "Temp-URL-Key",
- }
-}
-
-//TempURLKey2 returns a handle to read or write the X-Account-Meta-Temp-URL-Key-2 field.
-func (a AccountHeaders) TempURLKey2() StringField {
- return StringField{
- a.Metadata,
- "X-Account-Meta-", "Temp-URL-Key-2",
- }
-}
-
-//ContainerHeaders contains the headers for an account. The Raw attribute
-//contains the original set of headers returned from a HEAD or GET request on
-//the account. The other attributes contain the parsed values of common
-//headers, as noted in the tags next to each field. Well-known metadata headers
-//can be accessed in a type-safe way using the methods on this type.
+ 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"`
+ //forbid initialization as struct literal (must use NewAccountHeaders)
+ private struct{}
+}
+
+//NewAccountHeaders prepares a new AccountHeaders instance.
+func NewAccountHeaders() AccountHeaders {
+ var ah AccountHeaders
+ ah.Headers = make(headers.Headers)
+ initializeByReflection(&ah)
+ 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.
type ContainerHeaders struct {
- Metadata Metadata `schwift:"rw,X-Container-Meta-"`
- Raw http.Header
+ headers.Headers
+ Metadata headers.Metadata `schwift:"X-Container-Meta-"`
//TODO map well-known headers
+ //forbid initialization as struct literal (must use NewContainerHeaders)
+ private struct{}
}
-////////////////////////////////////////////////////////////////////////////////
-// field types
-
-//StringField is a helper type used in the interface of AccountHeaders,
-//ContainerHeaders and ObjectHeaders. For example:
-//
-// var headers AccountHeaders
-// ...
-// value := headers.TempURLKey().Get()
-// headers.TempURLKey().Set(value + " changed")
-// headers.TempURLKey().Clear()
-type StringField struct {
- metadata Metadata
- prefix string
- key string
-}
-
-//Get returns the value for this key, or the empty string if the key does not exist.
-func (f StringField) Get() string {
- return f.metadata.Get(f.key)
+//NewContainerHeaders prepares a new ContainerHeaders instance.
+func NewContainerHeaders() ContainerHeaders {
+ var ch ContainerHeaders
+ ch.Headers = make(headers.Headers)
+ initializeByReflection(&ch)
+ return ch
}
-//Set writes a new value for this key into the original AccountHeaders,
-//ContainerHeaders or ObjectHeaders instance.
-func (f StringField) Set(value string) {
- f.metadata.Set(f.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() or List().
+func (ch ContainerHeaders) Validate() error {
+ return validateByReflection(&ch)
}
-//Clear removes this key from the original AccountHeaders, ContainerHeaders or
-//ObjectHeaders instance.
-func (f StringField) Clear() {
- f.metadata.Clear(f.key)
-}
-
-//UnsignedIntField is a helper type used in the interface of AccountHeaders,
-//ContainerHeaders and ObjectHeaders. For example:
-//
-// var headers AccountHeaders
-// ...
-// if headers.QuotaBytes.Exists() {
-// value, err := headers.QuotaBytes().Get()
-// headers.QuotaBytes().Set(value * 2)
-// }
-// ....
-// headers.QuotaBytes().Clear()
-type UnsignedIntField struct {
- metadata Metadata
- prefix string
- key string
-}
-
-//Exists returns whether there is a value for this key.
-func (f UnsignedIntField) Exists() bool {
- return f.metadata.Get(f.key) != ""
-}
-
-//Get returns the value for this key, or 0 if the key does not exist.
-func (f UnsignedIntField) Get() (uint64, error) {
- str := f.metadata.Get(f.key)
- if str == "" {
- return 0, nil
- }
- value, err := strconv.ParseUint(str, 10, 64)
- if err != nil {
- err = MalformedHeaderError{Key: f.prefix + f.key, ParseError: err}
- }
- return value, err
-}
-
-//Set writes a new value for this key into the original AccountHeaders,
-//ContainerHeaders or ObjectHeaders instance.
-func (f UnsignedIntField) Set(value uint64) {
- f.metadata.Set(f.key, strconv.FormatUint(value, 10))
-}
-
-//Clear removes this key from the original AccountHeaders, ContainerHeaders or
-//ObjectHeaders instance.
-func (f UnsignedIntField) Clear() {
- f.metadata.Clear(f.key)
+type fieldInfo struct {
+ FieldName string
+ HeaderName string
}
-////////////////////////////////////////////////////////////////////////////////
-// generic parsing functions
-
-func parseHeaders(hdr http.Header, target interface{}) error {
- return foreachField(target, func(fieldPtr interface{}, info fieldInfo) error {
- //populate the .Raw field that all input types share
- if info.FieldName == "Raw" {
- *(fieldPtr.(*http.Header)) = hdr
- return nil
- }
-
- //skip over fields without schwift field tag
- if info.HeaderName == "" {
- return nil
- }
-
- //decode header value into field depending on type
- switch fieldPtr := fieldPtr.(type) {
- case *string:
- *fieldPtr = hdr.Get(info.HeaderName)
- case *uint64:
- value, err := strconv.ParseUint(hdr.Get(info.HeaderName), 10, 64)
- if err != nil {
- return MalformedHeaderError{info.HeaderName, err}
- }
- *fieldPtr = value
- case *Metadata:
- //collect all headers with a prefix equal to `headerName`
- values := make(Metadata)
- for key, value := range hdr {
- key = textproto.CanonicalMIMEHeaderKey(key)
- if strings.HasPrefix(key, info.HeaderName) {
- key = strings.TrimPrefix(key, info.HeaderName)
- values[key] = value[0]
- }
- }
- *fieldPtr = values
- default:
- panic(fmt.Sprintf("parseHeaders: cannot handle field type %T", fieldPtr))
- }
+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
return nil
})
}
-func compileHeaders(headers interface{}, opts *RequestOptions) RequestOptions {
- hdr := make(http.Header)
-
- foreachField(headers, func(fieldPtr interface{}, info fieldInfo) error {
- //skip over fields without schwift field tag, and readonly fields
- if info.HeaderName == "" || info.Access != "rw" {
- return nil
- }
+type validator interface {
+ Validate() error
+}
- //decode header value into field depending on type
- switch fieldPtr := fieldPtr.(type) {
- case *string:
- hdr.Set(info.HeaderName, *fieldPtr)
- case *uint64:
- hdr.Set(info.HeaderName, strconv.FormatUint(*fieldPtr, 10))
- case *Metadata:
- for key, value := range *fieldPtr {
- //empty string means that this key shall be removed
- if value == "" {
- //for object metadata, a key is removed by just omitting it...
- if info.HeaderName != "X-Object-Meta-" {
- //...for container and account metadata, a key is removed by
- //setting its value to the empty string
- hdr.Set(info.HeaderName+key, "")
- }
- } else {
- //NOTE: The spec says that `value` needs to be percent-encoded, but
- //neither python-swiftclient nor ncw/swift do so. If in doubt, we
- //follow the de-facto standards rather than the spec.
- hdr.Set(info.HeaderName+key, value)
- }
+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
}
- default:
- panic(fmt.Sprintf("compileHeaders: cannot handle field type %T", fieldPtr))
}
return nil
})
-
- //contents of `opts` overrides contents of `headers`
- result := RequestOptions{Headers: hdr}
- if opts != nil {
- result.Values = opts.Values
- for k, v := range opts.Headers {
- result.Headers[k] = v
- }
- }
- return result
-}
-
-type fieldInfo struct {
- FieldName string
- Access string
- HeaderName string
}
-func foreachField(value interface{}, callback func(fieldPtr interface{}, info fieldInfo) error) error {
- rv := reflect.ValueOf(value)
- //unpack pointer type if necessary
- if rv.Type().Kind() == reflect.Ptr {
- rv = rv.Elem()
- }
+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)
- fieldPtr := rv.Field(idx).Addr().Interface()
-
- //decode schwift:"<access>,<header-name>" tag
- tagValues := strings.SplitN(fieldType.Tag.Get("schwift"), ",", 2)
- fieldInfo := fieldInfo{
- FieldName: fieldType.Name,
- }
- if len(tagValues) == 2 {
- fieldInfo.Access = tagValues[0]
- fieldInfo.HeaderName = tagValues[1]
- }
-
- err := callback(fieldPtr, fieldInfo)
- if err != nil {
- return err
+ 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
+ }
}
}