blob: cd5bc65e086f268881534dc0ce52ad0582577553 [file] [log] [blame]
Jamie Hannaford6abf9282014-09-24 10:54:13 +02001package gophercloud
2
Jon Perrittf90a43c2014-09-28 20:09:46 -05003import (
4 "fmt"
5 "net/url"
6 "reflect"
7 "strconv"
8 "strings"
9 "time"
10)
11
jrperrittb1013232016-02-10 19:01:53 -060012type Opts struct {
13 ServiceClient *ServiceClient
14}
15
Jamie Hannafordd1e6e762014-11-06 14:26:31 +010016// EnabledState is a convenience type, mostly used in Create and Update
17// operations. Because the zero value of a bool is FALSE, we need to use a
18// pointer instead to indicate zero-ness.
19type EnabledState *bool
20
21// Convenience vars for EnabledState values.
22var (
23 iTrue = true
24 iFalse = false
25
26 Enabled EnabledState = &iTrue
27 Disabled EnabledState = &iFalse
28)
29
Jamie Hannaford7fa27892014-11-07 15:11:20 +010030// IntToPointer is a function for converting integers into integer pointers.
31// This is useful when passing in options to operations.
32func IntToPointer(i int) *int {
33 return &i
34}
35
Ash Wilson0735acb2014-10-31 14:18:00 -040036/*
37MaybeString is an internal function to be used by request methods in individual
38resource packages.
39
40It takes a string that might be a zero value and returns either a pointer to its
41address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -040042from an options struct by leaving them zeroed, but still pass nil to the JSON
43serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -040044*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +020045func MaybeString(original string) *string {
46 if original != "" {
47 return &original
48 }
49 return nil
50}
Jon Perrittf90a43c2014-09-28 20:09:46 -050051
Ash Wilson0735acb2014-10-31 14:18:00 -040052/*
53MaybeInt is an internal function to be used by request methods in individual
54resource packages.
55
Ash Wilson07d1cfb2014-10-31 14:21:26 -040056Like MaybeString, it accepts an int that may or may not be a zero value, and
57returns either a pointer to its address or nil. It's intended to hint that the
58JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -040059*/
Jon Perritt8d262582014-10-03 11:11:46 -050060func MaybeInt(original int) *int {
61 if original != 0 {
62 return &original
63 }
64 return nil
65}
66
Jon Perrittf90a43c2014-09-28 20:09:46 -050067var t time.Time
68
69func isZero(v reflect.Value) bool {
70 switch v.Kind() {
71 case reflect.Func, reflect.Map, reflect.Slice:
72 return v.IsNil()
73 case reflect.Array:
74 z := true
75 for i := 0; i < v.Len(); i++ {
76 z = z && isZero(v.Index(i))
77 }
78 return z
79 case reflect.Struct:
80 if v.Type() == reflect.TypeOf(t) {
81 if v.Interface().(time.Time).IsZero() {
82 return true
83 }
84 return false
85 }
86 z := true
87 for i := 0; i < v.NumField(); i++ {
88 z = z && isZero(v.Field(i))
89 }
90 return z
91 }
92 // Compare other types directly:
93 z := reflect.Zero(v.Type())
94 return v.Interface() == z.Interface()
95}
96
Jamie Hannafordb280dea2014-10-24 15:14:06 +020097/*
Ash Wilson0735acb2014-10-31 14:18:00 -040098BuildQueryString is an internal function to be used by request methods in
99individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200100
Ash Wilson0735acb2014-10-31 14:18:00 -0400101It accepts a tagged structure and expands it into a URL struct. Field names are
102converted into query parameters based on a "q" tag. For example:
103
104 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200105 Bar string `q:"x_bar"`
106 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200107 }
108
Ash Wilson0735acb2014-10-31 14:18:00 -0400109 instance := Something{
110 Bar: "AAA",
111 Baz: "BBB",
112 }
113
114will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
115
116The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700117their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200118*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500119func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500120 optsValue := reflect.ValueOf(opts)
121 if optsValue.Kind() == reflect.Ptr {
122 optsValue = optsValue.Elem()
123 }
124
125 optsType := reflect.TypeOf(opts)
126 if optsType.Kind() == reflect.Ptr {
127 optsType = optsType.Elem()
128 }
129
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100130 params := url.Values{}
131
Jon Perrittf90a43c2014-09-28 20:09:46 -0500132 if optsValue.Kind() == reflect.Struct {
133 for i := 0; i < optsValue.NumField(); i++ {
134 v := optsValue.Field(i)
135 f := optsType.Field(i)
136 qTag := f.Tag.Get("q")
137
138 // if the field has a 'q' tag, it goes in the query string
139 if qTag != "" {
140 tags := strings.Split(qTag, ",")
141
142 // if the field is set, add it to the slice of query pieces
143 if !isZero(v) {
144 switch v.Kind() {
145 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100146 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500147 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100148 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500149 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100150 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700151 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700152 switch v.Type().Elem() {
153 case reflect.TypeOf(0):
154 for i := 0; i < v.Len(); i++ {
155 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
156 }
157 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700158 for i := 0; i < v.Len(); i++ {
159 params.Add(tags[0], v.Index(i).String())
160 }
161 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500162 }
163 } else {
164 // Otherwise, the field is not set.
165 if len(tags) == 2 && tags[1] == "required" {
166 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500167 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500168 }
169 }
170 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100171 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500172
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100173 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500174 }
175 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500176 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500177}
178
Ash Wilson0735acb2014-10-31 14:18:00 -0400179/*
180BuildHeaders is an internal function to be used by request methods in
181individual resource packages.
182
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700183It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400184suitable for use as the HTTP headers of an outgoing request. Field names are
185mapped to header names based in "h" tags.
186
187 type struct Something {
188 Bar string `h:"x_bar"`
189 Baz int `h:"lorem_ipsum"`
190 }
191
192 instance := Something{
193 Bar: "AAA",
194 Baz: "BBB",
195 }
196
197will be converted into:
198
199 map[string]string{
200 "x_bar": "AAA",
201 "lorem_ipsum": "BBB",
202 }
203
204Untagged fields and fields left at their zero values are skipped. Integers,
205booleans and string values are supported.
206*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500207func BuildHeaders(opts interface{}) (map[string]string, error) {
208 optsValue := reflect.ValueOf(opts)
209 if optsValue.Kind() == reflect.Ptr {
210 optsValue = optsValue.Elem()
211 }
212
213 optsType := reflect.TypeOf(opts)
214 if optsType.Kind() == reflect.Ptr {
215 optsType = optsType.Elem()
216 }
217
218 optsMap := make(map[string]string)
219 if optsValue.Kind() == reflect.Struct {
220 for i := 0; i < optsValue.NumField(); i++ {
221 v := optsValue.Field(i)
222 f := optsType.Field(i)
223 hTag := f.Tag.Get("h")
224
225 // if the field has a 'h' tag, it goes in the header
226 if hTag != "" {
227 tags := strings.Split(hTag, ",")
228
229 // if the field is set, add it to the slice of query pieces
230 if !isZero(v) {
231 switch v.Kind() {
232 case reflect.String:
233 optsMap[tags[0]] = v.String()
234 case reflect.Int:
235 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
236 case reflect.Bool:
237 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
238 }
239 } else {
240 // Otherwise, the field is not set.
241 if len(tags) == 2 && tags[1] == "required" {
242 // And the field is required. Return an error.
243 return optsMap, fmt.Errorf("Required header not set.")
244 }
245 }
246 }
247
248 }
249 return optsMap, nil
250 }
251 // Return an error if the underlying type of 'opts' isn't a struct.
252 return optsMap, fmt.Errorf("Options type is not a struct.")
253}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100254
255// IDSliceToQueryString takes a slice of elements and converts them into a query
256// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
257// result would be `?name=20&name=40&name=60'
258func IDSliceToQueryString(name string, ids []int) string {
259 str := ""
260 for k, v := range ids {
261 if k == 0 {
262 str += "?"
263 } else {
264 str += "&"
265 }
266 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
267 }
268 return str
269}
270
271// IntWithinRange returns TRUE if an integer falls within a defined range, and
272// FALSE if not.
273func IntWithinRange(val, min, max int) bool {
274 return val > min && val < max
275}