github.com/gophercloud/gophercloud@v1.14.1/params.go (about) 1 package gophercloud 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/url" 7 "reflect" 8 "strconv" 9 "strings" 10 "time" 11 ) 12 13 /* 14 BuildRequestBody builds a map[string]interface from the given `struct`. If 15 parent is not an empty string, the final map[string]interface returned will 16 encapsulate the built one. For example: 17 18 disk := 1 19 createOpts := flavors.CreateOpts{ 20 ID: "1", 21 Name: "m1.tiny", 22 Disk: &disk, 23 RAM: 512, 24 VCPUs: 1, 25 RxTxFactor: 1.0, 26 } 27 28 body, err := gophercloud.BuildRequestBody(createOpts, "flavor") 29 30 The above example can be run as-is, however it is recommended to look at how 31 BuildRequestBody is used within Gophercloud to more fully understand how it 32 fits within the request process as a whole rather than use it directly as shown 33 above. 34 */ 35 func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) { 36 optsValue := reflect.ValueOf(opts) 37 if optsValue.Kind() == reflect.Ptr { 38 optsValue = optsValue.Elem() 39 } 40 41 optsType := reflect.TypeOf(opts) 42 if optsType.Kind() == reflect.Ptr { 43 optsType = optsType.Elem() 44 } 45 46 optsMap := make(map[string]interface{}) 47 if optsValue.Kind() == reflect.Struct { 48 //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind()) 49 for i := 0; i < optsValue.NumField(); i++ { 50 v := optsValue.Field(i) 51 f := optsType.Field(i) 52 53 if f.Name != strings.Title(f.Name) { 54 //fmt.Printf("Skipping field: %s...\n", f.Name) 55 continue 56 } 57 58 //fmt.Printf("Starting on field: %s...\n", f.Name) 59 60 zero := isZero(v) 61 //fmt.Printf("v is zero?: %v\n", zero) 62 63 // if the field has a required tag that's set to "true" 64 if requiredTag := f.Tag.Get("required"); requiredTag == "true" { 65 //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero) 66 // if the field's value is zero, return a missing-argument error 67 if zero { 68 // if the field has a 'required' tag, it can't have a zero-value 69 err := ErrMissingInput{} 70 err.Argument = f.Name 71 return nil, err 72 } 73 } 74 75 if xorTag := f.Tag.Get("xor"); xorTag != "" { 76 //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag) 77 xorField := optsValue.FieldByName(xorTag) 78 var xorFieldIsZero bool 79 if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) { 80 xorFieldIsZero = true 81 } else { 82 if xorField.Kind() == reflect.Ptr { 83 xorField = xorField.Elem() 84 } 85 xorFieldIsZero = isZero(xorField) 86 } 87 if !(zero != xorFieldIsZero) { 88 err := ErrMissingInput{} 89 err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag) 90 err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag) 91 return nil, err 92 } 93 } 94 95 if orTag := f.Tag.Get("or"); orTag != "" { 96 //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag) 97 //fmt.Printf("field is zero?: %v\n", zero) 98 if zero { 99 orField := optsValue.FieldByName(orTag) 100 var orFieldIsZero bool 101 if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) { 102 orFieldIsZero = true 103 } else { 104 if orField.Kind() == reflect.Ptr { 105 orField = orField.Elem() 106 } 107 orFieldIsZero = isZero(orField) 108 } 109 if orFieldIsZero { 110 err := ErrMissingInput{} 111 err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag) 112 err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag) 113 return nil, err 114 } 115 } 116 } 117 118 jsonTag := f.Tag.Get("json") 119 if jsonTag == "-" { 120 continue 121 } 122 123 if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) { 124 sliceValue := v 125 if sliceValue.Kind() == reflect.Ptr { 126 sliceValue = sliceValue.Elem() 127 } 128 129 for i := 0; i < sliceValue.Len(); i++ { 130 element := sliceValue.Index(i) 131 if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) { 132 _, err := BuildRequestBody(element.Interface(), "") 133 if err != nil { 134 return nil, err 135 } 136 } 137 } 138 } 139 if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) { 140 if zero { 141 //fmt.Printf("value before change: %+v\n", optsValue.Field(i)) 142 if jsonTag != "" { 143 jsonTagPieces := strings.Split(jsonTag, ",") 144 if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" { 145 if v.CanSet() { 146 if !v.IsNil() { 147 if v.Kind() == reflect.Ptr { 148 v.Set(reflect.Zero(v.Type())) 149 } 150 } 151 //fmt.Printf("value after change: %+v\n", optsValue.Field(i)) 152 } 153 } 154 } 155 continue 156 } 157 158 //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name) 159 _, err := BuildRequestBody(v.Interface(), f.Name) 160 if err != nil { 161 return nil, err 162 } 163 } 164 } 165 166 //fmt.Printf("opts: %+v \n", opts) 167 168 b, err := json.Marshal(opts) 169 if err != nil { 170 return nil, err 171 } 172 173 //fmt.Printf("string(b): %s\n", string(b)) 174 175 err = json.Unmarshal(b, &optsMap) 176 if err != nil { 177 return nil, err 178 } 179 180 //fmt.Printf("optsMap: %+v\n", optsMap) 181 182 if parent != "" { 183 optsMap = map[string]interface{}{parent: optsMap} 184 } 185 //fmt.Printf("optsMap after parent added: %+v\n", optsMap) 186 return optsMap, nil 187 } 188 // Return an error if the underlying type of 'opts' isn't a struct. 189 return nil, fmt.Errorf("Options type is not a struct.") 190 } 191 192 // EnabledState is a convenience type, mostly used in Create and Update 193 // operations. Because the zero value of a bool is FALSE, we need to use a 194 // pointer instead to indicate zero-ness. 195 type EnabledState *bool 196 197 // Convenience vars for EnabledState values. 198 var ( 199 iTrue = true 200 iFalse = false 201 202 Enabled EnabledState = &iTrue 203 Disabled EnabledState = &iFalse 204 ) 205 206 // IPVersion is a type for the possible IP address versions. Valid instances 207 // are IPv4 and IPv6 208 type IPVersion int 209 210 const ( 211 // IPv4 is used for IP version 4 addresses 212 IPv4 IPVersion = 4 213 // IPv6 is used for IP version 6 addresses 214 IPv6 IPVersion = 6 215 ) 216 217 // IntToPointer is a function for converting integers into integer pointers. 218 // This is useful when passing in options to operations. 219 func IntToPointer(i int) *int { 220 return &i 221 } 222 223 /* 224 MaybeString is an internal function to be used by request methods in individual 225 resource packages. 226 227 It takes a string that might be a zero value and returns either a pointer to its 228 address or nil. This is useful for allowing users to conveniently omit values 229 from an options struct by leaving them zeroed, but still pass nil to the JSON 230 serializer so they'll be omitted from the request body. 231 */ 232 func MaybeString(original string) *string { 233 if original != "" { 234 return &original 235 } 236 return nil 237 } 238 239 /* 240 MaybeInt is an internal function to be used by request methods in individual 241 resource packages. 242 243 Like MaybeString, it accepts an int that may or may not be a zero value, and 244 returns either a pointer to its address or nil. It's intended to hint that the 245 JSON serializer should omit its field. 246 */ 247 func MaybeInt(original int) *int { 248 if original != 0 { 249 return &original 250 } 251 return nil 252 } 253 254 /* 255 func isUnderlyingStructZero(v reflect.Value) bool { 256 switch v.Kind() { 257 case reflect.Ptr: 258 return isUnderlyingStructZero(v.Elem()) 259 default: 260 return isZero(v) 261 } 262 } 263 */ 264 265 var t time.Time 266 267 func isZero(v reflect.Value) bool { 268 //fmt.Printf("\n\nchecking isZero for value: %+v\n", v) 269 switch v.Kind() { 270 case reflect.Ptr: 271 if v.IsNil() { 272 return true 273 } 274 return false 275 case reflect.Func, reflect.Map, reflect.Slice: 276 return v.IsNil() 277 case reflect.Array: 278 z := true 279 for i := 0; i < v.Len(); i++ { 280 z = z && isZero(v.Index(i)) 281 } 282 return z 283 case reflect.Struct: 284 if v.Type() == reflect.TypeOf(t) { 285 if v.Interface().(time.Time).IsZero() { 286 return true 287 } 288 return false 289 } 290 z := true 291 for i := 0; i < v.NumField(); i++ { 292 z = z && isZero(v.Field(i)) 293 } 294 return z 295 } 296 // Compare other types directly: 297 z := reflect.Zero(v.Type()) 298 //fmt.Printf("zero type for value: %+v\n\n\n", z) 299 return v.Interface() == z.Interface() 300 } 301 302 /* 303 BuildQueryString is an internal function to be used by request methods in 304 individual resource packages. 305 306 It accepts a tagged structure and expands it into a URL struct. Field names are 307 converted into query parameters based on a "q" tag. For example: 308 309 type struct Something { 310 Bar string `q:"x_bar"` 311 Baz int `q:"lorem_ipsum"` 312 } 313 314 instance := Something{ 315 Bar: "AAA", 316 Baz: "BBB", 317 } 318 319 will be converted into "?x_bar=AAA&lorem_ipsum=BBB". 320 321 The struct's fields may be strings, integers, slices, or boolean values. Fields 322 left at their type's zero value will be omitted from the query. 323 324 Slice are handled in one of two ways: 325 326 type struct Something { 327 Bar []string `q:"bar"` // E.g. ?bar=1&bar=2 328 Baz []int `q:"baz" format="comma-separated"` // E.g. ?baz=1,2 329 } 330 */ 331 func BuildQueryString(opts interface{}) (*url.URL, error) { 332 optsValue := reflect.ValueOf(opts) 333 if optsValue.Kind() == reflect.Ptr { 334 optsValue = optsValue.Elem() 335 } 336 337 optsType := reflect.TypeOf(opts) 338 if optsType.Kind() == reflect.Ptr { 339 optsType = optsType.Elem() 340 } 341 342 params := url.Values{} 343 344 if optsValue.Kind() == reflect.Struct { 345 for i := 0; i < optsValue.NumField(); i++ { 346 v := optsValue.Field(i) 347 f := optsType.Field(i) 348 qTag := f.Tag.Get("q") 349 350 // if the field has a 'q' tag, it goes in the query string 351 if qTag != "" { 352 tags := strings.Split(qTag, ",") 353 354 // if the field is set, add it to the slice of query pieces 355 if !isZero(v) { 356 loop: 357 switch v.Kind() { 358 case reflect.Ptr: 359 v = v.Elem() 360 goto loop 361 case reflect.String: 362 params.Add(tags[0], v.String()) 363 case reflect.Int: 364 params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) 365 case reflect.Bool: 366 params.Add(tags[0], strconv.FormatBool(v.Bool())) 367 case reflect.Slice: 368 var values []string 369 switch v.Type().Elem() { 370 case reflect.TypeOf(0): 371 for i := 0; i < v.Len(); i++ { 372 values = append(values, strconv.FormatInt(v.Index(i).Int(), 10)) 373 } 374 default: 375 for i := 0; i < v.Len(); i++ { 376 values = append(values, v.Index(i).String()) 377 } 378 } 379 if sliceFormat := f.Tag.Get("format"); sliceFormat == "comma-separated" { 380 params.Add(tags[0], strings.Join(values, ",")) 381 } else { 382 params[tags[0]] = append(params[tags[0]], values...) 383 } 384 case reflect.Map: 385 if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { 386 var s []string 387 for _, k := range v.MapKeys() { 388 value := v.MapIndex(k).String() 389 s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) 390 } 391 params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) 392 } 393 } 394 } else { 395 // if the field has a 'required' tag, it can't have a zero-value 396 if requiredTag := f.Tag.Get("required"); requiredTag == "true" { 397 return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) 398 } 399 } 400 } 401 } 402 403 return &url.URL{RawQuery: params.Encode()}, nil 404 } 405 // Return an error if the underlying type of 'opts' isn't a struct. 406 return nil, fmt.Errorf("Options type is not a struct.") 407 } 408 409 /* 410 BuildHeaders is an internal function to be used by request methods in 411 individual resource packages. 412 413 It accepts an arbitrary tagged structure and produces a string map that's 414 suitable for use as the HTTP headers of an outgoing request. Field names are 415 mapped to header names based in "h" tags. 416 417 type struct Something { 418 Bar string `h:"x_bar"` 419 Baz int `h:"lorem_ipsum"` 420 } 421 422 instance := Something{ 423 Bar: "AAA", 424 Baz: "BBB", 425 } 426 427 will be converted into: 428 429 map[string]string{ 430 "x_bar": "AAA", 431 "lorem_ipsum": "BBB", 432 } 433 434 Untagged fields and fields left at their zero values are skipped. Integers, 435 booleans and string values are supported. 436 */ 437 func BuildHeaders(opts interface{}) (map[string]string, error) { 438 optsValue := reflect.ValueOf(opts) 439 if optsValue.Kind() == reflect.Ptr { 440 optsValue = optsValue.Elem() 441 } 442 443 optsType := reflect.TypeOf(opts) 444 if optsType.Kind() == reflect.Ptr { 445 optsType = optsType.Elem() 446 } 447 448 optsMap := make(map[string]string) 449 if optsValue.Kind() == reflect.Struct { 450 for i := 0; i < optsValue.NumField(); i++ { 451 v := optsValue.Field(i) 452 f := optsType.Field(i) 453 hTag := f.Tag.Get("h") 454 455 // if the field has a 'h' tag, it goes in the header 456 if hTag != "" { 457 tags := strings.Split(hTag, ",") 458 459 // if the field is set, add it to the slice of query pieces 460 if !isZero(v) { 461 if v.Kind() == reflect.Ptr { 462 v = v.Elem() 463 } 464 switch v.Kind() { 465 case reflect.String: 466 optsMap[tags[0]] = v.String() 467 case reflect.Int: 468 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) 469 case reflect.Int64: 470 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) 471 case reflect.Bool: 472 optsMap[tags[0]] = strconv.FormatBool(v.Bool()) 473 } 474 } else { 475 // if the field has a 'required' tag, it can't have a zero-value 476 if requiredTag := f.Tag.Get("required"); requiredTag == "true" { 477 return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) 478 } 479 } 480 } 481 482 } 483 return optsMap, nil 484 } 485 // Return an error if the underlying type of 'opts' isn't a struct. 486 return optsMap, fmt.Errorf("Options type is not a struct.") 487 } 488 489 // IDSliceToQueryString takes a slice of elements and converts them into a query 490 // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the 491 // result would be `?name=20&name=40&name=60' 492 func IDSliceToQueryString(name string, ids []int) string { 493 str := "" 494 for k, v := range ids { 495 if k == 0 { 496 str += "?" 497 } else { 498 str += "&" 499 } 500 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) 501 } 502 return str 503 } 504 505 // IntWithinRange returns TRUE if an integer falls within a defined range, and 506 // FALSE if not. 507 func IntWithinRange(val, min, max int) bool { 508 return val > min && val < max 509 }