blob: 33415848d15f67391e70554ff9e68e533fb231c3 [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
Jon Perritte1c6ceb2016-03-14 12:09:36 -0500169// IPVersion is a type for the possible IP address versions. Valid instances
170// are IPv4 and IPv6
171type IPVersion int
172
173const (
174 // IPv4 is used for IP version 4 addresses
175 IPv4 IPVersion = 4
176 // IPv6 is used for IP version 6 addresses
177 IPv6 IPVersion = 6
178)
179
Jamie Hannaford7fa27892014-11-07 15:11:20 +0100180// IntToPointer is a function for converting integers into integer pointers.
181// This is useful when passing in options to operations.
182func IntToPointer(i int) *int {
183 return &i
184}
185
Ash Wilson0735acb2014-10-31 14:18:00 -0400186/*
187MaybeString is an internal function to be used by request methods in individual
188resource packages.
189
190It takes a string that might be a zero value and returns either a pointer to its
191address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400192from an options struct by leaving them zeroed, but still pass nil to the JSON
193serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -0400194*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +0200195func MaybeString(original string) *string {
196 if original != "" {
197 return &original
198 }
199 return nil
200}
Jon Perrittf90a43c2014-09-28 20:09:46 -0500201
Ash Wilson0735acb2014-10-31 14:18:00 -0400202/*
203MaybeInt is an internal function to be used by request methods in individual
204resource packages.
205
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400206Like MaybeString, it accepts an int that may or may not be a zero value, and
207returns either a pointer to its address or nil. It's intended to hint that the
208JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -0400209*/
Jon Perritt8d262582014-10-03 11:11:46 -0500210func MaybeInt(original int) *int {
211 if original != 0 {
212 return &original
213 }
214 return nil
215}
216
Jon Perrittdb0ae142016-03-13 00:33:41 -0600217/*
218func isUnderlyingStructZero(v reflect.Value) bool {
219 switch v.Kind() {
220 case reflect.Ptr:
221 return isUnderlyingStructZero(v.Elem())
222 default:
223 return isZero(v)
224 }
225}
226*/
227
Jon Perrittf90a43c2014-09-28 20:09:46 -0500228var t time.Time
229
230func isZero(v reflect.Value) bool {
231 switch v.Kind() {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600232 case reflect.Ptr:
233 if v.IsNil() {
234 return true
235 }
236 return isZero(v.Elem())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500237 case reflect.Func, reflect.Map, reflect.Slice:
238 return v.IsNil()
239 case reflect.Array:
240 z := true
241 for i := 0; i < v.Len(); i++ {
242 z = z && isZero(v.Index(i))
243 }
244 return z
245 case reflect.Struct:
246 if v.Type() == reflect.TypeOf(t) {
247 if v.Interface().(time.Time).IsZero() {
248 return true
249 }
250 return false
251 }
252 z := true
253 for i := 0; i < v.NumField(); i++ {
254 z = z && isZero(v.Field(i))
255 }
256 return z
257 }
258 // Compare other types directly:
259 z := reflect.Zero(v.Type())
260 return v.Interface() == z.Interface()
261}
262
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200263/*
Ash Wilson0735acb2014-10-31 14:18:00 -0400264BuildQueryString is an internal function to be used by request methods in
265individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200266
Ash Wilson0735acb2014-10-31 14:18:00 -0400267It accepts a tagged structure and expands it into a URL struct. Field names are
268converted into query parameters based on a "q" tag. For example:
269
270 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200271 Bar string `q:"x_bar"`
272 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200273 }
274
Ash Wilson0735acb2014-10-31 14:18:00 -0400275 instance := Something{
276 Bar: "AAA",
277 Baz: "BBB",
278 }
279
280will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
281
282The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700283their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200284*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500285func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500286 optsValue := reflect.ValueOf(opts)
287 if optsValue.Kind() == reflect.Ptr {
288 optsValue = optsValue.Elem()
289 }
290
291 optsType := reflect.TypeOf(opts)
292 if optsType.Kind() == reflect.Ptr {
293 optsType = optsType.Elem()
294 }
295
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100296 params := url.Values{}
297
Jon Perrittf90a43c2014-09-28 20:09:46 -0500298 if optsValue.Kind() == reflect.Struct {
299 for i := 0; i < optsValue.NumField(); i++ {
300 v := optsValue.Field(i)
301 f := optsType.Field(i)
302 qTag := f.Tag.Get("q")
303
304 // if the field has a 'q' tag, it goes in the query string
305 if qTag != "" {
306 tags := strings.Split(qTag, ",")
307
308 // if the field is set, add it to the slice of query pieces
309 if !isZero(v) {
310 switch v.Kind() {
311 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100312 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500313 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100314 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500315 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100316 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700317 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700318 switch v.Type().Elem() {
319 case reflect.TypeOf(0):
320 for i := 0; i < v.Len(); i++ {
321 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
322 }
323 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700324 for i := 0; i < v.Len(); i++ {
325 params.Add(tags[0], v.Index(i).String())
326 }
327 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500328 }
329 } else {
330 // Otherwise, the field is not set.
331 if len(tags) == 2 && tags[1] == "required" {
332 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500333 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500334 }
335 }
336 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100337 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500338
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100339 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500340 }
341 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500342 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500343}
344
Ash Wilson0735acb2014-10-31 14:18:00 -0400345/*
346BuildHeaders is an internal function to be used by request methods in
347individual resource packages.
348
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700349It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400350suitable for use as the HTTP headers of an outgoing request. Field names are
351mapped to header names based in "h" tags.
352
353 type struct Something {
354 Bar string `h:"x_bar"`
355 Baz int `h:"lorem_ipsum"`
356 }
357
358 instance := Something{
359 Bar: "AAA",
360 Baz: "BBB",
361 }
362
363will be converted into:
364
365 map[string]string{
366 "x_bar": "AAA",
367 "lorem_ipsum": "BBB",
368 }
369
370Untagged fields and fields left at their zero values are skipped. Integers,
371booleans and string values are supported.
372*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500373func BuildHeaders(opts interface{}) (map[string]string, error) {
374 optsValue := reflect.ValueOf(opts)
375 if optsValue.Kind() == reflect.Ptr {
376 optsValue = optsValue.Elem()
377 }
378
379 optsType := reflect.TypeOf(opts)
380 if optsType.Kind() == reflect.Ptr {
381 optsType = optsType.Elem()
382 }
383
384 optsMap := make(map[string]string)
385 if optsValue.Kind() == reflect.Struct {
386 for i := 0; i < optsValue.NumField(); i++ {
387 v := optsValue.Field(i)
388 f := optsType.Field(i)
389 hTag := f.Tag.Get("h")
390
391 // if the field has a 'h' tag, it goes in the header
392 if hTag != "" {
393 tags := strings.Split(hTag, ",")
394
395 // if the field is set, add it to the slice of query pieces
396 if !isZero(v) {
397 switch v.Kind() {
398 case reflect.String:
399 optsMap[tags[0]] = v.String()
400 case reflect.Int:
401 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
402 case reflect.Bool:
403 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
404 }
405 } else {
406 // Otherwise, the field is not set.
407 if len(tags) == 2 && tags[1] == "required" {
408 // And the field is required. Return an error.
409 return optsMap, fmt.Errorf("Required header not set.")
410 }
411 }
412 }
413
414 }
415 return optsMap, nil
416 }
417 // Return an error if the underlying type of 'opts' isn't a struct.
418 return optsMap, fmt.Errorf("Options type is not a struct.")
419}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100420
421// IDSliceToQueryString takes a slice of elements and converts them into a query
422// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
423// result would be `?name=20&name=40&name=60'
424func IDSliceToQueryString(name string, ids []int) string {
425 str := ""
426 for k, v := range ids {
427 if k == 0 {
428 str += "?"
429 } else {
430 str += "&"
431 }
432 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
433 }
434 return str
435}
436
437// IntWithinRange returns TRUE if an integer falls within a defined range, and
438// FALSE if not.
439func IntWithinRange(val, min, max int) bool {
440 return val > min && val < max
441}