blob: 26c48c015be7cfcfddbbc336ef8e543b2a89d9af [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 Hannaford6abf9282014-09-24 10:54:13 +020012// MaybeString takes a string that might be a zero-value, and either returns a
13// pointer to its address or a nil value (i.e. empty pointer). This is useful
14// for converting zero values in options structs when the end-user hasn't
15// defined values. Those zero values need to be nil in order for the JSON
16// serialization to ignore them.
17func MaybeString(original string) *string {
18 if original != "" {
19 return &original
20 }
21 return nil
22}
Jon Perrittf90a43c2014-09-28 20:09:46 -050023
Jamie Hannafordcb12ee62014-10-06 15:35:36 +020024// MaybeInt takes an int that might be a zero-value, and either returns a
25// pointer to its address or a nil value (i.e. empty pointer).
Jon Perritt8d262582014-10-03 11:11:46 -050026func MaybeInt(original int) *int {
27 if original != 0 {
28 return &original
29 }
30 return nil
31}
32
Jon Perrittf90a43c2014-09-28 20:09:46 -050033var t time.Time
34
35func isZero(v reflect.Value) bool {
36 switch v.Kind() {
37 case reflect.Func, reflect.Map, reflect.Slice:
38 return v.IsNil()
39 case reflect.Array:
40 z := true
41 for i := 0; i < v.Len(); i++ {
42 z = z && isZero(v.Index(i))
43 }
44 return z
45 case reflect.Struct:
46 if v.Type() == reflect.TypeOf(t) {
47 if v.Interface().(time.Time).IsZero() {
48 return true
49 }
50 return false
51 }
52 z := true
53 for i := 0; i < v.NumField(); i++ {
54 z = z && isZero(v.Field(i))
55 }
56 return z
57 }
58 // Compare other types directly:
59 z := reflect.Zero(v.Type())
60 return v.Interface() == z.Interface()
61}
62
Jamie Hannafordcb12ee62014-10-06 15:35:36 +020063// BuildQueryString accepts a generic structure and parses it URL struct. It
64// converts field names into query names based on tags. So for example, this
65// type:
66//
67// struct {
68// Bar string `q:"x_bar"`
69// Baz int `q:"lorem_ipsum"`
70// }{
71// Bar: "XXX",
72// Baz: "YYY",
73// }
74//
75// will be converted into ?x_bar=XXX&lorem_ipsum=YYYY
Jon Perrittde47eac2014-09-30 15:34:17 -050076func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -050077 optsValue := reflect.ValueOf(opts)
78 if optsValue.Kind() == reflect.Ptr {
79 optsValue = optsValue.Elem()
80 }
81
82 optsType := reflect.TypeOf(opts)
83 if optsType.Kind() == reflect.Ptr {
84 optsType = optsType.Elem()
85 }
86
Jamie Hannafordcb12ee62014-10-06 15:35:36 +020087 var optsSlice []string
Jon Perrittf90a43c2014-09-28 20:09:46 -050088 if optsValue.Kind() == reflect.Struct {
89 for i := 0; i < optsValue.NumField(); i++ {
90 v := optsValue.Field(i)
91 f := optsType.Field(i)
92 qTag := f.Tag.Get("q")
93
94 // if the field has a 'q' tag, it goes in the query string
95 if qTag != "" {
96 tags := strings.Split(qTag, ",")
97
98 // if the field is set, add it to the slice of query pieces
99 if !isZero(v) {
100 switch v.Kind() {
101 case reflect.String:
102 optsSlice = append(optsSlice, tags[0]+"="+v.String())
103 case reflect.Int:
104 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatInt(v.Int(), 10))
105 case reflect.Bool:
106 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatBool(v.Bool()))
107 }
108 } else {
109 // Otherwise, the field is not set.
110 if len(tags) == 2 && tags[1] == "required" {
111 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -0500112 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -0500113 }
114 }
115 }
116
117 }
118 // URL encode the string for safety.
Jon Perritt255b6f82014-09-30 16:07:50 -0500119 s := strings.Join(optsSlice, "&")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500120 if s != "" {
121 s = "?" + s
122 }
Jon Perrittde47eac2014-09-30 15:34:17 -0500123 u, err := url.Parse(s)
124 if err != nil {
125 return nil, err
126 }
127 return u, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500128 }
129 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500130 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500131}
132
Jamie Hannafordcb12ee62014-10-06 15:35:36 +0200133// BuildHeaders accepts a generic structure and parses it string map. It
134// converts field names into header names based on "h" tags, and field values
135// into header values by a simple one-to-one mapping.
Jon Perrittf90a43c2014-09-28 20:09:46 -0500136func BuildHeaders(opts interface{}) (map[string]string, error) {
137 optsValue := reflect.ValueOf(opts)
138 if optsValue.Kind() == reflect.Ptr {
139 optsValue = optsValue.Elem()
140 }
141
142 optsType := reflect.TypeOf(opts)
143 if optsType.Kind() == reflect.Ptr {
144 optsType = optsType.Elem()
145 }
146
147 optsMap := make(map[string]string)
148 if optsValue.Kind() == reflect.Struct {
149 for i := 0; i < optsValue.NumField(); i++ {
150 v := optsValue.Field(i)
151 f := optsType.Field(i)
152 hTag := f.Tag.Get("h")
153
154 // if the field has a 'h' tag, it goes in the header
155 if hTag != "" {
156 tags := strings.Split(hTag, ",")
157
158 // if the field is set, add it to the slice of query pieces
159 if !isZero(v) {
160 switch v.Kind() {
161 case reflect.String:
162 optsMap[tags[0]] = v.String()
163 case reflect.Int:
164 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
165 case reflect.Bool:
166 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
167 }
168 } else {
169 // Otherwise, the field is not set.
170 if len(tags) == 2 && tags[1] == "required" {
171 // And the field is required. Return an error.
172 return optsMap, fmt.Errorf("Required header not set.")
173 }
174 }
175 }
176
177 }
178 return optsMap, nil
179 }
180 // Return an error if the underlying type of 'opts' isn't a struct.
181 return optsMap, fmt.Errorf("Options type is not a struct.")
182}