blob: 85640ec7873e3b74a0717043b0004017a02e2b9e [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 Perritt01618ee2016-03-09 03:04:06 -060013// BuildRequestBody builds a map[string]interface from the given `struct`.
14//
15//
16func BuildRequestBody(opts interface{}) (map[string]interface{}, error) {
17 optsValue := reflect.ValueOf(opts)
18 if optsValue.Kind() == reflect.Ptr {
19 optsValue = optsValue.Elem()
20 }
21
22 optsType := reflect.TypeOf(opts)
23 if optsType.Kind() == reflect.Ptr {
24 optsType = optsType.Elem()
25 }
26
27 optsMap := make(map[string]interface{})
28 if optsValue.Kind() == reflect.Struct {
29
30 for i := 0; i < optsValue.NumField(); i++ {
31 v := optsValue.Field(i)
32 f := optsType.Field(i)
33 requiredTag := f.Tag.Get("required")
34
35 // if the field has a 'required' tag, it can't have a zero-value
36 if requiredTag == "true" && isZero(v) {
37 err := ErrMissingInput{}
38 err.Argument = f.Name
39 return nil, err
40 }
41 }
42
43 b, err := json.Marshal(opts)
44 if err != nil {
45 return nil, err
46 }
47
48 err = json.Unmarshal(b, &optsMap)
49 if err != nil {
50 return nil, err
51 }
52
53 return optsMap, nil
54 }
55 // Return an error if the underlying type of 'opts' isn't a struct.
56 return nil, fmt.Errorf("Options type is not a struct.")
jrperrittb1013232016-02-10 19:01:53 -060057}
58
Jamie Hannafordd1e6e762014-11-06 14:26:31 +010059// EnabledState is a convenience type, mostly used in Create and Update
60// operations. Because the zero value of a bool is FALSE, we need to use a
61// pointer instead to indicate zero-ness.
62type EnabledState *bool
63
64// Convenience vars for EnabledState values.
65var (
66 iTrue = true
67 iFalse = false
68
69 Enabled EnabledState = &iTrue
70 Disabled EnabledState = &iFalse
71)
72
Jamie Hannaford7fa27892014-11-07 15:11:20 +010073// IntToPointer is a function for converting integers into integer pointers.
74// This is useful when passing in options to operations.
75func IntToPointer(i int) *int {
76 return &i
77}
78
Ash Wilson0735acb2014-10-31 14:18:00 -040079/*
80MaybeString is an internal function to be used by request methods in individual
81resource packages.
82
83It takes a string that might be a zero value and returns either a pointer to its
84address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -040085from an options struct by leaving them zeroed, but still pass nil to the JSON
86serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -040087*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +020088func MaybeString(original string) *string {
89 if original != "" {
90 return &original
91 }
92 return nil
93}
Jon Perrittf90a43c2014-09-28 20:09:46 -050094
Ash Wilson0735acb2014-10-31 14:18:00 -040095/*
96MaybeInt is an internal function to be used by request methods in individual
97resource packages.
98
Ash Wilson07d1cfb2014-10-31 14:21:26 -040099Like MaybeString, it accepts an int that may or may not be a zero value, and
100returns either a pointer to its address or nil. It's intended to hint that the
101JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -0400102*/
Jon Perritt8d262582014-10-03 11:11:46 -0500103func MaybeInt(original int) *int {
104 if original != 0 {
105 return &original
106 }
107 return nil
108}
109
Jon Perrittf90a43c2014-09-28 20:09:46 -0500110var t time.Time
111
112func isZero(v reflect.Value) bool {
113 switch v.Kind() {
114 case reflect.Func, reflect.Map, reflect.Slice:
115 return v.IsNil()
116 case reflect.Array:
117 z := true
118 for i := 0; i < v.Len(); i++ {
119 z = z && isZero(v.Index(i))
120 }
121 return z
122 case reflect.Struct:
123 if v.Type() == reflect.TypeOf(t) {
124 if v.Interface().(time.Time).IsZero() {
125 return true
126 }
127 return false
128 }
129 z := true
130 for i := 0; i < v.NumField(); i++ {
131 z = z && isZero(v.Field(i))
132 }
133 return z
134 }
135 // Compare other types directly:
136 z := reflect.Zero(v.Type())
137 return v.Interface() == z.Interface()
138}
139
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200140/*
Ash Wilson0735acb2014-10-31 14:18:00 -0400141BuildQueryString is an internal function to be used by request methods in
142individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200143
Ash Wilson0735acb2014-10-31 14:18:00 -0400144It accepts a tagged structure and expands it into a URL struct. Field names are
145converted into query parameters based on a "q" tag. For example:
146
147 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200148 Bar string `q:"x_bar"`
149 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200150 }
151
Ash Wilson0735acb2014-10-31 14:18:00 -0400152 instance := Something{
153 Bar: "AAA",
154 Baz: "BBB",
155 }
156
157will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
158
159The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700160their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200161*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500162func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500163 optsValue := reflect.ValueOf(opts)
164 if optsValue.Kind() == reflect.Ptr {
165 optsValue = optsValue.Elem()
166 }
167
168 optsType := reflect.TypeOf(opts)
169 if optsType.Kind() == reflect.Ptr {
170 optsType = optsType.Elem()
171 }
172
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100173 params := url.Values{}
174
Jon Perrittf90a43c2014-09-28 20:09:46 -0500175 if optsValue.Kind() == reflect.Struct {
176 for i := 0; i < optsValue.NumField(); i++ {
177 v := optsValue.Field(i)
178 f := optsType.Field(i)
179 qTag := f.Tag.Get("q")
180
181 // if the field has a 'q' tag, it goes in the query string
182 if qTag != "" {
183 tags := strings.Split(qTag, ",")
184
185 // if the field is set, add it to the slice of query pieces
186 if !isZero(v) {
187 switch v.Kind() {
188 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100189 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500190 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100191 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500192 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100193 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700194 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700195 switch v.Type().Elem() {
196 case reflect.TypeOf(0):
197 for i := 0; i < v.Len(); i++ {
198 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
199 }
200 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700201 for i := 0; i < v.Len(); i++ {
202 params.Add(tags[0], v.Index(i).String())
203 }
204 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500205 }
206 } else {
207 // Otherwise, the field is not set.
208 if len(tags) == 2 && tags[1] == "required" {
209 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500210 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500211 }
212 }
213 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100214 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500215
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100216 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500217 }
218 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500219 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500220}
221
Ash Wilson0735acb2014-10-31 14:18:00 -0400222/*
223BuildHeaders is an internal function to be used by request methods in
224individual resource packages.
225
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700226It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400227suitable for use as the HTTP headers of an outgoing request. Field names are
228mapped to header names based in "h" tags.
229
230 type struct Something {
231 Bar string `h:"x_bar"`
232 Baz int `h:"lorem_ipsum"`
233 }
234
235 instance := Something{
236 Bar: "AAA",
237 Baz: "BBB",
238 }
239
240will be converted into:
241
242 map[string]string{
243 "x_bar": "AAA",
244 "lorem_ipsum": "BBB",
245 }
246
247Untagged fields and fields left at their zero values are skipped. Integers,
248booleans and string values are supported.
249*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500250func BuildHeaders(opts interface{}) (map[string]string, error) {
251 optsValue := reflect.ValueOf(opts)
252 if optsValue.Kind() == reflect.Ptr {
253 optsValue = optsValue.Elem()
254 }
255
256 optsType := reflect.TypeOf(opts)
257 if optsType.Kind() == reflect.Ptr {
258 optsType = optsType.Elem()
259 }
260
261 optsMap := make(map[string]string)
262 if optsValue.Kind() == reflect.Struct {
263 for i := 0; i < optsValue.NumField(); i++ {
264 v := optsValue.Field(i)
265 f := optsType.Field(i)
266 hTag := f.Tag.Get("h")
267
268 // if the field has a 'h' tag, it goes in the header
269 if hTag != "" {
270 tags := strings.Split(hTag, ",")
271
272 // if the field is set, add it to the slice of query pieces
273 if !isZero(v) {
274 switch v.Kind() {
275 case reflect.String:
276 optsMap[tags[0]] = v.String()
277 case reflect.Int:
278 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
279 case reflect.Bool:
280 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
281 }
282 } else {
283 // Otherwise, the field is not set.
284 if len(tags) == 2 && tags[1] == "required" {
285 // And the field is required. Return an error.
286 return optsMap, fmt.Errorf("Required header not set.")
287 }
288 }
289 }
290
291 }
292 return optsMap, nil
293 }
294 // Return an error if the underlying type of 'opts' isn't a struct.
295 return optsMap, fmt.Errorf("Options type is not a struct.")
296}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100297
298// IDSliceToQueryString takes a slice of elements and converts them into a query
299// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
300// result would be `?name=20&name=40&name=60'
301func IDSliceToQueryString(name string, ids []int) string {
302 str := ""
303 for k, v := range ids {
304 if k == 0 {
305 str += "?"
306 } else {
307 str += "&"
308 }
309 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
310 }
311 return str
312}
313
314// IntWithinRange returns TRUE if an integer falls within a defined range, and
315// FALSE if not.
316func IntWithinRange(val, min, max int) bool {
317 return val > min && val < max
318}