blob: 12f894cecdb94e2e43239515a6c241d683e0d655 [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
18from an options struct, but still pass nil to the JSON serializer to omit them
19from a request body.
20*/
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
32Like MaybeString, it accepts an int that may be a zero value and returns either
33a pointer to its address or nil to hint to the JSON serializer to omit its
34field.
35*/
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
93their type's zero value will be omittted 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
156It accepts a arbitrary tagged structure and produces a string map that's
157suitable 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}