blob: 68c17eb5ae78a9719e0978cc727aa1e08b8f7ff1 [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
Ash Wilson0735acb2014-10-31 14:18:00 -040012/*
13MaybeString is an internal function to be used by request methods in individual
14resource packages.
15
16It takes a string that might be a zero value and returns either a pointer to its
17address or nil. This is useful for allowing users to conveniently omit values
Ash Wilson07d1cfb2014-10-31 14:21:26 -040018from an options struct by leaving them zeroed, but still pass nil to the JSON
19serializer so they'll be omitted from the request body.
Ash Wilson0735acb2014-10-31 14:18:00 -040020*/
Jamie Hannaford6abf9282014-09-24 10:54:13 +020021func MaybeString(original string) *string {
22 if original != "" {
23 return &original
24 }
25 return nil
26}
Jon Perrittf90a43c2014-09-28 20:09:46 -050027
Ash Wilson0735acb2014-10-31 14:18:00 -040028/*
29MaybeInt is an internal function to be used by request methods in individual
30resource packages.
31
Ash Wilson07d1cfb2014-10-31 14:21:26 -040032Like MaybeString, it accepts an int that may or may not be a zero value, and
33returns either a pointer to its address or nil. It's intended to hint that the
34JSON serializer should omit its field.
Ash Wilson0735acb2014-10-31 14:18:00 -040035*/
Jon Perritt8d262582014-10-03 11:11:46 -050036func MaybeInt(original int) *int {
37 if original != 0 {
38 return &original
39 }
40 return nil
41}
42
Jon Perrittf90a43c2014-09-28 20:09:46 -050043var t time.Time
44
45func isZero(v reflect.Value) bool {
46 switch v.Kind() {
47 case reflect.Func, reflect.Map, reflect.Slice:
48 return v.IsNil()
49 case reflect.Array:
50 z := true
51 for i := 0; i < v.Len(); i++ {
52 z = z && isZero(v.Index(i))
53 }
54 return z
55 case reflect.Struct:
56 if v.Type() == reflect.TypeOf(t) {
57 if v.Interface().(time.Time).IsZero() {
58 return true
59 }
60 return false
61 }
62 z := true
63 for i := 0; i < v.NumField(); i++ {
64 z = z && isZero(v.Field(i))
65 }
66 return z
67 }
68 // Compare other types directly:
69 z := reflect.Zero(v.Type())
70 return v.Interface() == z.Interface()
71}
72
Jamie Hannafordb280dea2014-10-24 15:14:06 +020073/*
Ash Wilson0735acb2014-10-31 14:18:00 -040074BuildQueryString is an internal function to be used by request methods in
75individual resource packages.
Jamie Hannafordb280dea2014-10-24 15:14:06 +020076
Ash Wilson0735acb2014-10-31 14:18:00 -040077It accepts a tagged structure and expands it into a URL struct. Field names are
78converted into query parameters based on a "q" tag. For example:
79
80 type struct Something {
Jamie Hannafordb280dea2014-10-24 15:14:06 +020081 Bar string `q:"x_bar"`
82 Baz int `q:"lorem_ipsum"`
Jamie Hannafordb280dea2014-10-24 15:14:06 +020083 }
84
Ash Wilson0735acb2014-10-31 14:18:00 -040085 instance := Something{
86 Bar: "AAA",
87 Baz: "BBB",
88 }
89
90will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
91
92The struct's fields may be strings, integers, or boolean values. Fields left at
Alex Gaynorc6cc18f2014-10-31 13:48:58 -070093their type's zero value will be omitted from the query.
Jamie Hannafordb280dea2014-10-24 15:14:06 +020094*/
Jon Perrittde47eac2014-09-30 15:34:17 -050095func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -050096 optsValue := reflect.ValueOf(opts)
97 if optsValue.Kind() == reflect.Ptr {
98 optsValue = optsValue.Elem()
99 }
100
101 optsType := reflect.TypeOf(opts)
102 if optsType.Kind() == reflect.Ptr {
103 optsType = optsType.Elem()
104 }
105
Jamie Hannafordcb12ee62014-10-06 15:35:36 +0200106 var optsSlice []string
Jon Perrittf90a43c2014-09-28 20:09:46 -0500107 if optsValue.Kind() == reflect.Struct {
108 for i := 0; i < optsValue.NumField(); i++ {
109 v := optsValue.Field(i)
110 f := optsType.Field(i)
111 qTag := f.Tag.Get("q")
112
113 // if the field has a 'q' tag, it goes in the query string
114 if qTag != "" {
115 tags := strings.Split(qTag, ",")
116
117 // if the field is set, add it to the slice of query pieces
118 if !isZero(v) {
119 switch v.Kind() {
120 case reflect.String:
121 optsSlice = append(optsSlice, tags[0]+"="+v.String())
122 case reflect.Int:
123 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatInt(v.Int(), 10))
124 case reflect.Bool:
125 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatBool(v.Bool()))
126 }
127 } else {
128 // Otherwise, the field is not set.
129 if len(tags) == 2 && tags[1] == "required" {
130 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500131 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500132 }
133 }
134 }
135
136 }
137 // URL encode the string for safety.
Jon Perritt255b6f82014-09-30 16:07:50 -0500138 s := strings.Join(optsSlice, "&")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500139 if s != "" {
140 s = "?" + s
141 }
Jon Perrittde47eac2014-09-30 15:34:17 -0500142 u, err := url.Parse(s)
143 if err != nil {
144 return nil, err
145 }
146 return u, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500147 }
148 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500149 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500150}
151
Ash Wilson0735acb2014-10-31 14:18:00 -0400152/*
153BuildHeaders is an internal function to be used by request methods in
154individual resource packages.
155
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700156It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400157suitable for use as the HTTP headers of an outgoing request. Field names are
158mapped to header names based in "h" tags.
159
160 type struct Something {
161 Bar string `h:"x_bar"`
162 Baz int `h:"lorem_ipsum"`
163 }
164
165 instance := Something{
166 Bar: "AAA",
167 Baz: "BBB",
168 }
169
170will be converted into:
171
172 map[string]string{
173 "x_bar": "AAA",
174 "lorem_ipsum": "BBB",
175 }
176
177Untagged fields and fields left at their zero values are skipped. Integers,
178booleans and string values are supported.
179*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500180func BuildHeaders(opts interface{}) (map[string]string, error) {
181 optsValue := reflect.ValueOf(opts)
182 if optsValue.Kind() == reflect.Ptr {
183 optsValue = optsValue.Elem()
184 }
185
186 optsType := reflect.TypeOf(opts)
187 if optsType.Kind() == reflect.Ptr {
188 optsType = optsType.Elem()
189 }
190
191 optsMap := make(map[string]string)
192 if optsValue.Kind() == reflect.Struct {
193 for i := 0; i < optsValue.NumField(); i++ {
194 v := optsValue.Field(i)
195 f := optsType.Field(i)
196 hTag := f.Tag.Get("h")
197
198 // if the field has a 'h' tag, it goes in the header
199 if hTag != "" {
200 tags := strings.Split(hTag, ",")
201
202 // if the field is set, add it to the slice of query pieces
203 if !isZero(v) {
204 switch v.Kind() {
205 case reflect.String:
206 optsMap[tags[0]] = v.String()
207 case reflect.Int:
208 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
209 case reflect.Bool:
210 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
211 }
212 } else {
213 // Otherwise, the field is not set.
214 if len(tags) == 2 && tags[1] == "required" {
215 // And the field is required. Return an error.
216 return optsMap, fmt.Errorf("Required header not set.")
217 }
218 }
219 }
220
221 }
222 return optsMap, nil
223 }
224 // Return an error if the underlying type of 'opts' isn't a struct.
225 return optsMap, fmt.Errorf("Options type is not a struct.")
226}