1 // Package xmlutil provides XML serialization of AWS requests and responses.
13 "github.com/aws/aws-sdk-go/private/protocol"
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)
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 {
28 for _, c := range root.Children {
30 return StructToXML(e, v, sorted)
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 {
44 // A xmlBuilder serializes values from Go code to XML
45 type xmlBuilder struct {
47 namespaces map[string]string
50 // buildValue generic XMLNode builder for any type. Will build value for their specific type
51 // struct, list, map, scalar.
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 {
57 if !value.IsValid() { // no need to handle zero values
59 } else if tag.Get("location") != "" { // don't handle non-body location values
77 if field, ok := value.Type().FieldByName("_"); ok {
78 tag = tag + reflect.StructTag(" ") + field.Tag
80 return b.buildStruct(value, current, tag)
82 return b.buildList(value, current, tag)
84 return b.buildMap(value, current, tag)
86 return b.buildScalar(value, current, tag)
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 {
98 if payload := tag.Get("payload"); payload != "" {
99 field, _ := value.Type().FieldByName(payload)
101 value = elemOf(value.FieldByName(payload))
103 if !value.IsValid() {
108 child := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
110 // there is an xmlNamespace associated with this struct
111 if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" {
113 Name: xml.Name{Local: "xmlns"},
117 b.namespaces[prefix] = uri // register the namespace
118 ns.Name.Local = "xmlns:" + prefix
121 child.Attr = append(child.Attr, ns)
124 var payloadFields, nonPayloadFields int
127 for i := 0; i < value.NumField(); i++ {
128 member := elemOf(value.Field(i))
131 if field.PkgPath != "" {
132 continue // ignore unexported fields
134 if field.Tag.Get("ignore") != "" {
139 if mTag.Get("location") != "" { // skip non-body members
145 if protocol.CanSetIdempotencyToken(value.Field(i), field) {
146 token := protocol.GetIdempotencyToken()
147 member = reflect.ValueOf(token)
150 memberName := mTag.Get("locationName")
151 if memberName == "" {
152 memberName = field.Name
153 mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`)
155 if err := b.buildValue(member, child, mTag); err != nil {
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)
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
176 // check for unflattened list member
177 flattened := tag.Get("flattened") != ""
179 xname := xml.Name{Local: tag.Get("locationName")}
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 {
189 list := NewXMLElement(xname)
190 current.AddChild(list)
192 for i := 0; i < value.Len(); i++ {
193 iname := tag.Get("locationNameList")
198 child := NewXMLElement(xml.Name{Local: iname})
200 if err := b.buildValue(value.Index(i), child, ""); err != nil {
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.
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
218 maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
219 current.AddChild(maproot)
222 kname, vname := "key", "value"
223 if n := tag.Get("locationNameKey"); n != "" {
226 if n := tag.Get("locationNameValue"); n != "" {
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() {
237 for _, k := range keys {
238 v := value.MapIndex(reflect.ValueOf(k))
241 if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps
242 child := NewXMLElement(xml.Name{Local: "entry"})
243 mapcur.AddChild(child)
247 kchild := NewXMLElement(xml.Name{Local: kname})
249 vchild := NewXMLElement(xml.Name{Local: vname})
250 mapcur.AddChild(kchild)
251 mapcur.AddChild(vchild)
253 if err := b.buildValue(v, vchild, ""); err != nil {
261 // buildScalar will convert the value into a string and append it as a attribute or child
262 // of the current XMLNode.
264 // The value will be added as an attribute if tag contains a "xmlAttribute" attribute value.
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 {
269 switch converted := value.Interface().(type) {
274 str = base64.StdEncoding.EncodeToString(converted)
277 str = strconv.FormatBool(converted)
279 str = strconv.FormatInt(converted, 10)
281 str = strconv.Itoa(converted)
283 str = strconv.FormatFloat(converted, 'f', -1, 64)
285 str = strconv.FormatFloat(float64(converted), 'f', -1, 32)
287 format := tag.Get("timestampFormat")
288 if len(format) == 0 {
289 format = protocol.ISO8601TimeFormatName
292 str = protocol.FormatTime(format, converted)
294 return fmt.Errorf("unsupported value for param %s: %v (%s)",
295 tag.Get("locationName"), value.Interface(), value.Type().Name())
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})