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