| 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.") |
| } |