blob: 77ae40de0e522e9c993fabbd569462d11f7f7828 [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
Ash Wilson0735acb2014-10-31 14:18:00 -040026/*
27MaybeString is an internal function to be used by request methods in individual
28resource packages.
29
30It takes a string that might be a zero value and returns either a pointer to its
31address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -040032from an options struct by leaving them zeroed, but still pass nil to the JSON
33serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -040034*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +020035func MaybeString(original string) *string {
36 if original != "" {
37 return &original
38 }
39 return nil
40}
Jon Perrittf90a43c2014-09-28 20:09:46 -050041
Ash Wilson0735acb2014-10-31 14:18:00 -040042/*
43MaybeInt is an internal function to be used by request methods in individual
44resource packages.
45
Ash Wilson07d1cfb2014-10-31 14:21:26 -040046Like MaybeString, it accepts an int that may or may not be a zero value, and
47returns either a pointer to its address or nil. It's intended to hint that the
48JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -040049*/
Jon Perritt8d262582014-10-03 11:11:46 -050050func MaybeInt(original int) *int {
51 if original != 0 {
52 return &original
53 }
54 return nil
55}
56
Jon Perrittf90a43c2014-09-28 20:09:46 -050057var t time.Time
58
59func isZero(v reflect.Value) bool {
60 switch v.Kind() {
61 case reflect.Func, reflect.Map, reflect.Slice:
62 return v.IsNil()
63 case reflect.Array:
64 z := true
65 for i := 0; i < v.Len(); i++ {
66 z = z && isZero(v.Index(i))
67 }
68 return z
69 case reflect.Struct:
70 if v.Type() == reflect.TypeOf(t) {
71 if v.Interface().(time.Time).IsZero() {
72 return true
73 }
74 return false
75 }
76 z := true
77 for i := 0; i < v.NumField(); i++ {
78 z = z && isZero(v.Field(i))
79 }
80 return z
81 }
82 // Compare other types directly:
83 z := reflect.Zero(v.Type())
84 return v.Interface() == z.Interface()
85}
86
Jamie Hannafordb280dea2014-10-24 15:14:06 +020087/*
Ash Wilson0735acb2014-10-31 14:18:00 -040088BuildQueryString is an internal function to be used by request methods in
89individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +020090
Ash Wilson0735acb2014-10-31 14:18:00 -040091It accepts a tagged structure and expands it into a URL struct. Field names are
92converted into query parameters based on a "q" tag. For example:
93
94 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +020095 Bar string `q:"x_bar"`
96 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +020097 }
98
Ash Wilson0735acb2014-10-31 14:18:00 -040099 instance := Something{
100 Bar: "AAA",
101 Baz: "BBB",
102 }
103
104will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
105
106The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700107their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +0200108*/
Jon Perrittde47eac2014-09-30 15:34:17 -0500109func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -0500110 optsValue := reflect.ValueOf(opts)
111 if optsValue.Kind() == reflect.Ptr {
112 optsValue = optsValue.Elem()
113 }
114
115 optsType := reflect.TypeOf(opts)
116 if optsType.Kind() == reflect.Ptr {
117 optsType = optsType.Elem()
118 }
119
Jamie Hannafordcb12ee62014-10-06 15:35:36 +0200120 var optsSlice []string
Jon Perrittf90a43c2014-09-28 20:09:46 -0500121 if optsValue.Kind() == reflect.Struct {
122 for i := 0; i < optsValue.NumField(); i++ {
123 v := optsValue.Field(i)
124 f := optsType.Field(i)
125 qTag := f.Tag.Get("q")
126
127 // if the field has a 'q' tag, it goes in the query string
128 if qTag != "" {
129 tags := strings.Split(qTag, ",")
130
131 // if the field is set, add it to the slice of query pieces
132 if !isZero(v) {
133 switch v.Kind() {
134 case reflect.String:
135 optsSlice = append(optsSlice, tags[0]+"="+v.String())
136 case reflect.Int:
137 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatInt(v.Int(), 10))
138 case reflect.Bool:
139 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatBool(v.Bool()))
140 }
141 } else {
142 // Otherwise, the field is not set.
143 if len(tags) == 2 && tags[1] == "required" {
144 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500145 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500146 }
147 }
148 }
149
150 }
151 // URL encode the string for safety.
Jon Perritt255b6f82014-09-30 16:07:50 -0500152 s := strings.Join(optsSlice, "&")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500153 if s != "" {
154 s = "?" + s
155 }
Jon Perrittde47eac2014-09-30 15:34:17 -0500156 u, err := url.Parse(s)
157 if err != nil {
158 return nil, err
159 }
160 return u, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500161 }
162 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500163 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500164}
165
Ash Wilson0735acb2014-10-31 14:18:00 -0400166/*
167BuildHeaders is an internal function to be used by request methods in
168individual resource packages.
169
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700170It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400171suitable for use as the HTTP headers of an outgoing request. Field names are
172mapped to header names based in "h" tags.
173
174 type struct Something {
175 Bar string `h:"x_bar"`
176 Baz int `h:"lorem_ipsum"`
177 }
178
179 instance := Something{
180 Bar: "AAA",
181 Baz: "BBB",
182 }
183
184will be converted into:
185
186 map[string]string{
187 "x_bar": "AAA",
188 "lorem_ipsum": "BBB",
189 }
190
191Untagged fields and fields left at their zero values are skipped. Integers,
192booleans and string values are supported.
193*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500194func BuildHeaders(opts interface{}) (map[string]string, error) {
195 optsValue := reflect.ValueOf(opts)
196 if optsValue.Kind() == reflect.Ptr {
197 optsValue = optsValue.Elem()
198 }
199
200 optsType := reflect.TypeOf(opts)
201 if optsType.Kind() == reflect.Ptr {
202 optsType = optsType.Elem()
203 }
204
205 optsMap := make(map[string]string)
206 if optsValue.Kind() == reflect.Struct {
207 for i := 0; i < optsValue.NumField(); i++ {
208 v := optsValue.Field(i)
209 f := optsType.Field(i)
210 hTag := f.Tag.Get("h")
211
212 // if the field has a 'h' tag, it goes in the header
213 if hTag != "" {
214 tags := strings.Split(hTag, ",")
215
216 // if the field is set, add it to the slice of query pieces
217 if !isZero(v) {
218 switch v.Kind() {
219 case reflect.String:
220 optsMap[tags[0]] = v.String()
221 case reflect.Int:
222 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
223 case reflect.Bool:
224 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
225 }
226 } else {
227 // Otherwise, the field is not set.
228 if len(tags) == 2 && tags[1] == "required" {
229 // And the field is required. Return an error.
230 return optsMap, fmt.Errorf("Required header not set.")
231 }
232 }
233 }
234
235 }
236 return optsMap, nil
237 }
238 // Return an error if the underlying type of 'opts' isn't a struct.
239 return optsMap, fmt.Errorf("Options type is not a struct.")
240}