github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/params.go (about) 1 package golangsdk 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 := golangsdk.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, or boolean values. Fields left at 322 their type's zero value will be omitted from the query. 323 */ 324 func BuildQueryString(opts interface{}) (*url.URL, error) { 325 optsValue := reflect.ValueOf(opts) 326 if optsValue.Kind() == reflect.Ptr { 327 optsValue = optsValue.Elem() 328 } 329 330 optsType := reflect.TypeOf(opts) 331 if optsType.Kind() == reflect.Ptr { 332 optsType = optsType.Elem() 333 } 334 335 params := url.Values{} 336 337 if optsValue.Kind() == reflect.Struct { 338 for i := 0; i < optsValue.NumField(); i++ { 339 v := optsValue.Field(i) 340 f := optsType.Field(i) 341 qTag := f.Tag.Get("q") 342 343 // if the field has a 'q' tag, it goes in the query string 344 if qTag != "" { 345 tags := strings.Split(qTag, ",") 346 347 // if the field is set, add it to the slice of query pieces 348 if !isZero(v) { 349 loop: 350 switch v.Kind() { 351 case reflect.Ptr: 352 v = v.Elem() 353 goto loop 354 case reflect.String: 355 params.Add(tags[0], v.String()) 356 case reflect.Int: 357 params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) 358 case reflect.Bool: 359 params.Add(tags[0], strconv.FormatBool(v.Bool())) 360 case reflect.Slice: 361 switch v.Type().Elem() { 362 case reflect.TypeOf(0): 363 for i := 0; i < v.Len(); i++ { 364 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10)) 365 } 366 default: 367 for i := 0; i < v.Len(); i++ { 368 params.Add(tags[0], v.Index(i).String()) 369 } 370 } 371 case reflect.Map: 372 if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String { 373 var s []string 374 for _, k := range v.MapKeys() { 375 value := v.MapIndex(k).String() 376 s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value)) 377 } 378 params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", "))) 379 } 380 } 381 } else { 382 // if the field has a 'required' tag, it can't have a zero-value 383 if requiredTag := f.Tag.Get("required"); requiredTag == "true" { 384 return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name) 385 } 386 } 387 } 388 } 389 390 return &url.URL{RawQuery: params.Encode()}, nil 391 } 392 // Return an error if the underlying type of 'opts' isn't a struct. 393 return nil, fmt.Errorf("Options type is not a struct.") 394 } 395 396 /* 397 BuildHeaders is an internal function to be used by request methods in 398 individual resource packages. 399 400 It accepts an arbitrary tagged structure and produces a string map that's 401 suitable for use as the HTTP headers of an outgoing request. Field names are 402 mapped to header names based in "h" tags. 403 404 type struct Something { 405 Bar string `h:"x_bar"` 406 Baz int `h:"lorem_ipsum"` 407 } 408 409 instance := Something{ 410 Bar: "AAA", 411 Baz: "BBB", 412 } 413 414 will be converted into: 415 416 map[string]string{ 417 "x_bar": "AAA", 418 "lorem_ipsum": "BBB", 419 } 420 421 Untagged fields and fields left at their zero values are skipped. Integers, 422 booleans and string values are supported. 423 */ 424 func BuildHeaders(opts interface{}) (map[string]string, error) { 425 optsValue := reflect.ValueOf(opts) 426 if optsValue.Kind() == reflect.Ptr { 427 optsValue = optsValue.Elem() 428 } 429 430 optsType := reflect.TypeOf(opts) 431 if optsType.Kind() == reflect.Ptr { 432 optsType = optsType.Elem() 433 } 434 435 optsMap := make(map[string]string) 436 if optsValue.Kind() == reflect.Struct { 437 for i := 0; i < optsValue.NumField(); i++ { 438 v := optsValue.Field(i) 439 f := optsType.Field(i) 440 hTag := f.Tag.Get("h") 441 442 // if the field has a 'h' tag, it goes in the header 443 if hTag != "" { 444 tags := strings.Split(hTag, ",") 445 446 // if the field is set, add it to the slice of query pieces 447 if !isZero(v) { 448 switch v.Kind() { 449 case reflect.String: 450 optsMap[tags[0]] = v.String() 451 case reflect.Int: 452 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) 453 case reflect.Int64: 454 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) 455 case reflect.Bool: 456 optsMap[tags[0]] = strconv.FormatBool(v.Bool()) 457 } 458 } else { 459 // if the field has a 'required' tag, it can't have a zero-value 460 if requiredTag := f.Tag.Get("required"); requiredTag == "true" { 461 return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name) 462 } 463 } 464 } 465 466 } 467 return optsMap, nil 468 } 469 // Return an error if the underlying type of 'opts' isn't a struct. 470 return optsMap, fmt.Errorf("Options type is not a struct.") 471 } 472 473 // IDSliceToQueryString takes a slice of elements and converts them into a query 474 // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the 475 // result would be `?name=20&name=40&name=60' 476 func IDSliceToQueryString(name string, ids []int) string { 477 str := "" 478 for k, v := range ids { 479 if k == 0 { 480 str += "?" 481 } else { 482 str += "&" 483 } 484 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) 485 } 486 return str 487 } 488 489 // IntWithinRange returns TRUE if an integer falls within a defined range, and 490 // FALSE if not. 491 func IntWithinRange(val, min, max int) bool { 492 return val > min && val < max 493 }