blob: 5e7f5d0c06036bb93b04f07ddde8743ec2a5a858 [file] [log] [blame]
Jamie Hannaford6abf9282014-09-24 10:54:13 +02001package gophercloud
2
Jon Perrittf90a43c2014-09-28 20:09:46 -05003import (
Jon Perritt01618ee2016-03-09 03:04:06 -06004 "encoding/json"
Jon Perrittf90a43c2014-09-28 20:09:46 -05005 "fmt"
6 "net/url"
7 "reflect"
8 "strconv"
9 "strings"
10 "time"
11)
12
Jon Perrittdb0ae142016-03-13 00:33:41 -060013// BuildRequestBody builds a map[string]interface from the given `struct`. If
14// parent is not the empty string, the final map[string]interface returned will
15// encapsulate the built one
Jon Perritt01618ee2016-03-09 03:04:06 -060016//
Jon Perrittdb0ae142016-03-13 00:33:41 -060017func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
Jon Perritt01618ee2016-03-09 03:04:06 -060018 optsValue := reflect.ValueOf(opts)
19 if optsValue.Kind() == reflect.Ptr {
20 optsValue = optsValue.Elem()
21 }
22
23 optsType := reflect.TypeOf(opts)
24 if optsType.Kind() == reflect.Ptr {
25 optsType = optsType.Elem()
26 }
27
28 optsMap := make(map[string]interface{})
29 if optsValue.Kind() == reflect.Struct {
Jon Perrittdb0ae142016-03-13 00:33:41 -060030 //fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
Jon Perritt01618ee2016-03-09 03:04:06 -060031 for i := 0; i < optsValue.NumField(); i++ {
32 v := optsValue.Field(i)
33 f := optsType.Field(i)
Jon Perritt01618ee2016-03-09 03:04:06 -060034
Jon Perrittdb0ae142016-03-13 00:33:41 -060035 fmt.Printf("Starting on field: %s...\n", f.Name)
36
37 zero := isZero(v)
38 fmt.Printf("v is zero?: %v\n", zero)
39
40 // if there are 0 tags or if there is only 1 and it's the json tag,
41 // we don't need to do anything for this field
42 //if len(strings.Split(string(f.Tag), " ")) < 2 && f.Tag.Get("json") != "" && zero {
43 // fmt.Printf("skipping field: %s with tag: %+v\n", f.Name, f.Tag)
44 // continue
45 //}
46
47 // if the field has a required tag that's set to "true"
48 if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
49 fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
50 // if the field's value is zero, return a missing-argument error
51 if zero {
52 // if the field has a 'required' tag, it can't have a zero-value
53 err := ErrMissingInput{}
54 err.Argument = f.Name
55 return nil, err
56 }
57 }
58
59 if xorTag := f.Tag.Get("xor"); xorTag != "" {
60 fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
61 xorField := optsValue.FieldByName(xorTag)
62 var xorFieldIsZero bool
63 if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
64 xorFieldIsZero = true
65 } else {
66 if xorField.Kind() == reflect.Ptr {
67 xorField = xorField.Elem()
68 }
69 xorFieldIsZero = isZero(xorField)
70 }
71 if !(zero != xorFieldIsZero) {
72 err := ErrMissingInput{}
73 err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
74 err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
75 return nil, err
76 }
77 }
78
79 if orTag := f.Tag.Get("or"); orTag != "" {
80 fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
81 fmt.Printf("field is zero?: %v\n", zero)
82 if zero {
83 orField := optsValue.FieldByName(orTag)
84 var orFieldIsZero bool
85 if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
86 orFieldIsZero = true
87 } else {
88 if orField.Kind() == reflect.Ptr {
89 orField = orField.Elem()
90 }
91 orFieldIsZero = isZero(orField)
92 }
93 if orFieldIsZero {
94 err := ErrMissingInput{}
95 err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
96 err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
97 return nil, err
98 }
99 }
100 }
101
102 if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
103 if zero {
104 fmt.Printf("value before change: %+v\n", optsValue.Field(i))
105 if jsonTag := f.Tag.Get("json"); jsonTag != "" {
106 jsonTagPieces := strings.Split(jsonTag, ",")
107 if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
108 if v.CanSet() {
109 if !v.IsNil() {
110 if v.Kind() == reflect.Ptr {
111 v.Set(reflect.Zero(v.Type()))
112 }
113 }
114 fmt.Printf("value after change: %+v\n", optsValue.Field(i))
115 }
116 }
117 }
118 continue
119 }
120
121 fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
122 _, err := BuildRequestBody(v.Interface(), f.Name)
123 if err != nil {
124 return nil, err
125 }
Jon Perritt01618ee2016-03-09 03:04:06 -0600126 }
127 }
128
Jon Perrittdb0ae142016-03-13 00:33:41 -0600129 fmt.Printf("opts: %+v \n", opts)
130
Jon Perritt01618ee2016-03-09 03:04:06 -0600131 b, err := json.Marshal(opts)
132 if err != nil {
133 return nil, err
134 }
135
Jon Perrittdb0ae142016-03-13 00:33:41 -0600136 fmt.Printf("string(b): %s\n", string(b))
137
Jon Perritt01618ee2016-03-09 03:04:06 -0600138 err = json.Unmarshal(b, &optsMap)
139 if err != nil {
140 return nil, err
141 }
142
Jon Perrittdb0ae142016-03-13 00:33:41 -0600143 //fmt.Printf("optsMap: %+v\n", optsMap)
144
145 if parent != "" {
146 optsMap = map[string]interface{}{parent: optsMap}
147 }
148 //fmt.Printf("optsMap after parent added: %+v\n", optsMap)
Jon Perritt01618ee2016-03-09 03:04:06 -0600149 return optsMap, nil
150 }
151 // Return an error if the underlying type of 'opts' isn't a struct.
152 return nil, fmt.Errorf("Options type is not a struct.")
jrperrittb1013232016-02-10 19:01:53 -0600153}
154
Jamie Hannafordd1e6e762014-11-06 14:26:31 +0100155// EnabledState is a convenience type, mostly used in Create and Update
156// operations. Because the zero value of a bool is FALSE, we need to use a
157// pointer instead to indicate zero-ness.
158type EnabledState *bool
159
160// Convenience vars for EnabledState values.
161var (
162 iTrue = true
163 iFalse = false
164
165 Enabled EnabledState = &iTrue
166 Disabled EnabledState = &iFalse
167)
168
Jamie Hannaford7fa27892014-11-07 15:11:20 +0100169// IntToPointer is a function for converting integers into integer pointers.
170// This is useful when passing in options to operations.
171func IntToPointer(i int) *int {
172 return &i
173}
174
Ash Wilson0735acb2014-10-31 14:18:00 -0400175/*
176MaybeString is an internal function to be used by request methods in individual
177resource packages.
178
179It takes a string that might be a zero value and returns either a pointer to its
180address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400181from an options struct by leaving them zeroed, but still pass nil to the JSON
182serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -0400183*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +0200184func MaybeString(original string) *string {
185 if original != "" {
186 return &original
187 }
188 return nil
189}
Jon Perrittf90a43c2014-09-28 20:09:46 -0500190
Ash Wilson0735acb2014-10-31 14:18:00 -0400191/*
192MaybeInt is an internal function to be used by request methods in individual
193resource packages.
194
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400195Like MaybeString, it accepts an int that may or may not be a zero value, and
196returns either a pointer to its address or nil. It's intended to hint that the
197JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -0400198*/
Jon Perritt8d262582014-10-03 11:11:46 -0500199func MaybeInt(original int) *int {
200 if original != 0 {
201 return &original
202 }
203 return nil
204}
205
Jon Perrittdb0ae142016-03-13 00:33:41 -0600206/*
207func isUnderlyingStructZero(v reflect.Value) bool {
208 switch v.Kind() {
209 case reflect.Ptr:
210 return isUnderlyingStructZero(v.Elem())
211 default:
212 return isZero(v)
213 }
214}
215*/
216
Jon Perrittf90a43c2014-09-28 20:09:46 -0500217var t time.Time
218
219func isZero(v reflect.Value) bool {
220 switch v.Kind() {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600221 case reflect.Ptr:
222 if v.IsNil() {
223 return true
224 }
225 return isZero(v.Elem())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500226 case reflect.Func, reflect.Map, reflect.Slice:
227 return v.IsNil()
228 case reflect.Array:
229 z := true
230 for i := 0; i < v.Len(); i++ {
231 z = z && isZero(v.Index(i))
232 }
233 return z
234 case reflect.Struct:
235 if v.Type() == reflect.TypeOf(t) {
236 if v.Interface().(time.Time).IsZero() {
237 return true
238 }
239 return false
240 }
241 z := true
242 for i := 0; i < v.NumField(); i++ {
243 z = z && isZero(v.Field(i))
244 }
245 return z
246 }
247 // Compare other types directly:
248 z := reflect.Zero(v.Type())
249 return v.Interface() == z.Interface()
250}
251
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200252/*
Ash Wilson0735acb2014-10-31 14:18:00 -0400253BuildQueryString is an internal function to be used by request methods in
254individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200255
Ash Wilson0735acb2014-10-31 14:18:00 -0400256It accepts a tagged structure and expands it into a URL struct. Field names are
257converted into query parameters based on a "q" tag. For example:
258
259 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200260 Bar string `q:"x_bar"`
261 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200262 }
263
Ash Wilson0735acb2014-10-31 14:18:00 -0400264 instance := Something{
265 Bar: "AAA",
266 Baz: "BBB",
267 }
268
269will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
270
271The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700272their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200273*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500274func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500275 optsValue := reflect.ValueOf(opts)
276 if optsValue.Kind() == reflect.Ptr {
277 optsValue = optsValue.Elem()
278 }
279
280 optsType := reflect.TypeOf(opts)
281 if optsType.Kind() == reflect.Ptr {
282 optsType = optsType.Elem()
283 }
284
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100285 params := url.Values{}
286
Jon Perrittf90a43c2014-09-28 20:09:46 -0500287 if optsValue.Kind() == reflect.Struct {
288 for i := 0; i < optsValue.NumField(); i++ {
289 v := optsValue.Field(i)
290 f := optsType.Field(i)
291 qTag := f.Tag.Get("q")
292
293 // if the field has a 'q' tag, it goes in the query string
294 if qTag != "" {
295 tags := strings.Split(qTag, ",")
296
297 // if the field is set, add it to the slice of query pieces
298 if !isZero(v) {
299 switch v.Kind() {
300 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100301 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500302 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100303 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500304 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100305 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700306 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700307 switch v.Type().Elem() {
308 case reflect.TypeOf(0):
309 for i := 0; i < v.Len(); i++ {
310 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
311 }
312 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700313 for i := 0; i < v.Len(); i++ {
314 params.Add(tags[0], v.Index(i).String())
315 }
316 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500317 }
318 } else {
319 // Otherwise, the field is not set.
320 if len(tags) == 2 && tags[1] == "required" {
321 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500322 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500323 }
324 }
325 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100326 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500327
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100328 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500329 }
330 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500331 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500332}
333
Ash Wilson0735acb2014-10-31 14:18:00 -0400334/*
335BuildHeaders is an internal function to be used by request methods in
336individual resource packages.
337
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700338It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400339suitable for use as the HTTP headers of an outgoing request. Field names are
340mapped to header names based in "h" tags.
341
342 type struct Something {
343 Bar string `h:"x_bar"`
344 Baz int `h:"lorem_ipsum"`
345 }
346
347 instance := Something{
348 Bar: "AAA",
349 Baz: "BBB",
350 }
351
352will be converted into:
353
354 map[string]string{
355 "x_bar": "AAA",
356 "lorem_ipsum": "BBB",
357 }
358
359Untagged fields and fields left at their zero values are skipped. Integers,
360booleans and string values are supported.
361*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500362func BuildHeaders(opts interface{}) (map[string]string, error) {
363 optsValue := reflect.ValueOf(opts)
364 if optsValue.Kind() == reflect.Ptr {
365 optsValue = optsValue.Elem()
366 }
367
368 optsType := reflect.TypeOf(opts)
369 if optsType.Kind() == reflect.Ptr {
370 optsType = optsType.Elem()
371 }
372
373 optsMap := make(map[string]string)
374 if optsValue.Kind() == reflect.Struct {
375 for i := 0; i < optsValue.NumField(); i++ {
376 v := optsValue.Field(i)
377 f := optsType.Field(i)
378 hTag := f.Tag.Get("h")
379
380 // if the field has a 'h' tag, it goes in the header
381 if hTag != "" {
382 tags := strings.Split(hTag, ",")
383
384 // if the field is set, add it to the slice of query pieces
385 if !isZero(v) {
386 switch v.Kind() {
387 case reflect.String:
388 optsMap[tags[0]] = v.String()
389 case reflect.Int:
390 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
391 case reflect.Bool:
392 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
393 }
394 } else {
395 // Otherwise, the field is not set.
396 if len(tags) == 2 && tags[1] == "required" {
397 // And the field is required. Return an error.
398 return optsMap, fmt.Errorf("Required header not set.")
399 }
400 }
401 }
402
403 }
404 return optsMap, nil
405 }
406 // Return an error if the underlying type of 'opts' isn't a struct.
407 return optsMap, fmt.Errorf("Options type is not a struct.")
408}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100409
410// IDSliceToQueryString takes a slice of elements and converts them into a query
411// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
412// result would be `?name=20&name=40&name=60'
413func IDSliceToQueryString(name string, ids []int) string {
414 str := ""
415 for k, v := range ids {
416 if k == 0 {
417 str += "?"
418 } else {
419 str += "&"
420 }
421 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
422 }
423 return str
424}
425
426// IntWithinRange returns TRUE if an integer falls within a defined range, and
427// FALSE if not.
428func IntWithinRange(val, min, max int) bool {
429 return val > min && val < max
430}