github.com/aavshr/aws-sdk-go@v1.41.3/private/protocol/xml/xmlutil/build.go (about)

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