blob: a9809030b8f2fe9b3b3f90d573dc8abc80e8151d [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 Hannafordf68c3e42014-11-18 13:02:09 +0100106 params := url.Values{}
107
Jon Perrittf90a43c2014-09-28 20:09:46 -0500108 if optsValue.Kind() == reflect.Struct {
109 for i := 0; i < optsValue.NumField(); i++ {
110 v := optsValue.Field(i)
111 f := optsType.Field(i)
112 qTag := f.Tag.Get("q")
113
114 // if the field has a 'q' tag, it goes in the query string
115 if qTag != "" {
116 tags := strings.Split(qTag, ",")
117
118 // if the field is set, add it to the slice of query pieces
119 if !isZero(v) {
120 switch v.Kind() {
121 case reflect.String:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100122 params.Add(tags[0], v.String())
Jon Perrittf90a43c2014-09-28 20:09:46 -0500123 case reflect.Int:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100124 params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500125 case reflect.Bool:
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100126 params.Add(tags[0], strconv.FormatBool(v.Bool()))
Jon Perrittf90a43c2014-09-28 20:09:46 -0500127 }
128 } else {
129 // Otherwise, the field is not set.
130 if len(tags) == 2 && tags[1] == "required" {
131 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500132 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500133 }
134 }
135 }
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100136 }
Jon Perrittf90a43c2014-09-28 20:09:46 -0500137
Jamie Hannafordf68c3e42014-11-18 13:02:09 +0100138 return &url.URL{RawQuery: params.Encode()}, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500139 }
140 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500141 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500142}
143
Ash Wilson0735acb2014-10-31 14:18:00 -0400144/*
145BuildHeaders is an internal function to be used by request methods in
146individual resource packages.
147
Alex Gaynorc6cc18f2014-10-31 13:48:58 -0700148It accepts an arbitrary tagged structure and produces a string map that's
Ash Wilson0735acb2014-10-31 14:18:00 -0400149suitable for use as the HTTP headers of an outgoing request. Field names are
150mapped to header names based in "h" tags.
151
152 type struct Something {
153 Bar string `h:"x_bar"`
154 Baz int `h:"lorem_ipsum"`
155 }
156
157 instance := Something{
158 Bar: "AAA",
159 Baz: "BBB",
160 }
161
162will be converted into:
163
164 map[string]string{
165 "x_bar": "AAA",
166 "lorem_ipsum": "BBB",
167 }
168
169Untagged fields and fields left at their zero values are skipped. Integers,
170booleans and string values are supported.
171*/
Jon Perrittf90a43c2014-09-28 20:09:46 -0500172func BuildHeaders(opts interface{}) (map[string]string, error) {
173 optsValue := reflect.ValueOf(opts)
174 if optsValue.Kind() == reflect.Ptr {
175 optsValue = optsValue.Elem()
176 }
177
178 optsType := reflect.TypeOf(opts)
179 if optsType.Kind() == reflect.Ptr {
180 optsType = optsType.Elem()
181 }
182
183 optsMap := make(map[string]string)
184 if optsValue.Kind() == reflect.Struct {
185 for i := 0; i < optsValue.NumField(); i++ {
186 v := optsValue.Field(i)
187 f := optsType.Field(i)
188 hTag := f.Tag.Get("h")
189
190 // if the field has a 'h' tag, it goes in the header
191 if hTag != "" {
192 tags := strings.Split(hTag, ",")
193
194 // if the field is set, add it to the slice of query pieces
195 if !isZero(v) {
196 switch v.Kind() {
197 case reflect.String:
198 optsMap[tags[0]] = v.String()
199 case reflect.Int:
200 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
201 case reflect.Bool:
202 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
203 }
204 } else {
205 // Otherwise, the field is not set.
206 if len(tags) == 2 && tags[1] == "required" {
207 // And the field is required. Return an error.
208 return optsMap, fmt.Errorf("Required header not set.")
209 }
210 }
211 }
212
213 }
214 return optsMap, nil
215 }
216 // Return an error if the underlying type of 'opts' isn't a struct.
217 return optsMap, fmt.Errorf("Options type is not a struct.")
218}