blob: c492ec0f5c28a836ab872d1414d6f08c789f2214 [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
24var t time.Time
25
26func isZero(v reflect.Value) bool {
27 switch v.Kind() {
28 case reflect.Func, reflect.Map, reflect.Slice:
29 return v.IsNil()
30 case reflect.Array:
31 z := true
32 for i := 0; i < v.Len(); i++ {
33 z = z && isZero(v.Index(i))
34 }
35 return z
36 case reflect.Struct:
37 if v.Type() == reflect.TypeOf(t) {
38 if v.Interface().(time.Time).IsZero() {
39 return true
40 }
41 return false
42 }
43 z := true
44 for i := 0; i < v.NumField(); i++ {
45 z = z && isZero(v.Field(i))
46 }
47 return z
48 }
49 // Compare other types directly:
50 z := reflect.Zero(v.Type())
51 return v.Interface() == z.Interface()
52}
53
Jon Perrittde47eac2014-09-30 15:34:17 -050054func BuildQueryString(opts interface{}) (*url.URL, error) {
Jon Perrittf90a43c2014-09-28 20:09:46 -050055 optsValue := reflect.ValueOf(opts)
56 if optsValue.Kind() == reflect.Ptr {
57 optsValue = optsValue.Elem()
58 }
59
60 optsType := reflect.TypeOf(opts)
61 if optsType.Kind() == reflect.Ptr {
62 optsType = optsType.Elem()
63 }
64
65 optsSlice := make([]string, 0)
66 if optsValue.Kind() == reflect.Struct {
67 for i := 0; i < optsValue.NumField(); i++ {
68 v := optsValue.Field(i)
69 f := optsType.Field(i)
70 qTag := f.Tag.Get("q")
71
72 // if the field has a 'q' tag, it goes in the query string
73 if qTag != "" {
74 tags := strings.Split(qTag, ",")
75
76 // if the field is set, add it to the slice of query pieces
77 if !isZero(v) {
78 switch v.Kind() {
79 case reflect.String:
80 optsSlice = append(optsSlice, tags[0]+"="+v.String())
81 case reflect.Int:
82 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatInt(v.Int(), 10))
83 case reflect.Bool:
84 optsSlice = append(optsSlice, tags[0]+"="+strconv.FormatBool(v.Bool()))
85 }
86 } else {
87 // Otherwise, the field is not set.
88 if len(tags) == 2 && tags[1] == "required" {
89 // And the field is required. Return an error.
Jon Perrittdb00ad12014-09-30 16:29:50 -050090 return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
Jon Perrittf90a43c2014-09-28 20:09:46 -050091 }
92 }
93 }
94
95 }
96 // URL encode the string for safety.
Jon Perritt255b6f82014-09-30 16:07:50 -050097 s := strings.Join(optsSlice, "&")
Jon Perrittf90a43c2014-09-28 20:09:46 -050098 if s != "" {
99 s = "?" + s
100 }
Jon Perrittde47eac2014-09-30 15:34:17 -0500101 u, err := url.Parse(s)
102 if err != nil {
103 return nil, err
104 }
105 return u, nil
Jon Perrittf90a43c2014-09-28 20:09:46 -0500106 }
107 // Return an error if the underlying type of 'opts' isn't a struct.
Jon Perrittde47eac2014-09-30 15:34:17 -0500108 return nil, fmt.Errorf("Options type is not a struct.")
Jon Perrittf90a43c2014-09-28 20:09:46 -0500109}
110
111func BuildRequestBody(opts interface{}) (map[string]interface{}, error) {
112 return nil, nil
113}
114
115func BuildHeaders(opts interface{}) (map[string]string, error) {
116 optsValue := reflect.ValueOf(opts)
117 if optsValue.Kind() == reflect.Ptr {
118 optsValue = optsValue.Elem()
119 }
120
121 optsType := reflect.TypeOf(opts)
122 if optsType.Kind() == reflect.Ptr {
123 optsType = optsType.Elem()
124 }
125
126 optsMap := make(map[string]string)
127 if optsValue.Kind() == reflect.Struct {
128 for i := 0; i < optsValue.NumField(); i++ {
129 v := optsValue.Field(i)
130 f := optsType.Field(i)
131 hTag := f.Tag.Get("h")
132
133 // if the field has a 'h' tag, it goes in the header
134 if hTag != "" {
135 tags := strings.Split(hTag, ",")
136
137 // if the field is set, add it to the slice of query pieces
138 if !isZero(v) {
139 switch v.Kind() {
140 case reflect.String:
141 optsMap[tags[0]] = v.String()
142 case reflect.Int:
143 optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
144 case reflect.Bool:
145 optsMap[tags[0]] = strconv.FormatBool(v.Bool())
146 }
147 } else {
148 // Otherwise, the field is not set.
149 if len(tags) == 2 && tags[1] == "required" {
150 // And the field is required. Return an error.
151 return optsMap, fmt.Errorf("Required header not set.")
152 }
153 }
154 }
155
156 }
157 return optsMap, nil
158 }
159 // Return an error if the underlying type of 'opts' isn't a struct.
160 return optsMap, fmt.Errorf("Options type is not a struct.")
161}