]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil/build.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / aws / aws-sdk-go / private / protocol / xml / xmlutil / build.go
1 // Package xmlutil provides XML serialization of AWS requests and responses.
2 package xmlutil
3
4 import (
5 "encoding/base64"
6 "encoding/xml"
7 "fmt"
8 "reflect"
9 "sort"
10 "strconv"
11 "time"
12
13 "github.com/aws/aws-sdk-go/private/protocol"
14 )
15
16 // BuildXML will serialize params into an xml.Encoder. Error will be returned
17 // if the serialization of any of the params or nested values fails.
18 func BuildXML(params interface{}, e *xml.Encoder) error {
19 return buildXML(params, e, false)
20 }
21
22 func buildXML(params interface{}, e *xml.Encoder, sorted bool) error {
23 b := xmlBuilder{encoder: e, namespaces: map[string]string{}}
24 root := NewXMLElement(xml.Name{})
25 if err := b.buildValue(reflect.ValueOf(params), root, ""); err != nil {
26 return err
27 }
28 for _, c := range root.Children {
29 for _, v := range c {
30 return StructToXML(e, v, sorted)
31 }
32 }
33 return nil
34 }
35
36 // Returns the reflection element of a value, if it is a pointer.
37 func elemOf(value reflect.Value) reflect.Value {
38 for value.Kind() == reflect.Ptr {
39 value = value.Elem()
40 }
41 return value
42 }
43
44 // A xmlBuilder serializes values from Go code to XML
45 type xmlBuilder struct {
46 encoder *xml.Encoder
47 namespaces map[string]string
48 }
49
50 // buildValue generic XMLNode builder for any type. Will build value for their specific type
51 // struct, list, map, scalar.
52 //
53 // Also takes a "type" tag value to set what type a value should be converted to XMLNode as. If
54 // type is not provided reflect will be used to determine the value's type.
55 func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
56 value = elemOf(value)
57 if !value.IsValid() { // no need to handle zero values
58 return nil
59 } else if tag.Get("location") != "" { // don't handle non-body location values
60 return nil
61 }
62
63 t := tag.Get("type")
64 if t == "" {
65 switch value.Kind() {
66 case reflect.Struct:
67 t = "structure"
68 case reflect.Slice:
69 t = "list"
70 case reflect.Map:
71 t = "map"
72 }
73 }
74
75 switch t {
76 case "structure":
77 if field, ok := value.Type().FieldByName("_"); ok {
78 tag = tag + reflect.StructTag(" ") + field.Tag
79 }
80 return b.buildStruct(value, current, tag)
81 case "list":
82 return b.buildList(value, current, tag)
83 case "map":
84 return b.buildMap(value, current, tag)
85 default:
86 return b.buildScalar(value, current, tag)
87 }
88 }
89
90 // buildStruct adds a struct and its fields to the current XMLNode. All fields and any nested
91 // types are converted to XMLNodes also.
92 func (b *xmlBuilder) buildStruct(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
93 if !value.IsValid() {
94 return nil
95 }
96
97 // unwrap payloads
98 if payload := tag.Get("payload"); payload != "" {
99 field, _ := value.Type().FieldByName(payload)
100 tag = field.Tag
101 value = elemOf(value.FieldByName(payload))
102
103 if !value.IsValid() {
104 return nil
105 }
106 }
107
108 child := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
109
110 // there is an xmlNamespace associated with this struct
111 if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" {
112 ns := xml.Attr{
113 Name: xml.Name{Local: "xmlns"},
114 Value: uri,
115 }
116 if prefix != "" {
117 b.namespaces[prefix] = uri // register the namespace
118 ns.Name.Local = "xmlns:" + prefix
119 }
120
121 child.Attr = append(child.Attr, ns)
122 }
123
124 var payloadFields, nonPayloadFields int
125
126 t := value.Type()
127 for i := 0; i < value.NumField(); i++ {
128 member := elemOf(value.Field(i))
129 field := t.Field(i)
130
131 if field.PkgPath != "" {
132 continue // ignore unexported fields
133 }
134 if field.Tag.Get("ignore") != "" {
135 continue
136 }
137
138 mTag := field.Tag
139 if mTag.Get("location") != "" { // skip non-body members
140 nonPayloadFields++
141 continue
142 }
143 payloadFields++
144
145 if protocol.CanSetIdempotencyToken(value.Field(i), field) {
146 token := protocol.GetIdempotencyToken()
147 member = reflect.ValueOf(token)
148 }
149
150 memberName := mTag.Get("locationName")
151 if memberName == "" {
152 memberName = field.Name
153 mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`)
154 }
155 if err := b.buildValue(member, child, mTag); err != nil {
156 return err
157 }
158 }
159
160 // Only case where the child shape is not added is if the shape only contains
161 // non-payload fields, e.g headers/query.
162 if !(payloadFields == 0 && nonPayloadFields > 0) {
163 current.AddChild(child)
164 }
165
166 return nil
167 }
168
169 // buildList adds the value's list items to the current XMLNode as children nodes. All
170 // nested values in the list are converted to XMLNodes also.
171 func (b *xmlBuilder) buildList(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
172 if value.IsNil() { // don't build omitted lists
173 return nil
174 }
175
176 // check for unflattened list member
177 flattened := tag.Get("flattened") != ""
178
179 xname := xml.Name{Local: tag.Get("locationName")}
180 if flattened {
181 for i := 0; i < value.Len(); i++ {
182 child := NewXMLElement(xname)
183 current.AddChild(child)
184 if err := b.buildValue(value.Index(i), child, ""); err != nil {
185 return err
186 }
187 }
188 } else {
189 list := NewXMLElement(xname)
190 current.AddChild(list)
191
192 for i := 0; i < value.Len(); i++ {
193 iname := tag.Get("locationNameList")
194 if iname == "" {
195 iname = "member"
196 }
197
198 child := NewXMLElement(xml.Name{Local: iname})
199 list.AddChild(child)
200 if err := b.buildValue(value.Index(i), child, ""); err != nil {
201 return err
202 }
203 }
204 }
205
206 return nil
207 }
208
209 // buildMap adds the value's key/value pairs to the current XMLNode as children nodes. All
210 // nested values in the map are converted to XMLNodes also.
211 //
212 // Error will be returned if it is unable to build the map's values into XMLNodes
213 func (b *xmlBuilder) buildMap(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
214 if value.IsNil() { // don't build omitted maps
215 return nil
216 }
217
218 maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
219 current.AddChild(maproot)
220 current = maproot
221
222 kname, vname := "key", "value"
223 if n := tag.Get("locationNameKey"); n != "" {
224 kname = n
225 }
226 if n := tag.Get("locationNameValue"); n != "" {
227 vname = n
228 }
229
230 // sorting is not required for compliance, but it makes testing easier
231 keys := make([]string, value.Len())
232 for i, k := range value.MapKeys() {
233 keys[i] = k.String()
234 }
235 sort.Strings(keys)
236
237 for _, k := range keys {
238 v := value.MapIndex(reflect.ValueOf(k))
239
240 mapcur := current
241 if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps
242 child := NewXMLElement(xml.Name{Local: "entry"})
243 mapcur.AddChild(child)
244 mapcur = child
245 }
246
247 kchild := NewXMLElement(xml.Name{Local: kname})
248 kchild.Text = k
249 vchild := NewXMLElement(xml.Name{Local: vname})
250 mapcur.AddChild(kchild)
251 mapcur.AddChild(vchild)
252
253 if err := b.buildValue(v, vchild, ""); err != nil {
254 return err
255 }
256 }
257
258 return nil
259 }
260
261 // buildScalar will convert the value into a string and append it as a attribute or child
262 // of the current XMLNode.
263 //
264 // The value will be added as an attribute if tag contains a "xmlAttribute" attribute value.
265 //
266 // Error will be returned if the value type is unsupported.
267 func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
268 var str string
269 switch converted := value.Interface().(type) {
270 case string:
271 str = converted
272 case []byte:
273 if !value.IsNil() {
274 str = base64.StdEncoding.EncodeToString(converted)
275 }
276 case bool:
277 str = strconv.FormatBool(converted)
278 case int64:
279 str = strconv.FormatInt(converted, 10)
280 case int:
281 str = strconv.Itoa(converted)
282 case float64:
283 str = strconv.FormatFloat(converted, 'f', -1, 64)
284 case float32:
285 str = strconv.FormatFloat(float64(converted), 'f', -1, 32)
286 case time.Time:
287 format := tag.Get("timestampFormat")
288 if len(format) == 0 {
289 format = protocol.ISO8601TimeFormatName
290 }
291
292 str = protocol.FormatTime(format, converted)
293 default:
294 return fmt.Errorf("unsupported value for param %s: %v (%s)",
295 tag.Get("locationName"), value.Interface(), value.Type().Name())
296 }
297
298 xname := xml.Name{Local: tag.Get("locationName")}
299 if tag.Get("xmlAttribute") != "" { // put into current node's attribute list
300 attr := xml.Attr{Name: xname, Value: str}
301 current.Attr = append(current.Attr, attr)
302 } else { // regular text node
303 current.AddChild(&XMLNode{Name: xname, Text: str})
304 }
305 return nil
306 }