]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package queryutil |
2 | ||
3 | import ( | |
4 | "encoding/base64" | |
5 | "fmt" | |
6 | "net/url" | |
7 | "reflect" | |
8 | "sort" | |
9 | "strconv" | |
10 | "strings" | |
11 | "time" | |
12 | ||
13 | "github.com/aws/aws-sdk-go/private/protocol" | |
14 | ) | |
15 | ||
16 | // Parse parses an object i and fills a url.Values object. The isEC2 flag | |
17 | // indicates if this is the EC2 Query sub-protocol. | |
18 | func Parse(body url.Values, i interface{}, isEC2 bool) error { | |
19 | q := queryParser{isEC2: isEC2} | |
20 | return q.parseValue(body, reflect.ValueOf(i), "", "") | |
21 | } | |
22 | ||
23 | func elemOf(value reflect.Value) reflect.Value { | |
24 | for value.Kind() == reflect.Ptr { | |
25 | value = value.Elem() | |
26 | } | |
27 | return value | |
28 | } | |
29 | ||
30 | type queryParser struct { | |
31 | isEC2 bool | |
32 | } | |
33 | ||
34 | func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { | |
35 | value = elemOf(value) | |
36 | ||
37 | // no need to handle zero values | |
38 | if !value.IsValid() { | |
39 | return nil | |
40 | } | |
41 | ||
42 | t := tag.Get("type") | |
43 | if t == "" { | |
44 | switch value.Kind() { | |
45 | case reflect.Struct: | |
46 | t = "structure" | |
47 | case reflect.Slice: | |
48 | t = "list" | |
49 | case reflect.Map: | |
50 | t = "map" | |
51 | } | |
52 | } | |
53 | ||
54 | switch t { | |
55 | case "structure": | |
56 | return q.parseStruct(v, value, prefix) | |
57 | case "list": | |
58 | return q.parseList(v, value, prefix, tag) | |
59 | case "map": | |
60 | return q.parseMap(v, value, prefix, tag) | |
61 | default: | |
62 | return q.parseScalar(v, value, prefix, tag) | |
63 | } | |
64 | } | |
65 | ||
66 | func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error { | |
67 | if !value.IsValid() { | |
68 | return nil | |
69 | } | |
70 | ||
71 | t := value.Type() | |
72 | for i := 0; i < value.NumField(); i++ { | |
73 | elemValue := elemOf(value.Field(i)) | |
74 | field := t.Field(i) | |
75 | ||
76 | if field.PkgPath != "" { | |
77 | continue // ignore unexported fields | |
78 | } | |
79 | if field.Tag.Get("ignore") != "" { | |
80 | continue | |
81 | } | |
82 | ||
83 | if protocol.CanSetIdempotencyToken(value.Field(i), field) { | |
84 | token := protocol.GetIdempotencyToken() | |
85 | elemValue = reflect.ValueOf(token) | |
86 | } | |
87 | ||
88 | var name string | |
89 | if q.isEC2 { | |
90 | name = field.Tag.Get("queryName") | |
91 | } | |
92 | if name == "" { | |
93 | if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" { | |
94 | name = field.Tag.Get("locationNameList") | |
95 | } else if locName := field.Tag.Get("locationName"); locName != "" { | |
96 | name = locName | |
97 | } | |
98 | if name != "" && q.isEC2 { | |
99 | name = strings.ToUpper(name[0:1]) + name[1:] | |
100 | } | |
101 | } | |
102 | if name == "" { | |
103 | name = field.Name | |
104 | } | |
105 | ||
106 | if prefix != "" { | |
107 | name = prefix + "." + name | |
108 | } | |
109 | ||
110 | if err := q.parseValue(v, elemValue, name, field.Tag); err != nil { | |
111 | return err | |
112 | } | |
113 | } | |
114 | return nil | |
115 | } | |
116 | ||
117 | func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { | |
118 | // If it's empty, generate an empty value | |
119 | if !value.IsNil() && value.Len() == 0 { | |
120 | v.Set(prefix, "") | |
121 | return nil | |
122 | } | |
123 | ||
124 | // check for unflattened list member | |
125 | if !q.isEC2 && tag.Get("flattened") == "" { | |
126 | if listName := tag.Get("locationNameList"); listName == "" { | |
127 | prefix += ".member" | |
128 | } else { | |
129 | prefix += "." + listName | |
130 | } | |
131 | } | |
132 | ||
133 | for i := 0; i < value.Len(); i++ { | |
134 | slicePrefix := prefix | |
135 | if slicePrefix == "" { | |
136 | slicePrefix = strconv.Itoa(i + 1) | |
137 | } else { | |
138 | slicePrefix = slicePrefix + "." + strconv.Itoa(i+1) | |
139 | } | |
140 | if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil { | |
141 | return err | |
142 | } | |
143 | } | |
144 | return nil | |
145 | } | |
146 | ||
147 | func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { | |
148 | // If it's empty, generate an empty value | |
149 | if !value.IsNil() && value.Len() == 0 { | |
150 | v.Set(prefix, "") | |
151 | return nil | |
152 | } | |
153 | ||
154 | // check for unflattened list member | |
155 | if !q.isEC2 && tag.Get("flattened") == "" { | |
156 | prefix += ".entry" | |
157 | } | |
158 | ||
159 | // sort keys for improved serialization consistency. | |
160 | // this is not strictly necessary for protocol support. | |
161 | mapKeyValues := value.MapKeys() | |
162 | mapKeys := map[string]reflect.Value{} | |
163 | mapKeyNames := make([]string, len(mapKeyValues)) | |
164 | for i, mapKey := range mapKeyValues { | |
165 | name := mapKey.String() | |
166 | mapKeys[name] = mapKey | |
167 | mapKeyNames[i] = name | |
168 | } | |
169 | sort.Strings(mapKeyNames) | |
170 | ||
171 | for i, mapKeyName := range mapKeyNames { | |
172 | mapKey := mapKeys[mapKeyName] | |
173 | mapValue := value.MapIndex(mapKey) | |
174 | ||
175 | kname := tag.Get("locationNameKey") | |
176 | if kname == "" { | |
177 | kname = "key" | |
178 | } | |
179 | vname := tag.Get("locationNameValue") | |
180 | if vname == "" { | |
181 | vname = "value" | |
182 | } | |
183 | ||
184 | // serialize key | |
185 | var keyName string | |
186 | if prefix == "" { | |
187 | keyName = strconv.Itoa(i+1) + "." + kname | |
188 | } else { | |
189 | keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname | |
190 | } | |
191 | ||
192 | if err := q.parseValue(v, mapKey, keyName, ""); err != nil { | |
193 | return err | |
194 | } | |
195 | ||
196 | // serialize value | |
197 | var valueName string | |
198 | if prefix == "" { | |
199 | valueName = strconv.Itoa(i+1) + "." + vname | |
200 | } else { | |
201 | valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname | |
202 | } | |
203 | ||
204 | if err := q.parseValue(v, mapValue, valueName, ""); err != nil { | |
205 | return err | |
206 | } | |
207 | } | |
208 | ||
209 | return nil | |
210 | } | |
211 | ||
212 | func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error { | |
213 | switch value := r.Interface().(type) { | |
214 | case string: | |
215 | v.Set(name, value) | |
216 | case []byte: | |
217 | if !r.IsNil() { | |
218 | v.Set(name, base64.StdEncoding.EncodeToString(value)) | |
219 | } | |
220 | case bool: | |
221 | v.Set(name, strconv.FormatBool(value)) | |
222 | case int64: | |
223 | v.Set(name, strconv.FormatInt(value, 10)) | |
224 | case int: | |
225 | v.Set(name, strconv.Itoa(value)) | |
226 | case float64: | |
227 | v.Set(name, strconv.FormatFloat(value, 'f', -1, 64)) | |
228 | case float32: | |
229 | v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32)) | |
230 | case time.Time: | |
231 | const ISO8601UTC = "2006-01-02T15:04:05Z" | |
232 | v.Set(name, value.UTC().Format(ISO8601UTC)) | |
233 | default: | |
234 | return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name()) | |
235 | } | |
236 | return nil | |
237 | } |