blob: da4ad94a32f8f3fc57ad64219f63666117497b5a [file] [log] [blame]
package gophercloud
import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
// MaybeString takes a string that might be a zero-value, and either returns a
// pointer to its address or a nil value (i.e. empty pointer). This is useful
// for converting zero values in options structs when the end-user hasn't
// defined values. Those zero values need to be nil in order for the JSON
// serialization to ignore them.
func MaybeString(original string) *string {
if original != "" {
return &original
}
return nil
}
var t time.Time
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Func, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
z := true
for i := 0; i < v.Len(); i++ {
z = z && isZero(v.Index(i))
}
return z
case reflect.Struct:
if v.Type() == reflect.TypeOf(t) {
if v.Interface().(time.Time).IsZero() {
return true
}
return false
}
z := true
for i := 0; i < v.NumField(); i++ {
z = z && isZero(v.Field(i))
}
return z
}
// Compare other types directly:
z := reflect.Zero(v.Type())
return v.Interface() == z.Interface()
}
func BuildQueryString(opts interface{}) (string, error) {
optsValue := reflect.ValueOf(opts)
if optsValue.Kind() == reflect.Ptr {
optsValue = optsValue.Elem()
}
optsType := reflect.TypeOf(opts)
if optsType.Kind() == reflect.Ptr {
optsType = optsType.Elem()
}
optsSlice := make([]string, 0)
if optsValue.Kind() == reflect.Struct {
for i := 0; i < optsValue.NumField(); i++ {
v := optsValue.Field(i)
f := optsType.Field(i)
qTag := f.Tag.Get("q")
// if the field has a 'q' tag, it goes in the query string
if qTag != "" {
tags := strings.Split(qTag, ",")
// if the field is set, add it to the slice of query pieces
if !isZero(v) {
switch v.Kind() {
case reflect.String:
optsSlice = append(optsSlice, tags[0]+"="+v.String())
case reflect.Int:
optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatInt(v.Int(), 10))
case reflect.Bool:
optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatBool(v.Bool()))
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return "", fmt.Errorf("Required query parameter not set.")
}
}
}
}
// URL encode the string for safety.
s := url.QueryEscape(strings.Join(optsSlice, "&"))
if s != "" {
s = "?" + s
}
return s, nil
}
// Return an error if the underlying type of 'opts' isn't a struct.
return "", fmt.Errorf("Options type is not a struct.")
}
func BuildRequestBody(opts interface{}) (map[string]interface{}, error) {
return nil, nil
}
func BuildHeaders(opts interface{}) (map[string]string, error) {
optsValue := reflect.ValueOf(opts)
if optsValue.Kind() == reflect.Ptr {
optsValue = optsValue.Elem()
}
optsType := reflect.TypeOf(opts)
if optsType.Kind() == reflect.Ptr {
optsType = optsType.Elem()
}
optsMap := make(map[string]string)
if optsValue.Kind() == reflect.Struct {
for i := 0; i < optsValue.NumField(); i++ {
v := optsValue.Field(i)
f := optsType.Field(i)
hTag := f.Tag.Get("h")
// if the field has a 'h' tag, it goes in the header
if hTag != "" {
tags := strings.Split(hTag, ",")
// if the field is set, add it to the slice of query pieces
if !isZero(v) {
switch v.Kind() {
case reflect.String:
optsMap[tags[0]] = v.String()
case reflect.Int:
optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
case reflect.Bool:
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return optsMap, fmt.Errorf("Required header not set.")
}
}
}
}
return optsMap, nil
}
// Return an error if the underlying type of 'opts' isn't a struct.
return optsMap, fmt.Errorf("Options type is not a struct.")
}