blob: 4d0f1e6e02811a759f0cf387a39ff14d35e0b95a [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
Jamie Hannafordd1e6e762014-11-06 14:26:31 +010012// EnabledState is a convenience type, mostly used in Create and Update
13// operations. Because the zero value of a bool is FALSE, we need to use a
14// pointer instead to indicate zero-ness.
15type EnabledState *bool
16
17// Convenience vars for EnabledState values.
18var (
19 iTrue = true
20 iFalse = false
21
22 Enabled EnabledState = &iTrue
23 Disabled EnabledState = &iFalse
24)
25
Jamie Hannaford7fa27892014-11-07 15:11:20 +010026// IntToPointer is a function for converting integers into integer pointers.
27// This is useful when passing in options to operations.
28func IntToPointer(i int) *int {
29 return &i
30}
31
Ash Wilson0735acb2014-10-31 14:18:00 -040032/*
33MaybeString is an internal function to be used by request methods in individual
34resource packages.
35
36It takes a string that might be a zero value and returns either a pointer to its
37address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -040038from an options struct by leaving them zeroed, but still pass nil to the JSON
39serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -040040*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +020041func MaybeString(original string) *string {
42 if original != "" {
43 return &original
44 }
45 return nil
46}
Jon Perrittf90a43c2014-09-28 20:09:46 -050047
Ash Wilson0735acb2014-10-31 14:18:00 -040048/*
49MaybeInt is an internal function to be used by request methods in individual
50resource packages.
51
Ash Wilson07d1cfb2014-10-31 14:21:26 -040052Like MaybeString, it accepts an int that may or may not be a zero value, and
53returns either a pointer to its address or nil. It's intended to hint that the
54JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -040055*/
Jon Perritt8d262582014-10-03 11:11:46 -050056func MaybeInt(original int) *int {
57 if original != 0 {
58 return &original
59 }
60 return nil
61}
62
Jon Perrittf90a43c2014-09-28 20:09:46 -050063var t time.Time
64
65func isZero(v reflect.Value) bool {
66 switch v.Kind() {
67 case reflect.Func, reflect.Map, reflect.Slice:
68 return v.IsNil()
69 case reflect.Array:
70 z := true
71 for i := 0; i < v.Len(); i++ {
72 z = z && isZero(v.Index(i))
73 }
74 return z
75 case reflect.Struct:
76 if v.Type() == reflect.TypeOf(t) {
77 if v.Interface().(time.Time).IsZero() {
78 return true
79 }
80 return false
81 }
82 z := true
83 for i := 0; i < v.NumField(); i++ {
84 z = z && isZero(v.Field(i))
85 }
86 return z
87 }
88 // Compare other types directly:
89 z := reflect.Zero(v.Type())
90 return v.Interface() == z.Interface()
91}
92
Jamie Hannafordb280dea2014-10-24 15:14:06 +020093/*
Ash Wilson0735acb2014-10-31 14:18:00 -040094BuildQueryString is an internal function to be used by request methods in
95individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +020096
Ash Wilson0735acb2014-10-31 14:18:00 -040097It accepts a tagged structure and expands it into a URL struct. Field names are
98converted into query parameters based on a "q" tag. For example:
99
100 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200101 Bar string `q:"x_bar"`
102 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200103 }
104
Ash Wilson0735acb2014-10-31 14:18:00 -0400105 instance := Something{
106 Bar: "AAA",
107 Baz: "BBB",
108 }
109
110will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
111
112The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700113their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200114*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500115func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500116 optsValue := reflect.ValueOf(opts)
117 if optsValue.Kind() == reflect.Ptr {
118 optsValue = optsValue.Elem()
119 }
120
121 optsType := reflect.TypeOf(opts)
122 if optsType.Kind() == reflect.Ptr {
123 optsType = optsType.Elem()
124 }
125
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100126 params := url.Values{}
127
Jon Perrittf90a43c2014-09-28 20:09:46 -0500128 if optsValue.Kind() == reflect.Struct {
129 for i := 0; i < optsValue.NumField(); i++ {
130 v := optsValue.Field(i)
131 f := optsType.Field(i)
132 qTag := f.Tag.Get("q")
133
134 // if the field has a 'q' tag, it goes in the query string
135 if qTag != "" {
136 tags := strings.Split(qTag, ",")
137
138 // if the field is set, add it to the slice of query pieces
139 if !isZero(v) {
140 switch v.Kind() {
141 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100142 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500143 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100144 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500145 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100146 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittdc561902015-02-08 15:22:02 -0700147 case reflect.Slice:
Jon Perritte43f3de2015-02-12 11:45:34 -0700148 switch v.Type().Elem() {
149 case reflect.TypeOf(0):
150 for i := 0; i < v.Len(); i++ {
151 params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
152 }
153 default:
Jon Perrittdc561902015-02-08 15:22:02 -0700154 for i := 0; i < v.Len(); i++ {
155 params.Add(tags[0], v.Index(i).String())
156 }
157 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500158 }
159 } else {
160 // Otherwise, the field is not set.
161 if len(tags) == 2 && tags[1] == "required" {
162 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500163 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500164 }
165 }
166 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100167 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500168
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100169 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500170 }
171 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500172 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500173}
174
Ash Wilson0735acb2014-10-31 14:18:00 -0400175/*
176BuildHeaders is an internal function to be used by request methods in
177individual resource packages.
178
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700179It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400180suitable for use as the HTTP headers of an outgoing request. Field names are
181mapped to header names based in "h" tags.
182
183 type struct Something {
184 Bar string `h:"x_bar"`
185 Baz int `h:"lorem_ipsum"`
186 }
187
188 instance := Something{
189 Bar: "AAA",
190 Baz: "BBB",
191 }
192
193will be converted into:
194
195 map[string]string{
196 "x_bar": "AAA",
197 "lorem_ipsum": "BBB",
198 }
199
200Untagged fields and fields left at their zero values are skipped. Integers,
201booleans and string values are supported.
202*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500203func BuildHeaders(opts interface{}) (map[string]string, error) {
204 optsValue := reflect.ValueOf(opts)
205 if optsValue.Kind() == reflect.Ptr {
206 optsValue = optsValue.Elem()
207 }
208
209 optsType := reflect.TypeOf(opts)
210 if optsType.Kind() == reflect.Ptr {
211 optsType = optsType.Elem()
212 }
213
214 optsMap := make(map[string]string)
215 if optsValue.Kind() == reflect.Struct {
216 for i := 0; i < optsValue.NumField(); i++ {
217 v := optsValue.Field(i)
218 f := optsType.Field(i)
219 hTag := f.Tag.Get("h")
220
221 // if the field has a 'h' tag, it goes in the header
222 if hTag != "" {
223 tags := strings.Split(hTag, ",")
224
225 // if the field is set, add it to the slice of query pieces
226 if !isZero(v) {
227 switch v.Kind() {
228 case reflect.String:
229 optsMap[tags[0]] = v.String()
230 case reflect.Int:
231 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
232 case reflect.Bool:
233 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
234 }
235 } else {
236 // Otherwise, the field is not set.
237 if len(tags) == 2 && tags[1] == "required" {
238 // And the field is required. Return an error.
239 return optsMap, fmt.Errorf("Required header not set.")
240 }
241 }
242 }
243
244 }
245 return optsMap, nil
246 }
247 // Return an error if the underlying type of 'opts' isn't a struct.
248 return optsMap, fmt.Errorf("Options type is not a struct.")
249}
Jamie Hannaford950561c2014-11-12 11:12:20 +0100250
251// IDSliceToQueryString takes a slice of elements and converts them into a query
252// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
253// result would be `?name=20&name=40&name=60'
254func IDSliceToQueryString(name string, ids []int) string {
255 str := ""
256 for k, v := range ids {
257 if k == 0 {
258 str += "?"
259 } else {
260 str += "&"
261 }
262 str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
263 }
264 return str
265}
266
267// IntWithinRange returns TRUE if an integer falls within a defined range, and
268// FALSE if not.
269func IntWithinRange(val, min, max int) bool {
270 return val > min && val < max
271}