diff options
Diffstat (limited to 'headers.go')
| -rw-r--r-- | headers.go | 322 |
1 files changed, 90 insertions, 232 deletions
@@ -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 + } } } |
