blob: e484fe1c1ed0ae081125c1e6f7b0abb0baa46e77 [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) {
Jon Perritt2be387a2016-03-31 09:31:58 -050036 //fmt.Printf("Skipping field: %s...\n", f.Name)
Jon Perritt77f79d22016-03-15 06:54:33 -050037 continue
38 }
39
Jon Perritt2be387a2016-03-31 09:31:58 -050040 //fmt.Printf("Starting on field: %s...\n", f.Name)
Jon Perrittdb0ae142016-03-13 00:33:41 -060041
42 zero := isZero(v)
Jon Perritt2be387a2016-03-31 09:31:58 -050043 //fmt.Printf("v is zero?: %v\n", zero)
Jon Perrittdb0ae142016-03-13 00:33:41 -060044
45 // if the field has a required tag that's set to "true"
46 if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
Jon Perritt77f79d22016-03-15 06:54:33 -050047 //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 -060048 // if the field's value is zero, return a missing-argument error
49 if zero {
50 // if the field has a 'required' tag, it can't have a zero-value
51 err := ErrMissingInput{}
52 err.Argument = f.Name
53 return nil, err
54 }
55 }
56
57 if xorTag := f.Tag.Get("xor"); xorTag != "" {
Jon Perritt77f79d22016-03-15 06:54:33 -050058 //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 -060059 xorField := optsValue.FieldByName(xorTag)
60 var xorFieldIsZero bool
61 if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
62 xorFieldIsZero = true
63 } else {
64 if xorField.Kind() == reflect.Ptr {
65 xorField = xorField.Elem()
66 }
67 xorFieldIsZero = isZero(xorField)
68 }
69 if !(zero != xorFieldIsZero) {
70 err := ErrMissingInput{}
71 err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
72 err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
73 return nil, err
74 }
75 }
76
77 if orTag := f.Tag.Get("or"); orTag != "" {
Jon Perritt77f79d22016-03-15 06:54:33 -050078 //fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
79 //fmt.Printf("field is zero?: %v\n", zero)
Jon Perrittdb0ae142016-03-13 00:33:41 -060080 if zero {
81 orField := optsValue.FieldByName(orTag)
82 var orFieldIsZero bool
83 if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
84 orFieldIsZero = true
85 } else {
86 if orField.Kind() == reflect.Ptr {
87 orField = orField.Elem()
88 }
89 orFieldIsZero = isZero(orField)
90 }
91 if orFieldIsZero {
92 err := ErrMissingInput{}
93 err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
94 err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
95 return nil, err
96 }
97 }
98 }
99
100 if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
101 if zero {
Jon Perritt77f79d22016-03-15 06:54:33 -0500102 //fmt.Printf("value before change: %+v\n", optsValue.Field(i))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600103 if jsonTag := f.Tag.Get("json"); jsonTag != "" {
104 jsonTagPieces := strings.Split(jsonTag, ",")
105 if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
106 if v.CanSet() {
107 if !v.IsNil() {
108 if v.Kind() == reflect.Ptr {
109 v.Set(reflect.Zero(v.Type()))
110 }
111 }
Jon Perritt77f79d22016-03-15 06:54:33 -0500112 //fmt.Printf("value after change: %+v\n", optsValue.Field(i))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600113 }
114 }
115 }
116 continue
117 }
118
Jon Perritt77f79d22016-03-15 06:54:33 -0500119 //fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
Jon Perrittdb0ae142016-03-13 00:33:41 -0600120 _, err := BuildRequestBody(v.Interface(), f.Name)
121 if err != nil {
122 return nil, err
123 }
Jon Perritt01618ee2016-03-09 03:04:06 -0600124 }
125 }
126
Jon Perritt77f79d22016-03-15 06:54:33 -0500127 //fmt.Printf("opts: %+v \n", opts)
Jon Perrittdb0ae142016-03-13 00:33:41 -0600128
Jon Perritt01618ee2016-03-09 03:04:06 -0600129 b, err := json.Marshal(opts)
130 if err != nil {
131 return nil, err
132 }
133
Jon Perritt77f79d22016-03-15 06:54:33 -0500134 //fmt.Printf("string(b): %s\n", string(b))
Jon Perrittdb0ae142016-03-13 00:33:41 -0600135
Jon Perritt01618ee2016-03-09 03:04:06 -0600136 err = json.Unmarshal(b, &optsMap)
137 if err != nil {
138 return nil, err
139 }
140
Jon Perrittdb0ae142016-03-13 00:33:41 -0600141 //fmt.Printf("optsMap: %+v\n", optsMap)
142
143 if parent != "" {
144 optsMap = map[string]interface{}{parent: optsMap}
145 }
146 //fmt.Printf("optsMap after parent added: %+v\n", optsMap)
Jon Perritt01618ee2016-03-09 03:04:06 -0600147 return optsMap, nil
148 }
149 // Return an error if the underlying type of 'opts' isn't a struct.
150 return nil, fmt.Errorf("Options type is not a struct.")
jrperrittb1013232016-02-10 19:01:53 -0600151}
152
Jamie Hannafordd1e6e762014-11-06 14:26:31 +0100153// EnabledState is a convenience type, mostly used in Create and Update
154// operations. Because the zero value of a bool is FALSE, we need to use a
155// pointer instead to indicate zero-ness.
156type EnabledState *bool
157
158// Convenience vars for EnabledState values.
159var (
160 iTrue = true
161 iFalse = false
162
163 Enabled EnabledState = &iTrue
164 Disabled EnabledState = &iFalse
165)
166
Jon Perritte1c6ceb2016-03-14 12:09:36 -0500167// IPVersion is a type for the possible IP address versions. Valid instances
168// are IPv4 and IPv6
169type IPVersion int
170
171const (
172 // IPv4 is used for IP version 4 addresses
173 IPv4 IPVersion = 4
174 // IPv6 is used for IP version 6 addresses
175 IPv6 IPVersion = 6
176)
177
Jamie Hannaford7fa27892014-11-07 15:11:20 +0100178// IntToPointer is a function for converting integers into integer pointers.
179// This is useful when passing in options to operations.
180func IntToPointer(i int) *int {
181 return &i
182}
183
Ash Wilson0735acb2014-10-31 14:18:00 -0400184/*
185MaybeString is an internal function to be used by request methods in individual
186resource packages.
187
188It takes a string that might be a zero value and returns either a pointer to its
189address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400190from an options struct by leaving them zeroed, but still pass nil to the JSON
191serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -0400192*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +0200193func MaybeString(original string) *string {
194 if original != "" {
195 return &original
196 }
197 return nil
198}
Jon Perrittf90a43c2014-09-28 20:09:46 -0500199
Ash Wilson0735acb2014-10-31 14:18:00 -0400200/*
201MaybeInt is an internal function to be used by request methods in individual
202resource packages.
203
Ash Wilson07d1cfb2014-10-31 14:21:26 -0400204Like MaybeString, it accepts an int that may or may not be a zero value, and
205returns either a pointer to its address or nil. It's intended to hint that the
206JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -0400207*/
Jon Perritt8d262582014-10-03 11:11:46 -0500208func MaybeInt(original int) *int {
209 if original != 0 {
210 return &original
211 }
212 return nil
213}
214
Jon Perrittdb0ae142016-03-13 00:33:41 -0600215/*
216func isUnderlyingStructZero(v reflect.Value) bool {
217 switch v.Kind() {
218 case reflect.Ptr:
219 return isUnderlyingStructZero(v.Elem())
220 default:
221 return isZero(v)
222 }
223}
224*/
225
Jon Perrittf90a43c2014-09-28 20:09:46 -0500226var t time.Time
227
228func isZero(v reflect.Value) bool {
jrperritt29ae6b32016-04-13 12:59:37 -0500229 //fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500230 switch v.Kind() {
Jon Perrittdb0ae142016-03-13 00:33:41 -0600231 case reflect.Ptr:
232 if v.IsNil() {
233 return true
234 }
Joe Topjian12f19e52016-11-05 14:35:08 -0600235 return false
Jon Perrittf90a43c2014-09-28 20:09:46 -0500236 case reflect.Func, reflect.Map, reflect.Slice:
237 return v.IsNil()
238 case reflect.Array:
239 z := true
240 for i := 0; i < v.Len(); i++ {
241 z = z && isZero(v.Index(i))
242 }
243 return z
244 case reflect.Struct:
245 if v.Type() == reflect.TypeOf(t) {
246 if v.Interface().(time.Time).IsZero() {
247 return true
248 }
249 return false
250 }
251 z := true
252 for i := 0; i < v.NumField(); i++ {
253 z = z && isZero(v.Field(i))
254 }
255 return z
256 }
257 // Compare other types directly:
258 z := reflect.Zero(v.Type())
jrperritt29ae6b32016-04-13 12:59:37 -0500259 //fmt.Printf("zero type for value: %+v\n\n\n", z)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500260 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) {
Joe Topjian12f19e52016-11-05 14:35:08 -0600310 loop:
Jon Perrittf90a43c2014-09-28 20:09:46 -0500311 switch v.Kind() {
Joe Topjian12f19e52016-11-05 14:35:08 -0600312 case reflect.Ptr:
313 v = v.Elem()
314 goto loop
Jon Perrittf90a43c2014-09-28 20:09:46 -0500315 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100316 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500317 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100318 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500319 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100320 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700321 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700322 switch v.Type().Elem() {
323 case reflect.TypeOf(0):
324 for i := 0; i < v.Len(); i++ {
325 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
326 }
327 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700328 for i := 0; i < v.Len(); i++ {
329 params.Add(tags[0], v.Index(i).String())
330 }
331 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500332 }
333 } else {
334 // Otherwise, the field is not set.
335 if len(tags) == 2 && tags[1] == "required" {
336 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500337 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500338 }
339 }
340 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100341 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500342
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100343 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500344 }
345 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500346 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500347}
348
Ash Wilson0735acb2014-10-31 14:18:00 -0400349/*
350BuildHeaders is an internal function to be used by request methods in
351individual resource packages.
352
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700353It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400354suitable for use as the HTTP headers of an outgoing request. Field names are
355mapped to header names based in "h" tags.
356
357 type struct Something {
358 Bar string `h:"x_bar"`
359 Baz int `h:"lorem_ipsum"`
360 }
361
362 instance := Something{
363 Bar: "AAA",
364 Baz: "BBB",
365 }
366
367will be converted into:
368
369 map[string]string{
370 "x_bar": "AAA",
371 "lorem_ipsum": "BBB",
372 }
373
374Untagged fields and fields left at their zero values are skipped. Integers,
375booleans and string values are supported.
376*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500377func BuildHeaders(opts interface{}) (map[string]string, error) {
378 optsValue := reflect.ValueOf(opts)
379 if optsValue.Kind() == reflect.Ptr {
380 optsValue = optsValue.Elem()
381 }
382
383 optsType := reflect.TypeOf(opts)
384 if optsType.Kind() == reflect.Ptr {
385 optsType = optsType.Elem()
386 }
387
388 optsMap := make(map[string]string)
389 if optsValue.Kind() == reflect.Struct {
390 for i := 0; i < optsValue.NumField(); i++ {
391 v := optsValue.Field(i)
392 f := optsType.Field(i)
393 hTag := f.Tag.Get("h")
394
395 // if the field has a 'h' tag, it goes in the header
396 if hTag != "" {
397 tags := strings.Split(hTag, ",")
398
399 // if the field is set, add it to the slice of query pieces
400 if !isZero(v) {
401 switch v.Kind() {
402 case reflect.String:
403 optsMap[tags[0]] = v.String()
404 case reflect.Int:
405 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
406 case reflect.Bool:
407 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
408 }
409 } else {
410 // Otherwise, the field is not set.
411 if len(tags) == 2 && tags[1] == "required" {
412 // And the field is required. Return an error.
413 return optsMap, fmt.Errorf("Required header not set.")
414 }
415 }
416 }
417
418 }
419 return optsMap, nil
420 }
421 // Return an error if the underlying type of 'opts' isn't a struct.
422 return optsMap, fmt.Errorf("Options type is not a struct.")
423}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100424
425// IDSliceToQueryString takes a slice of elements and converts them into a query
426// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
427// result would be `?name=20&name=40&name=60'
428func IDSliceToQueryString(name string, ids []int) string {
429 str := ""
430 for k, v := range ids {
431 if k == 0 {
432 str += "?"
433 } else {
434 str += "&"
435 }
436 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
437 }
438 return str
439}
440
441// IntWithinRange returns TRUE if an integer falls within a defined range, and
442// FALSE if not.
443func IntWithinRange(val, min, max int) bool {
444 return val > min && val < max
445}