blob: 14b3e2894622a6beef597e0aa31375633c2914a5 [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 Perritt77f79d22016-03-15 06:54:33 -050035 if f.Name != strings.Title(f.Name) {
36 fmt.Printf("Skipping field: %s...\n", f.Name)
37 continue
38 }
39
Jon Perrittdb0ae142016-03-13 00:33:41 -060040 fmt.Printf("Starting on field: %s...\n", f.Name)
41
42 zero := isZero(v)
43 fmt.Printf("v is zero?: %v\n", zero)
44
45 // if there are 0 tags or if there is only 1 and it's the json tag,
46 // we don't need to do anything for this field
47 //if len(strings.Split(string(f.Tag), " ")) < 2 && f.Tag.Get("json") != "" && zero {
48 // fmt.Printf("skipping field: %s with tag: %+v\n", f.Name, f.Tag)
49 // continue
50 //}
51
52 // if the field has a required tag that's set to "true"
53 if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
Jon Perritt77f79d22016-03-15 06:54:33 -050054 //fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
Jon Perrittdb0ae142016-03-13 00:33:41 -060055 // if the field's value is zero, return a missing-argument error
56 if zero {
57 // if the field has a 'required' tag, it can't have a zero-value
58 err := ErrMissingInput{}
59 err.Argument = f.Name
60 return nil, err
61 }
62 }
63
64 if xorTag := f.Tag.Get("xor"); xorTag != "" {
Jon Perritt77f79d22016-03-15 06:54:33 -050065 //fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
Jon Perrittdb0ae142016-03-13 00:33:41 -060066 xorField := optsValue.FieldByName(xorTag)
67 var xorFieldIsZero bool
68 if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
69 xorFieldIsZero = true
70 } else {
71 if xorField.Kind() == reflect.Ptr {
72 xorField = xorField.Elem()
73 }
74 xorFieldIsZero = isZero(xorField)
75 }
76 if !(zero != xorFieldIsZero) {
77 err := ErrMissingInput{}
78 err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
79 err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
80 return nil, err
81 }
82 }
83
84 if orTag := f.Tag.Get("or"); orTag != "" {
Jon Perritt77f79d22016-03-15 06:54:33 -050085 //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
86 //fmt.Printf("field is zero?: %v\n", zero)
Jon Perrittdb0ae142016-03-13 00:33:41 -060087 if zero {
88 orField := optsValue.FieldByName(orTag)
89 var orFieldIsZero bool
90 if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
91 orFieldIsZero = true
92 } else {
93 if orField.Kind() == reflect.Ptr {
94 orField = orField.Elem()
95 }
96 orFieldIsZero = isZero(orField)
97 }
98 if orFieldIsZero {
99 err := ErrMissingInput{}
100 err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
101 err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
102 return nil, err
103 }
104 }
105 }
106
107 if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
108 if zero {
Jon Perritt77f79d22016-03-15 06:54:33 -0500109 //fmt.Printf("value before change: %+v\n", optsValue.Field(i))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600110 if jsonTag := f.Tag.Get("json"); jsonTag != "" {
111 jsonTagPieces := strings.Split(jsonTag, ",")
112 if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
113 if v.CanSet() {
114 if !v.IsNil() {
115 if v.Kind() == reflect.Ptr {
116 v.Set(reflect.Zero(v.Type()))
117 }
118 }
Jon Perritt77f79d22016-03-15 06:54:33 -0500119 //fmt.Printf("value after change: %+v\n", optsValue.Field(i))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600120 }
121 }
122 }
123 continue
124 }
125
Jon Perritt77f79d22016-03-15 06:54:33 -0500126 //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
Jon Perrittdb0ae142016-03-13 00:33:41 -0600127 _, err := BuildRequestBody(v.Interface(), f.Name)
128 if err != nil {
129 return nil, err
130 }
Jon Perritt01618ee2016-03-09 03:04:06 -0600131 }
132 }
133
Jon Perritt77f79d22016-03-15 06:54:33 -0500134 //fmt.Printf("opts: %+v \n", opts)
Jon Perrittdb0ae142016-03-13 00:33:41 -0600135
Jon Perritt01618ee2016-03-09 03:04:06 -0600136 b, err := json.Marshal(opts)
137 if err != nil {
138 return nil, err
139 }
140
Jon Perritt77f79d22016-03-15 06:54:33 -0500141 //fmt.Printf("string(b): %s\n", string(b))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600142
Jon Perritt01618ee2016-03-09 03:04:06 -0600143 err = json.Unmarshal(b, &optsMap)
144 if err != nil {
145 return nil, err
146 }
147
Jon Perrittdb0ae142016-03-13 00:33:41 -0600148 //fmt.Printf("optsMap: %+v\n", optsMap)
149
150 if parent != "" {
151 optsMap = map[string]interface{}{parent: optsMap}
152 }
153 //fmt.Printf("optsMap after parent added: %+v\n", optsMap)
Jon Perritt01618ee2016-03-09 03:04:06 -0600154 return optsMap, nil
155 }
156 // Return an error if the underlying type of 'opts' isn't a struct.
157 return nil, fmt.Errorf("Options type is not a struct.")
jrperrittb1013232016-02-10 19:01:53 -0600158}
159
Jamie Hannafordd1e6e762014-11-06 14:26:31 +0100160// EnabledState is a convenience type, mostly used in Create and Update
161// operations. Because the zero value of a bool is FALSE, we need to use a
162// pointer instead to indicate zero-ness.
163type EnabledState *bool
164
165// Convenience vars for EnabledState values.
166var (
167 iTrue = true
168 iFalse = false
169
170 Enabled EnabledState = &iTrue
171 Disabled EnabledState = &iFalse
172)
173
Jon Perritte1c6ceb2016-03-14 12:09:36 -0500174// IPVersion is a type for the possible IP address versions. Valid instances
175// are IPv4 and IPv6
176type IPVersion int
177
178const (
179 // IPv4 is used for IP version 4 addresses
180 IPv4 IPVersion = 4
181 // IPv6 is used for IP version 6 addresses
182 IPv6 IPVersion = 6
183)
184
Jamie Hannaford7fa27892014-11-07 15:11:20 +0100185// IntToPointer is a function for converting integers into integer pointers.
186// This is useful when passing in options to operations.
187func IntToPointer(i int) *int {
188 return &i
189}
190
Ash Wilson0735acb2014-10-31 14:18:00 -0400191/*
192MaybeString is an internal function to be used by request methods in individual
193resource packages.
194
195It takes a string that might be a zero value and returns either a pointer to its
196address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400197from an options struct by leaving them zeroed, but still pass nil to the JSON
198serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -0400199*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +0200200func MaybeString(original string) *string {
201 if original != "" {
202 return &original
203 }
204 return nil
205}
Jon Perrittf90a43c2014-09-28 20:09:46 -0500206
Ash Wilson0735acb2014-10-31 14:18:00 -0400207/*
208MaybeInt is an internal function to be used by request methods in individual
209resource packages.
210
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400211Like MaybeString, it accepts an int that may or may not be a zero value, and
212returns either a pointer to its address or nil. It's intended to hint that the
213JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -0400214*/
Jon Perritt8d262582014-10-03 11:11:46 -0500215func MaybeInt(original int) *int {
216 if original != 0 {
217 return &original
218 }
219 return nil
220}
221
Jon Perrittdb0ae142016-03-13 00:33:41 -0600222/*
223func isUnderlyingStructZero(v reflect.Value) bool {
224 switch v.Kind() {
225 case reflect.Ptr:
226 return isUnderlyingStructZero(v.Elem())
227 default:
228 return isZero(v)
229 }
230}
231*/
232
Jon Perrittf90a43c2014-09-28 20:09:46 -0500233var t time.Time
234
235func isZero(v reflect.Value) bool {
Jon Perritt77f79d22016-03-15 06:54:33 -0500236 fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500237 switch v.Kind() {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600238 case reflect.Ptr:
239 if v.IsNil() {
240 return true
241 }
242 return isZero(v.Elem())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500243 case reflect.Func, reflect.Map, reflect.Slice:
244 return v.IsNil()
245 case reflect.Array:
246 z := true
247 for i := 0; i < v.Len(); i++ {
248 z = z && isZero(v.Index(i))
249 }
250 return z
251 case reflect.Struct:
252 if v.Type() == reflect.TypeOf(t) {
253 if v.Interface().(time.Time).IsZero() {
254 return true
255 }
256 return false
257 }
258 z := true
259 for i := 0; i < v.NumField(); i++ {
260 z = z && isZero(v.Field(i))
261 }
262 return z
263 }
264 // Compare other types directly:
265 z := reflect.Zero(v.Type())
Jon Perritt77f79d22016-03-15 06:54:33 -0500266 fmt.Printf("zero type for value: %+v\n\n\n", z)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500267 return v.Interface() == z.Interface()
268}
269
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200270/*
Ash Wilson0735acb2014-10-31 14:18:00 -0400271BuildQueryString is an internal function to be used by request methods in
272individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200273
Ash Wilson0735acb2014-10-31 14:18:00 -0400274It accepts a tagged structure and expands it into a URL struct. Field names are
275converted into query parameters based on a "q" tag. For example:
276
277 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200278 Bar string `q:"x_bar"`
279 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200280 }
281
Ash Wilson0735acb2014-10-31 14:18:00 -0400282 instance := Something{
283 Bar: "AAA",
284 Baz: "BBB",
285 }
286
287will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
288
289The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700290their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200291*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500292func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500293 optsValue := reflect.ValueOf(opts)
294 if optsValue.Kind() == reflect.Ptr {
295 optsValue = optsValue.Elem()
296 }
297
298 optsType := reflect.TypeOf(opts)
299 if optsType.Kind() == reflect.Ptr {
300 optsType = optsType.Elem()
301 }
302
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100303 params := url.Values{}
304
Jon Perrittf90a43c2014-09-28 20:09:46 -0500305 if optsValue.Kind() == reflect.Struct {
306 for i := 0; i < optsValue.NumField(); i++ {
307 v := optsValue.Field(i)
308 f := optsType.Field(i)
309 qTag := f.Tag.Get("q")
310
311 // if the field has a 'q' tag, it goes in the query string
312 if qTag != "" {
313 tags := strings.Split(qTag, ",")
314
315 // if the field is set, add it to the slice of query pieces
316 if !isZero(v) {
317 switch v.Kind() {
318 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100319 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500320 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100321 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500322 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100323 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700324 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700325 switch v.Type().Elem() {
326 case reflect.TypeOf(0):
327 for i := 0; i < v.Len(); i++ {
328 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
329 }
330 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700331 for i := 0; i < v.Len(); i++ {
332 params.Add(tags[0], v.Index(i).String())
333 }
334 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500335 }
336 } else {
337 // Otherwise, the field is not set.
338 if len(tags) == 2 && tags[1] == "required" {
339 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500340 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500341 }
342 }
343 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100344 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500345
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100346 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500347 }
348 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500349 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500350}
351
Ash Wilson0735acb2014-10-31 14:18:00 -0400352/*
353BuildHeaders is an internal function to be used by request methods in
354individual resource packages.
355
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700356It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400357suitable for use as the HTTP headers of an outgoing request. Field names are
358mapped to header names based in "h" tags.
359
360 type struct Something {
361 Bar string `h:"x_bar"`
362 Baz int `h:"lorem_ipsum"`
363 }
364
365 instance := Something{
366 Bar: "AAA",
367 Baz: "BBB",
368 }
369
370will be converted into:
371
372 map[string]string{
373 "x_bar": "AAA",
374 "lorem_ipsum": "BBB",
375 }
376
377Untagged fields and fields left at their zero values are skipped. Integers,
378booleans and string values are supported.
379*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500380func BuildHeaders(opts interface{}) (map[string]string, error) {
381 optsValue := reflect.ValueOf(opts)
382 if optsValue.Kind() == reflect.Ptr {
383 optsValue = optsValue.Elem()
384 }
385
386 optsType := reflect.TypeOf(opts)
387 if optsType.Kind() == reflect.Ptr {
388 optsType = optsType.Elem()
389 }
390
391 optsMap := make(map[string]string)
392 if optsValue.Kind() == reflect.Struct {
393 for i := 0; i < optsValue.NumField(); i++ {
394 v := optsValue.Field(i)
395 f := optsType.Field(i)
396 hTag := f.Tag.Get("h")
397
398 // if the field has a 'h' tag, it goes in the header
399 if hTag != "" {
400 tags := strings.Split(hTag, ",")
401
402 // if the field is set, add it to the slice of query pieces
403 if !isZero(v) {
404 switch v.Kind() {
405 case reflect.String:
406 optsMap[tags[0]] = v.String()
407 case reflect.Int:
408 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
409 case reflect.Bool:
410 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
411 }
412 } else {
413 // Otherwise, the field is not set.
414 if len(tags) == 2 && tags[1] == "required" {
415 // And the field is required. Return an error.
416 return optsMap, fmt.Errorf("Required header not set.")
417 }
418 }
419 }
420
421 }
422 return optsMap, nil
423 }
424 // Return an error if the underlying type of 'opts' isn't a struct.
425 return optsMap, fmt.Errorf("Options type is not a struct.")
426}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100427
428// IDSliceToQueryString takes a slice of elements and converts them into a query
429// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
430// result would be `?name=20&name=40&name=60'
431func IDSliceToQueryString(name string, ids []int) string {
432 str := ""
433 for k, v := range ids {
434 if k == 0 {
435 str += "?"
436 } else {
437 str += "&"
438 }
439 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
440 }
441 return str
442}
443
444// IntWithinRange returns TRUE if an integer falls within a defined range, and
445// FALSE if not.
446func IntWithinRange(val, min, max int) bool {
447 return val > min && val < max
448}