1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
|
package gophercloud
import (
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
// EnabledState is a convenience type, mostly used in Create and Update
// operations. Because the zero value of a bool is FALSE, we need to use a
// pointer instead to indicate zero-ness.
type EnabledState *bool
// Convenience vars for EnabledState values.
var (
iTrue = true
iFalse = false
Enabled EnabledState = &iTrue
Disabled EnabledState = &iFalse
)
// IntToPointer is a function for converting integers into integer pointers.
// This is useful when passing in options to operations.
func IntToPointer(i int) *int {
return &i
}
/*
MaybeString is an internal function to be used by request methods in individual
resource packages.
It takes a string that might be a zero value and returns either a pointer to its
address or nil. This is useful for allowing users to conveniently omit values
from an options struct by leaving them zeroed, but still pass nil to the JSON
serializer so they'll be omitted from the request body.
*/
func MaybeString(original string) *string {
if original != "" {
return &original
}
return nil
}
/*
MaybeInt is an internal function to be used by request methods in individual
resource packages.
Like MaybeString, it accepts an int that may or may not be a zero value, and
returns either a pointer to its address or nil. It's intended to hint that the
JSON serializer should omit its field.
*/
func MaybeInt(original int) *int {
if original != 0 {
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()
}
/*
BuildQueryString is an internal function to be used by request methods in
individual resource packages.
It accepts a tagged structure and expands it into a URL struct. Field names are
converted into query parameters based on a "q" tag. For example:
type struct Something {
Bar string `q:"x_bar"`
Baz int `q:"lorem_ipsum"`
}
instance := Something{
Bar: "AAA",
Baz: "BBB",
}
will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
The struct's fields may be strings, integers, or boolean values. Fields left at
their type's zero value will be omitted from the query.
*/
func BuildQueryString(opts interface{}) (*url.URL, 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()
}
params := url.Values{}
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:
params.Add(tags[0], v.String())
case reflect.Int:
params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
case reflect.Bool:
params.Add(tags[0], strconv.FormatBool(v.Bool()))
case reflect.Slice:
switch v.Type().Elem() {
case reflect.TypeOf(0):
for i := 0; i < v.Len(); i++ {
params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
}
default:
for i := 0; i < v.Len(); i++ {
params.Add(tags[0], v.Index(i).String())
}
}
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
}
}
}
}
return &url.URL{RawQuery: params.Encode()}, nil
}
// Return an error if the underlying type of 'opts' isn't a struct.
return nil, fmt.Errorf("Options type is not a struct.")
}
/*
BuildHeaders is an internal function to be used by request methods in
individual resource packages.
It accepts an arbitrary tagged structure and produces a string map that's
suitable for use as the HTTP headers of an outgoing request. Field names are
mapped to header names based in "h" tags.
type struct Something {
Bar string `h:"x_bar"`
Baz int `h:"lorem_ipsum"`
}
instance := Something{
Bar: "AAA",
Baz: "BBB",
}
will be converted into:
map[string]string{
"x_bar": "AAA",
"lorem_ipsum": "BBB",
}
Untagged fields and fields left at their zero values are skipped. Integers,
booleans and string values are supported.
*/
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.")
}
// IDSliceToQueryString takes a slice of elements and converts them into a query
// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
// result would be `?name=20&name=40&name=60'
func IDSliceToQueryString(name string, ids []int) string {
str := ""
for k, v := range ids {
if k == 0 {
str += "?"
} else {
str += "&"
}
str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
}
return str
}
// IntWithinRange returns TRUE if an integer falls within a defined range, and
// FALSE if not.
func IntWithinRange(val, min, max int) bool {
return val > min && val < max
}
|