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:
19 createOpts := flavors.CreateOpts{
28 body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
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
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()
41 optsType := reflect.TypeOf(opts)
42 if optsType.Kind() == reflect.Ptr {
43 optsType = optsType.Elem()
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)
53 if f.Name != strings.Title(f.Name) {
54 //fmt.Printf("Skipping field: %s...\n", f.Name)
58 //fmt.Printf("Starting on field: %s...\n", f.Name)
61 //fmt.Printf("v is zero?: %v\n", zero)
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
68 // if the field has a 'required' tag, it can't have a zero-value
69 err := ErrMissingInput{}
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()) {
82 if xorField.Kind() == reflect.Ptr {
83 xorField = xorField.Elem()
85 xorFieldIsZero = isZero(xorField)
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)
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)
99 orField := optsValue.FieldByName(orTag)
100 var orFieldIsZero bool
101 if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
104 if orField.Kind() == reflect.Ptr {
105 orField = orField.Elem()
107 orFieldIsZero = isZero(orField)
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)
118 jsonTag := f.Tag.Get("json")
123 if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
125 if sliceValue.Kind() == reflect.Ptr {
126 sliceValue = sliceValue.Elem()
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(), "")
139 if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
141 //fmt.Printf("value before change: %+v\n", optsValue.Field(i))
143 jsonTagPieces := strings.Split(jsonTag, ",")
144 if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
147 if v.Kind() == reflect.Ptr {
148 v.Set(reflect.Zero(v.Type()))
151 //fmt.Printf("value after change: %+v\n", optsValue.Field(i))
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)
166 //fmt.Printf("opts: %+v \n", opts)
168 b, err := json.Marshal(opts)
173 //fmt.Printf("string(b): %s\n", string(b))
175 err = json.Unmarshal(b, &optsMap)
180 //fmt.Printf("optsMap: %+v\n", optsMap)
183 optsMap = map[string]interface{}{parent: optsMap}
185 //fmt.Printf("optsMap after parent added: %+v\n", optsMap)
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.")
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
197 // Convenience vars for EnabledState values.
202 Enabled EnabledState = &iTrue
203 Disabled EnabledState = &iFalse
206 // IPVersion is a type for the possible IP address versions. Valid instances
211 // IPv4 is used for IP version 4 addresses
213 // IPv6 is used for IP version 6 addresses
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 {
224 MaybeString is an internal function to be used by request methods in individual
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.
232 func MaybeString(original string) *string {
240 MaybeInt is an internal function to be used by request methods in individual
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.
247 func MaybeInt(original int) *int {
255 func isUnderlyingStructZero(v reflect.Value) bool {
258 return isUnderlyingStructZero(v.Elem())
267 func isZero(v reflect.Value) bool {
268 //fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
275 case reflect.Func, reflect.Map, reflect.Slice:
279 for i := 0; i < v.Len(); i++ {
280 z = z && isZero(v.Index(i))
284 if v.Type() == reflect.TypeOf(t) {
285 if v.Interface().(time.Time).IsZero() {
291 for i := 0; i < v.NumField(); i++ {
292 z = z && isZero(v.Field(i))
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()
303 BuildQueryString is an internal function to be used by request methods in
304 individual resource packages.
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:
309 type struct Something {
310 Bar string `q:"x_bar"`
311 Baz int `q:"lorem_ipsum"`
314 instance := Something{
319 will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
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.
324 func BuildQueryString(opts interface{}) (*url.URL, error) {
325 optsValue := reflect.ValueOf(opts)
326 if optsValue.Kind() == reflect.Ptr {
327 optsValue = optsValue.Elem()
330 optsType := reflect.TypeOf(opts)
331 if optsType.Kind() == reflect.Ptr {
332 optsType = optsType.Elem()
335 params := url.Values{}
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")
343 // if the field has a 'q' tag, it goes in the query string
345 tags := strings.Split(qTag, ",")
347 // if the field is set, add it to the slice of query pieces
355 params.Add(tags[0], v.String())
357 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
359 params.Add(tags[0], strconv.FormatBool(v.Bool()))
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))
367 for i := 0; i < v.Len(); i++ {
368 params.Add(tags[0], v.Index(i).String())
372 if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
374 for _, k := range v.MapKeys() {
375 value := v.MapIndex(k).String()
376 s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
378 params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
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)
390 return &url.URL{RawQuery: params.Encode()}, nil
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.")
397 BuildHeaders is an internal function to be used by request methods in
398 individual resource packages.
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.
404 type struct Something {
405 Bar string `h:"x_bar"`
406 Baz int `h:"lorem_ipsum"`
409 instance := Something{
414 will be converted into:
418 "lorem_ipsum": "BBB",
421 Untagged fields and fields left at their zero values are skipped. Integers,
422 booleans and string values are supported.
424 func BuildHeaders(opts interface{}) (map[string]string, error) {
425 optsValue := reflect.ValueOf(opts)
426 if optsValue.Kind() == reflect.Ptr {
427 optsValue = optsValue.Elem()
430 optsType := reflect.TypeOf(opts)
431 if optsType.Kind() == reflect.Ptr {
432 optsType = optsType.Elem()
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")
442 // if the field has a 'h' tag, it goes in the header
444 tags := strings.Split(hTag, ",")
446 // if the field is set, add it to the slice of query pieces
450 optsMap[tags[0]] = v.String()
452 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
454 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
457 // if the field has a 'required' tag, it can't have a zero-value
458 if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
459 return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
467 // Return an error if the underlying type of 'opts' isn't a struct.
468 return optsMap, fmt.Errorf("Options type is not a struct.")
471 // IDSliceToQueryString takes a slice of elements and converts them into a query
472 // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
473 // result would be `?name=20&name=40&name=60'
474 func IDSliceToQueryString(name string, ids []int) string {
476 for k, v := range ids {
482 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
487 // IntWithinRange returns TRUE if an integer falls within a defined range, and
489 func IntWithinRange(val, min, max int) bool {
490 return val > min && val < max