Add route53 plugin (#1390)

* Update vendor

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Add route53 plugin

This fix adds route53 plugin so that it is possible to
query route53 record through CoreDNS.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2018-01-15 09:59:29 -08:00 committed by GitHub
parent d699b89063
commit 584dd87c70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
352 changed files with 81636 additions and 1798 deletions

4
vendor/github.com/aws/aws-sdk-go/private/README.md generated vendored Normal file
View file

@ -0,0 +1,4 @@
## AWS SDK for Go Private packages ##
`private` is a collection of packages used internally by the SDK, and is subject to have breaking changes. This package is not `internal` so that if you really need to use its functionality, and understand breaking changes will be made, you are able to.
These packages will be refactored in the future so that the API generator and model parsers are exposed cleanly on their own. Making it easier for you to generate your own code based on the API models.

View file

@ -0,0 +1,75 @@
package protocol
import (
"crypto/rand"
"fmt"
"reflect"
)
// RandReader is the random reader the protocol package will use to read
// random bytes from. This is exported for testing, and should not be used.
var RandReader = rand.Reader
const idempotencyTokenFillTag = `idempotencyToken`
// CanSetIdempotencyToken returns true if the struct field should be
// automatically populated with a Idempotency token.
//
// Only *string and string type fields that are tagged with idempotencyToken
// which are not already set can be auto filled.
func CanSetIdempotencyToken(v reflect.Value, f reflect.StructField) bool {
switch u := v.Interface().(type) {
// To auto fill an Idempotency token the field must be a string,
// tagged for auto fill, and have a zero value.
case *string:
return u == nil && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
case string:
return len(u) == 0 && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
}
return false
}
// GetIdempotencyToken returns a randomly generated idempotency token.
func GetIdempotencyToken() string {
b := make([]byte, 16)
RandReader.Read(b)
return UUIDVersion4(b)
}
// SetIdempotencyToken will set the value provided with a Idempotency Token.
// Given that the value can be set. Will panic if value is not setable.
func SetIdempotencyToken(v reflect.Value) {
if v.Kind() == reflect.Ptr {
if v.IsNil() && v.CanSet() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
v = reflect.Indirect(v)
if !v.CanSet() {
panic(fmt.Sprintf("unable to set idempotnecy token %v", v))
}
b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
// TODO handle error
return
}
v.Set(reflect.ValueOf(UUIDVersion4(b)))
}
// UUIDVersion4 returns a Version 4 random UUID from the byte slice provided
func UUIDVersion4(u []byte) string {
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
// 13th character is "4"
u[6] = (u[6] | 0x40) & 0x4F
// 17th character is "8", "9", "a", or "b"
u[8] = (u[8] | 0x80) & 0xBF
return fmt.Sprintf(`%X-%X-%X-%X-%X`, u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}

View file

@ -0,0 +1,106 @@
package protocol_test
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/stretchr/testify/assert"
)
func TestCanSetIdempotencyToken(t *testing.T) {
cases := []struct {
CanSet bool
Case interface{}
}{
{
true,
struct {
Field *string `idempotencyToken:"true"`
}{},
},
{
true,
struct {
Field string `idempotencyToken:"true"`
}{},
},
{
false,
struct {
Field *string `idempotencyToken:"true"`
}{Field: new(string)},
},
{
false,
struct {
Field string `idempotencyToken:"true"`
}{Field: "value"},
},
{
false,
struct {
Field *int `idempotencyToken:"true"`
}{},
},
{
false,
struct {
Field *string
}{},
},
}
for i, c := range cases {
v := reflect.Indirect(reflect.ValueOf(c.Case))
ty := v.Type()
canSet := protocol.CanSetIdempotencyToken(v.Field(0), ty.Field(0))
assert.Equal(t, c.CanSet, canSet, "Expect case %d can set to match", i)
}
}
func TestSetIdempotencyToken(t *testing.T) {
cases := []struct {
Case interface{}
}{
{
&struct {
Field *string `idempotencyToken:"true"`
}{},
},
{
&struct {
Field string `idempotencyToken:"true"`
}{},
},
{
&struct {
Field *string `idempotencyToken:"true"`
}{Field: new(string)},
},
{
&struct {
Field string `idempotencyToken:"true"`
}{Field: ""},
},
}
for i, c := range cases {
v := reflect.Indirect(reflect.ValueOf(c.Case))
protocol.SetIdempotencyToken(v.Field(0))
assert.NotEmpty(t, v.Field(0).Interface(), "Expect case %d to be set", i)
}
}
func TestUUIDVersion4(t *testing.T) {
uuid := protocol.UUIDVersion4(make([]byte, 16))
assert.Equal(t, `00000000-0000-4000-8000-000000000000`, uuid)
b := make([]byte, 16)
for i := 0; i < len(b); i++ {
b[i] = 1
}
uuid = protocol.UUIDVersion4(b)
assert.Equal(t, `01010101-0101-4101-8101-010101010101`, uuid)
}

View file

@ -0,0 +1,76 @@
package protocol
import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"github.com/aws/aws-sdk-go/aws"
)
// EscapeMode is the mode that should be use for escaping a value
type EscapeMode uint
// The modes for escaping a value before it is marshaled, and unmarshaled.
const (
NoEscape EscapeMode = iota
Base64Escape
QuotedEscape
)
// EncodeJSONValue marshals the value into a JSON string, and optionally base64
// encodes the string before returning it.
//
// Will panic if the escape mode is unknown.
func EncodeJSONValue(v aws.JSONValue, escape EscapeMode) (string, error) {
b, err := json.Marshal(v)
if err != nil {
return "", err
}
switch escape {
case NoEscape:
return string(b), nil
case Base64Escape:
return base64.StdEncoding.EncodeToString(b), nil
case QuotedEscape:
return strconv.Quote(string(b)), nil
}
panic(fmt.Sprintf("EncodeJSONValue called with unknown EscapeMode, %v", escape))
}
// DecodeJSONValue will attempt to decode the string input as a JSONValue.
// Optionally decoding base64 the value first before JSON unmarshaling.
//
// Will panic if the escape mode is unknown.
func DecodeJSONValue(v string, escape EscapeMode) (aws.JSONValue, error) {
var b []byte
var err error
switch escape {
case NoEscape:
b = []byte(v)
case Base64Escape:
b, err = base64.StdEncoding.DecodeString(v)
case QuotedEscape:
var u string
u, err = strconv.Unquote(v)
b = []byte(u)
default:
panic(fmt.Sprintf("DecodeJSONValue called with unknown EscapeMode, %v", escape))
}
if err != nil {
return nil, err
}
m := aws.JSONValue{}
err = json.Unmarshal(b, &m)
if err != nil {
return nil, err
}
return m, nil
}

View file

@ -0,0 +1,93 @@
package protocol
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
)
var testJSONValueCases = []struct {
Value aws.JSONValue
Mode EscapeMode
String string
}{
{
Value: aws.JSONValue{
"abc": 123.,
},
Mode: NoEscape,
String: `{"abc":123}`,
},
{
Value: aws.JSONValue{
"abc": 123.,
},
Mode: Base64Escape,
String: `eyJhYmMiOjEyM30=`,
},
{
Value: aws.JSONValue{
"abc": 123.,
},
Mode: QuotedEscape,
String: `"{\"abc\":123}"`,
},
}
func TestEncodeJSONValue(t *testing.T) {
for i, c := range testJSONValueCases {
str, err := EncodeJSONValue(c.Value, c.Mode)
if err != nil {
t.Fatalf("%d, expect no error, got %v", i, err)
}
if e, a := c.String, str; e != a {
t.Errorf("%d, expect %v encoded value, got %v", i, e, a)
}
}
}
func TestDecodeJSONValue(t *testing.T) {
for i, c := range testJSONValueCases {
val, err := DecodeJSONValue(c.String, c.Mode)
if err != nil {
t.Fatalf("%d, expect no error, got %v", i, err)
}
if e, a := c.Value, val; !reflect.DeepEqual(e, a) {
t.Errorf("%d, expect %v encoded value, got %v", i, e, a)
}
}
}
func TestEncodeJSONValue_PanicUnkownMode(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expect panic, got none")
} else {
reason := fmt.Sprintf("%v", r)
if e, a := "unknown EscapeMode", reason; !strings.Contains(a, e) {
t.Errorf("expect %q to be in %v", e, a)
}
}
}()
val := aws.JSONValue{}
EncodeJSONValue(val, 123456)
}
func TestDecodeJSONValue_PanicUnkownMode(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expect panic, got none")
} else {
reason := fmt.Sprintf("%v", r)
if e, a := "unknown EscapeMode", reason; !strings.Contains(a, e) {
t.Errorf("expect %q to be in %v", e, a)
}
}
}()
DecodeJSONValue(`{"abc":123}`, 123456)
}

View file

@ -0,0 +1,203 @@
package protocol_test
import (
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/awstesting"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/ec2query"
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
"github.com/aws/aws-sdk-go/private/protocol/query"
"github.com/aws/aws-sdk-go/private/protocol/rest"
"github.com/aws/aws-sdk-go/private/protocol/restjson"
"github.com/aws/aws-sdk-go/private/protocol/restxml"
)
func xmlData(set bool, b []byte, size, delta int) {
const openingTags = "<B><A>"
const closingTags = "</A></B>"
if !set {
copy(b, []byte(openingTags))
}
if size == 0 {
copy(b[delta-len(closingTags):], []byte(closingTags))
}
}
func jsonData(set bool, b []byte, size, delta int) {
if !set {
copy(b, []byte("{\"A\": \""))
}
if size == 0 {
copy(b[delta-len("\"}"):], []byte("\"}"))
}
}
func buildNewRequest(data interface{}) *request.Request {
v := url.Values{}
v.Set("test", "TEST")
v.Add("test1", "TEST1")
req := &request.Request{
HTTPRequest: &http.Request{
Header: make(http.Header),
Body: &awstesting.ReadCloser{Size: 2048},
URL: &url.URL{
RawQuery: v.Encode(),
},
},
Params: &struct {
LocationName string `locationName:"test"`
}{
"Test",
},
ClientInfo: metadata.ClientInfo{
ServiceName: "test",
TargetPrefix: "test",
JSONVersion: "test",
APIVersion: "test",
Endpoint: "test",
SigningName: "test",
SigningRegion: "test",
},
Operation: &request.Operation{
Name: "test",
},
}
req.HTTPResponse = &http.Response{
Body: &awstesting.ReadCloser{Size: 2048},
Header: http.Header{
"X-Amzn-Requestid": []string{"1"},
},
StatusCode: http.StatusOK,
}
if data == nil {
data = &struct {
_ struct{} `type:"structure"`
LocationName *string `locationName:"testName"`
Location *string `location:"statusCode"`
A *string `type:"string"`
}{}
}
req.Data = data
return req
}
type expected struct {
dataType int
closed bool
size int
errExists bool
}
const (
jsonType = iota
xmlType
)
func checkForLeak(data interface{}, build, fn func(*request.Request), t *testing.T, result expected) {
req := buildNewRequest(data)
reader := req.HTTPResponse.Body.(*awstesting.ReadCloser)
switch result.dataType {
case jsonType:
reader.FillData = jsonData
case xmlType:
reader.FillData = xmlData
}
build(req)
fn(req)
if result.errExists {
assert.NotNil(t, req.Error)
} else {
assert.Nil(t, req.Error)
}
assert.Equal(t, reader.Closed, result.closed)
assert.Equal(t, reader.Size, result.size)
}
func TestJSONRpc(t *testing.T) {
checkForLeak(nil, jsonrpc.Build, jsonrpc.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(nil, jsonrpc.Build, jsonrpc.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
checkForLeak(nil, jsonrpc.Build, jsonrpc.UnmarshalError, t, expected{jsonType, true, 0, true})
}
func TestQuery(t *testing.T) {
checkForLeak(nil, query.Build, query.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(nil, query.Build, query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
checkForLeak(nil, query.Build, query.UnmarshalError, t, expected{jsonType, true, 0, true})
}
func TestRest(t *testing.T) {
// case 1: Payload io.ReadSeeker
checkForLeak(nil, rest.Build, rest.Unmarshal, t, expected{jsonType, false, 2048, false})
checkForLeak(nil, query.Build, query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
// case 2: Payload *string
// should close the body
dataStr := struct {
_ struct{} `type:"structure" payload:"Payload"`
LocationName *string `locationName:"testName"`
Location *string `location:"statusCode"`
A *string `type:"string"`
Payload *string `locationName:"payload" type:"blob" required:"true"`
}{}
checkForLeak(&dataStr, rest.Build, rest.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(&dataStr, query.Build, query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
// case 3: Payload []byte
// should close the body
dataBytes := struct {
_ struct{} `type:"structure" payload:"Payload"`
LocationName *string `locationName:"testName"`
Location *string `location:"statusCode"`
A *string `type:"string"`
Payload []byte `locationName:"payload" type:"blob" required:"true"`
}{}
checkForLeak(&dataBytes, rest.Build, rest.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(&dataBytes, query.Build, query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
// case 4: Payload unsupported type
// should close the body
dataUnsupported := struct {
_ struct{} `type:"structure" payload:"Payload"`
LocationName *string `locationName:"testName"`
Location *string `location:"statusCode"`
A *string `type:"string"`
Payload string `locationName:"payload" type:"blob" required:"true"`
}{}
checkForLeak(&dataUnsupported, rest.Build, rest.Unmarshal, t, expected{jsonType, true, 0, true})
checkForLeak(&dataUnsupported, query.Build, query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
}
func TestRestJSON(t *testing.T) {
checkForLeak(nil, restjson.Build, restjson.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(nil, restjson.Build, restjson.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
checkForLeak(nil, restjson.Build, restjson.UnmarshalError, t, expected{jsonType, true, 0, true})
}
func TestRestXML(t *testing.T) {
checkForLeak(nil, restxml.Build, restxml.Unmarshal, t, expected{xmlType, true, 0, false})
checkForLeak(nil, restxml.Build, restxml.UnmarshalMeta, t, expected{xmlType, false, 2048, false})
checkForLeak(nil, restxml.Build, restxml.UnmarshalError, t, expected{xmlType, true, 0, true})
}
func TestXML(t *testing.T) {
checkForLeak(nil, ec2query.Build, ec2query.Unmarshal, t, expected{jsonType, true, 0, false})
checkForLeak(nil, ec2query.Build, ec2query.UnmarshalMeta, t, expected{jsonType, false, 2048, false})
checkForLeak(nil, ec2query.Build, ec2query.UnmarshalError, t, expected{jsonType, true, 0, true})
}
func TestProtocol(t *testing.T) {
checkForLeak(nil, restxml.Build, protocol.UnmarshalDiscardBody, t, expected{xmlType, true, 0, false})
}

View file

@ -0,0 +1,36 @@
// Package query provides serialization of AWS query requests, and responses.
package query
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/query.json build_test.go
import (
"net/url"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/query/queryutil"
)
// BuildHandler is a named request handler for building query protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.query.Build", Fn: Build}
// Build builds a request for an AWS Query service.
func Build(r *request.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.ClientInfo.APIVersion},
}
if err := queryutil.Parse(body, r.Params, false); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding Query request", err)
return
}
if r.ExpireTime == 0 {
r.HTTPRequest.Method = "POST"
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
r.SetBufferBody([]byte(body.Encode()))
} else { // This is a pre-signed request
r.HTTPRequest.Method = "GET"
r.HTTPRequest.URL.RawQuery = body.Encode()
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
package queryutil
import (
"encoding/base64"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/private/protocol"
)
// Parse parses an object i and fills a url.Values object. The isEC2 flag
// indicates if this is the EC2 Query sub-protocol.
func Parse(body url.Values, i interface{}, isEC2 bool) error {
q := queryParser{isEC2: isEC2}
return q.parseValue(body, reflect.ValueOf(i), "", "")
}
func elemOf(value reflect.Value) reflect.Value {
for value.Kind() == reflect.Ptr {
value = value.Elem()
}
return value
}
type queryParser struct {
isEC2 bool
}
func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
value = elemOf(value)
// no need to handle zero values
if !value.IsValid() {
return nil
}
t := tag.Get("type")
if t == "" {
switch value.Kind() {
case reflect.Struct:
t = "structure"
case reflect.Slice:
t = "list"
case reflect.Map:
t = "map"
}
}
switch t {
case "structure":
return q.parseStruct(v, value, prefix)
case "list":
return q.parseList(v, value, prefix, tag)
case "map":
return q.parseMap(v, value, prefix, tag)
default:
return q.parseScalar(v, value, prefix, tag)
}
}
func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error {
if !value.IsValid() {
return nil
}
t := value.Type()
for i := 0; i < value.NumField(); i++ {
elemValue := elemOf(value.Field(i))
field := t.Field(i)
if field.PkgPath != "" {
continue // ignore unexported fields
}
if field.Tag.Get("ignore") != "" {
continue
}
if protocol.CanSetIdempotencyToken(value.Field(i), field) {
token := protocol.GetIdempotencyToken()
elemValue = reflect.ValueOf(token)
}
var name string
if q.isEC2 {
name = field.Tag.Get("queryName")
}
if name == "" {
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
name = field.Tag.Get("locationNameList")
} else if locName := field.Tag.Get("locationName"); locName != "" {
name = locName
}
if name != "" && q.isEC2 {
name = strings.ToUpper(name[0:1]) + name[1:]
}
}
if name == "" {
name = field.Name
}
if prefix != "" {
name = prefix + "." + name
}
if err := q.parseValue(v, elemValue, name, field.Tag); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
// If it's empty, generate an empty value
if !value.IsNil() && value.Len() == 0 {
v.Set(prefix, "")
return nil
}
if _, ok := value.Interface().([]byte); ok {
return q.parseScalar(v, value, prefix, tag)
}
// check for unflattened list member
if !q.isEC2 && tag.Get("flattened") == "" {
if listName := tag.Get("locationNameList"); listName == "" {
prefix += ".member"
} else {
prefix += "." + listName
}
}
for i := 0; i < value.Len(); i++ {
slicePrefix := prefix
if slicePrefix == "" {
slicePrefix = strconv.Itoa(i + 1)
} else {
slicePrefix = slicePrefix + "." + strconv.Itoa(i+1)
}
if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
// If it's empty, generate an empty value
if !value.IsNil() && value.Len() == 0 {
v.Set(prefix, "")
return nil
}
// check for unflattened list member
if !q.isEC2 && tag.Get("flattened") == "" {
prefix += ".entry"
}
// sort keys for improved serialization consistency.
// this is not strictly necessary for protocol support.
mapKeyValues := value.MapKeys()
mapKeys := map[string]reflect.Value{}
mapKeyNames := make([]string, len(mapKeyValues))
for i, mapKey := range mapKeyValues {
name := mapKey.String()
mapKeys[name] = mapKey
mapKeyNames[i] = name
}
sort.Strings(mapKeyNames)
for i, mapKeyName := range mapKeyNames {
mapKey := mapKeys[mapKeyName]
mapValue := value.MapIndex(mapKey)
kname := tag.Get("locationNameKey")
if kname == "" {
kname = "key"
}
vname := tag.Get("locationNameValue")
if vname == "" {
vname = "value"
}
// serialize key
var keyName string
if prefix == "" {
keyName = strconv.Itoa(i+1) + "." + kname
} else {
keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname
}
if err := q.parseValue(v, mapKey, keyName, ""); err != nil {
return err
}
// serialize value
var valueName string
if prefix == "" {
valueName = strconv.Itoa(i+1) + "." + vname
} else {
valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname
}
if err := q.parseValue(v, mapValue, valueName, ""); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error {
switch value := r.Interface().(type) {
case string:
v.Set(name, value)
case []byte:
if !r.IsNil() {
v.Set(name, base64.StdEncoding.EncodeToString(value))
}
case bool:
v.Set(name, strconv.FormatBool(value))
case int64:
v.Set(name, strconv.FormatInt(value, 10))
case int:
v.Set(name, strconv.Itoa(value))
case float64:
v.Set(name, strconv.FormatFloat(value, 'f', -1, 64))
case float32:
v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32))
case time.Time:
const ISO8601UTC = "2006-01-02T15:04:05Z"
v.Set(name, value.UTC().Format(ISO8601UTC))
default:
return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name())
}
return nil
}

View file

@ -0,0 +1,35 @@
package query
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/query.json unmarshal_test.go
import (
"encoding/xml"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
)
// UnmarshalHandler is a named request handler for unmarshaling query protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.query.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling query protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.query.UnmarshalMeta", Fn: UnmarshalMeta}
// Unmarshal unmarshals a response for an AWS Query service.
func Unmarshal(r *request.Request) {
defer r.HTTPResponse.Body.Close()
if r.DataFilled() {
decoder := xml.NewDecoder(r.HTTPResponse.Body)
err := xmlutil.UnmarshalXML(r.Data, decoder, r.Operation.Name+"Result")
if err != nil {
r.Error = awserr.New("SerializationError", "failed decoding Query response", err)
return
}
}
}
// UnmarshalMeta unmarshals header response values for an AWS Query service.
func UnmarshalMeta(r *request.Request) {
r.RequestID = r.HTTPResponse.Header.Get("X-Amzn-Requestid")
}

View file

@ -0,0 +1,66 @@
package query
import (
"encoding/xml"
"io/ioutil"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
type xmlErrorResponse struct {
XMLName xml.Name `xml:"ErrorResponse"`
Code string `xml:"Error>Code"`
Message string `xml:"Error>Message"`
RequestID string `xml:"RequestId"`
}
type xmlServiceUnavailableResponse struct {
XMLName xml.Name `xml:"ServiceUnavailableException"`
}
// UnmarshalErrorHandler is a name request handler to unmarshal request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.query.UnmarshalError", Fn: UnmarshalError}
// UnmarshalError unmarshals an error response for an AWS Query service.
func UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
bodyBytes, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "failed to read from query HTTP response body", err)
return
}
// First check for specific error
resp := xmlErrorResponse{}
decodeErr := xml.Unmarshal(bodyBytes, &resp)
if decodeErr == nil {
reqID := resp.RequestID
if reqID == "" {
reqID = r.RequestID
}
r.Error = awserr.NewRequestFailure(
awserr.New(resp.Code, resp.Message, nil),
r.HTTPResponse.StatusCode,
reqID,
)
return
}
// Check for unhandled error
servUnavailResp := xmlServiceUnavailableResponse{}
unavailErr := xml.Unmarshal(bodyBytes, &servUnavailResp)
if unavailErr == nil {
r.Error = awserr.NewRequestFailure(
awserr.New("ServiceUnavailableException", "service is unavailable", nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
return
}
// Failed to retrieve any error message from the response body
r.Error = awserr.New("SerializationError",
"failed to decode query XML error response", decodeErr)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,291 @@
// Package rest provides RESTful serialization of AWS requests and responses.
package rest
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"path"
"reflect"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
)
// RFC822 returns an RFC822 formatted timestamp for AWS protocols
const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT"
// Whether the byte value can be sent without escaping in AWS URLs
var noEscape [256]bool
var errValueNotSet = fmt.Errorf("value not set")
func init() {
for i := 0; i < len(noEscape); i++ {
// AWS expects every character except these to be escaped
noEscape[i] = (i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
i == '-' ||
i == '.' ||
i == '_' ||
i == '~'
}
}
// BuildHandler is a named request handler for building rest protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.rest.Build", Fn: Build}
// Build builds the REST component of a service request.
func Build(r *request.Request) {
if r.ParamsFilled() {
v := reflect.ValueOf(r.Params).Elem()
buildLocationElements(r, v, false)
buildBody(r, v)
}
}
// BuildAsGET builds the REST component of a service request with the ability to hoist
// data from the body.
func BuildAsGET(r *request.Request) {
if r.ParamsFilled() {
v := reflect.ValueOf(r.Params).Elem()
buildLocationElements(r, v, true)
buildBody(r, v)
}
}
func buildLocationElements(r *request.Request, v reflect.Value, buildGETQuery bool) {
query := r.HTTPRequest.URL.Query()
// Setup the raw path to match the base path pattern. This is needed
// so that when the path is mutated a custom escaped version can be
// stored in RawPath that will be used by the Go client.
r.HTTPRequest.URL.RawPath = r.HTTPRequest.URL.Path
for i := 0; i < v.NumField(); i++ {
m := v.Field(i)
if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
continue
}
if m.IsValid() {
field := v.Type().Field(i)
name := field.Tag.Get("locationName")
if name == "" {
name = field.Name
}
if kind := m.Kind(); kind == reflect.Ptr {
m = m.Elem()
} else if kind == reflect.Interface {
if !m.Elem().IsValid() {
continue
}
}
if !m.IsValid() {
continue
}
if field.Tag.Get("ignore") != "" {
continue
}
var err error
switch field.Tag.Get("location") {
case "headers": // header maps
err = buildHeaderMap(&r.HTTPRequest.Header, m, field.Tag)
case "header":
err = buildHeader(&r.HTTPRequest.Header, m, name, field.Tag)
case "uri":
err = buildURI(r.HTTPRequest.URL, m, name, field.Tag)
case "querystring":
err = buildQueryString(query, m, name, field.Tag)
default:
if buildGETQuery {
err = buildQueryString(query, m, name, field.Tag)
}
}
r.Error = err
}
if r.Error != nil {
return
}
}
r.HTTPRequest.URL.RawQuery = query.Encode()
if !aws.BoolValue(r.Config.DisableRestProtocolURICleaning) {
cleanPath(r.HTTPRequest.URL)
}
}
func buildBody(r *request.Request, v reflect.Value) {
if field, ok := v.Type().FieldByName("_"); ok {
if payloadName := field.Tag.Get("payload"); payloadName != "" {
pfield, _ := v.Type().FieldByName(payloadName)
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
payload := reflect.Indirect(v.FieldByName(payloadName))
if payload.IsValid() && payload.Interface() != nil {
switch reader := payload.Interface().(type) {
case io.ReadSeeker:
r.SetReaderBody(reader)
case []byte:
r.SetBufferBody(reader)
case string:
r.SetStringBody(reader)
default:
r.Error = awserr.New("SerializationError",
"failed to encode REST request",
fmt.Errorf("unknown payload type %s", payload.Type()))
}
}
}
}
}
}
func buildHeader(header *http.Header, v reflect.Value, name string, tag reflect.StructTag) error {
str, err := convertType(v, tag)
if err == errValueNotSet {
return nil
} else if err != nil {
return awserr.New("SerializationError", "failed to encode REST request", err)
}
header.Add(name, str)
return nil
}
func buildHeaderMap(header *http.Header, v reflect.Value, tag reflect.StructTag) error {
prefix := tag.Get("locationName")
for _, key := range v.MapKeys() {
str, err := convertType(v.MapIndex(key), tag)
if err == errValueNotSet {
continue
} else if err != nil {
return awserr.New("SerializationError", "failed to encode REST request", err)
}
header.Add(prefix+key.String(), str)
}
return nil
}
func buildURI(u *url.URL, v reflect.Value, name string, tag reflect.StructTag) error {
value, err := convertType(v, tag)
if err == errValueNotSet {
return nil
} else if err != nil {
return awserr.New("SerializationError", "failed to encode REST request", err)
}
u.Path = strings.Replace(u.Path, "{"+name+"}", value, -1)
u.Path = strings.Replace(u.Path, "{"+name+"+}", value, -1)
u.RawPath = strings.Replace(u.RawPath, "{"+name+"}", EscapePath(value, true), -1)
u.RawPath = strings.Replace(u.RawPath, "{"+name+"+}", EscapePath(value, false), -1)
return nil
}
func buildQueryString(query url.Values, v reflect.Value, name string, tag reflect.StructTag) error {
switch value := v.Interface().(type) {
case []*string:
for _, item := range value {
query.Add(name, *item)
}
case map[string]*string:
for key, item := range value {
query.Add(key, *item)
}
case map[string][]*string:
for key, items := range value {
for _, item := range items {
query.Add(key, *item)
}
}
default:
str, err := convertType(v, tag)
if err == errValueNotSet {
return nil
} else if err != nil {
return awserr.New("SerializationError", "failed to encode REST request", err)
}
query.Set(name, str)
}
return nil
}
func cleanPath(u *url.URL) {
hasSlash := strings.HasSuffix(u.Path, "/")
// clean up path, removing duplicate `/`
u.Path = path.Clean(u.Path)
u.RawPath = path.Clean(u.RawPath)
if hasSlash && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
u.RawPath += "/"
}
}
// EscapePath escapes part of a URL path in Amazon style
func EscapePath(path string, encodeSep bool) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
if noEscape[c] || (c == '/' && !encodeSep) {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
func convertType(v reflect.Value, tag reflect.StructTag) (str string, err error) {
v = reflect.Indirect(v)
if !v.IsValid() {
return "", errValueNotSet
}
switch value := v.Interface().(type) {
case string:
str = value
case []byte:
str = base64.StdEncoding.EncodeToString(value)
case bool:
str = strconv.FormatBool(value)
case int64:
str = strconv.FormatInt(value, 10)
case float64:
str = strconv.FormatFloat(value, 'f', -1, 64)
case time.Time:
str = value.UTC().Format(RFC822)
case aws.JSONValue:
if len(value) == 0 {
return "", errValueNotSet
}
escaping := protocol.NoEscape
if tag.Get("location") == "header" {
escaping = protocol.Base64Escape
}
str, err = protocol.EncodeJSONValue(value, escaping)
if err != nil {
return "", fmt.Errorf("unable to encode JSONValue, %v", err)
}
default:
err := fmt.Errorf("unsupported value for param %v (%s)", v.Interface(), v.Type())
return "", err
}
return str, nil
}

View file

@ -0,0 +1,63 @@
package rest
import (
"net/http"
"net/url"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
)
func TestCleanPath(t *testing.T) {
uri := &url.URL{
Path: "//foo//bar",
Scheme: "https",
Host: "host",
}
cleanPath(uri)
expected := "https://host/foo/bar"
if a, e := uri.String(), expected; a != e {
t.Errorf("expect %q URI, got %q", e, a)
}
}
func TestMarshalPath(t *testing.T) {
in := struct {
Bucket *string `location:"uri" locationName:"bucket"`
Key *string `location:"uri" locationName:"key"`
}{
Bucket: aws.String("mybucket"),
Key: aws.String("my/cool+thing space/object世界"),
}
expectURL := `/mybucket/my/cool+thing space/object世界`
expectEscapedURL := `/mybucket/my/cool%2Bthing%20space/object%E4%B8%96%E7%95%8C`
req := &request.Request{
HTTPRequest: &http.Request{
URL: &url.URL{Scheme: "https", Host: "exmaple.com", Path: "/{bucket}/{key+}"},
},
Params: &in,
}
Build(req)
if req.Error != nil {
t.Fatalf("unexpected error, %v", req.Error)
}
if a, e := req.HTTPRequest.URL.Path, expectURL; a != e {
t.Errorf("expect %q URI, got %q", e, a)
}
if a, e := req.HTTPRequest.URL.RawPath, expectEscapedURL; a != e {
t.Errorf("expect %q escaped URI, got %q", e, a)
}
if a, e := req.HTTPRequest.URL.EscapedPath(), expectEscapedURL; a != e {
t.Errorf("expect %q escaped URI, got %q", e, a)
}
}

View file

@ -0,0 +1,45 @@
package rest
import "reflect"
// PayloadMember returns the payload field member of i if there is one, or nil.
func PayloadMember(i interface{}) interface{} {
if i == nil {
return nil
}
v := reflect.ValueOf(i).Elem()
if !v.IsValid() {
return nil
}
if field, ok := v.Type().FieldByName("_"); ok {
if payloadName := field.Tag.Get("payload"); payloadName != "" {
field, _ := v.Type().FieldByName(payloadName)
if field.Tag.Get("type") != "structure" {
return nil
}
payload := v.FieldByName(payloadName)
if payload.IsValid() || (payload.Kind() == reflect.Ptr && !payload.IsNil()) {
return payload.Interface()
}
}
}
return nil
}
// PayloadType returns the type of a payload field member of i if there is one, or "".
func PayloadType(i interface{}) string {
v := reflect.Indirect(reflect.ValueOf(i))
if !v.IsValid() {
return ""
}
if field, ok := v.Type().FieldByName("_"); ok {
if payloadName := field.Tag.Get("payload"); payloadName != "" {
if member, ok := v.Type().FieldByName(payloadName); ok {
return member.Tag.Get("type")
}
}
}
return ""
}

View file

@ -0,0 +1,63 @@
package rest_test
import (
"bytes"
"io/ioutil"
"net/http"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/aws/aws-sdk-go/awstesting/unit"
"github.com/aws/aws-sdk-go/private/protocol/rest"
)
func TestUnsetHeaders(t *testing.T) {
cfg := &aws.Config{Region: aws.String("us-west-2")}
c := unit.Session.ClientConfig("testService", cfg)
svc := client.New(
*cfg,
metadata.ClientInfo{
ServiceName: "testService",
SigningName: c.SigningName,
SigningRegion: c.SigningRegion,
Endpoint: c.Endpoint,
APIVersion: "",
},
c.Handlers,
)
// Handlers
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
svc.Handlers.Build.PushBackNamed(rest.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed(rest.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed(rest.UnmarshalMetaHandler)
op := &request.Operation{
Name: "test-operation",
HTTPPath: "/",
}
input := &struct {
Foo aws.JSONValue `location:"header" locationName:"x-amz-foo" type:"jsonvalue"`
Bar aws.JSONValue `location:"header" locationName:"x-amz-bar" type:"jsonvalue"`
}{}
output := &struct {
Foo aws.JSONValue `location:"header" locationName:"x-amz-foo" type:"jsonvalue"`
Bar aws.JSONValue `location:"header" locationName:"x-amz-bar" type:"jsonvalue"`
}{}
req := svc.NewRequest(op, input, output)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewBuffer(nil)), Header: http.Header{}}
req.HTTPResponse.Header.Set("X-Amz-Foo", "e30=")
// unmarshal response
rest.UnmarshalMeta(req)
rest.Unmarshal(req)
if req.Error != nil {
t.Fatal(req.Error)
}
}

View file

@ -0,0 +1,221 @@
package rest
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
)
// UnmarshalHandler is a named request handler for unmarshaling rest protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.rest.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling rest protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.rest.UnmarshalMeta", Fn: UnmarshalMeta}
// Unmarshal unmarshals the REST component of a response in a REST service.
func Unmarshal(r *request.Request) {
if r.DataFilled() {
v := reflect.Indirect(reflect.ValueOf(r.Data))
unmarshalBody(r, v)
}
}
// UnmarshalMeta unmarshals the REST metadata of a response in a REST service
func UnmarshalMeta(r *request.Request) {
r.RequestID = r.HTTPResponse.Header.Get("X-Amzn-Requestid")
if r.RequestID == "" {
// Alternative version of request id in the header
r.RequestID = r.HTTPResponse.Header.Get("X-Amz-Request-Id")
}
if r.DataFilled() {
v := reflect.Indirect(reflect.ValueOf(r.Data))
unmarshalLocationElements(r, v)
}
}
func unmarshalBody(r *request.Request, v reflect.Value) {
if field, ok := v.Type().FieldByName("_"); ok {
if payloadName := field.Tag.Get("payload"); payloadName != "" {
pfield, _ := v.Type().FieldByName(payloadName)
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
payload := v.FieldByName(payloadName)
if payload.IsValid() {
switch payload.Interface().(type) {
case []byte:
defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
} else {
payload.Set(reflect.ValueOf(b))
}
case *string:
defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
} else {
str := string(b)
payload.Set(reflect.ValueOf(&str))
}
default:
switch payload.Type().String() {
case "io.ReadCloser":
payload.Set(reflect.ValueOf(r.HTTPResponse.Body))
case "io.ReadSeeker":
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError",
"failed to read response body", err)
return
}
payload.Set(reflect.ValueOf(ioutil.NopCloser(bytes.NewReader(b))))
default:
io.Copy(ioutil.Discard, r.HTTPResponse.Body)
defer r.HTTPResponse.Body.Close()
r.Error = awserr.New("SerializationError",
"failed to decode REST response",
fmt.Errorf("unknown payload type %s", payload.Type()))
}
}
}
}
}
}
}
func unmarshalLocationElements(r *request.Request, v reflect.Value) {
for i := 0; i < v.NumField(); i++ {
m, field := v.Field(i), v.Type().Field(i)
if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) {
continue
}
if m.IsValid() {
name := field.Tag.Get("locationName")
if name == "" {
name = field.Name
}
switch field.Tag.Get("location") {
case "statusCode":
unmarshalStatusCode(m, r.HTTPResponse.StatusCode)
case "header":
err := unmarshalHeader(m, r.HTTPResponse.Header.Get(name), field.Tag)
if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
break
}
case "headers":
prefix := field.Tag.Get("locationName")
err := unmarshalHeaderMap(m, r.HTTPResponse.Header, prefix)
if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST response", err)
break
}
}
}
if r.Error != nil {
return
}
}
}
func unmarshalStatusCode(v reflect.Value, statusCode int) {
if !v.IsValid() {
return
}
switch v.Interface().(type) {
case *int64:
s := int64(statusCode)
v.Set(reflect.ValueOf(&s))
}
}
func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) error {
switch r.Interface().(type) {
case map[string]*string: // we only support string map value types
out := map[string]*string{}
for k, v := range headers {
k = http.CanonicalHeaderKey(k)
if strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
out[k[len(prefix):]] = &v[0]
}
}
r.Set(reflect.ValueOf(out))
}
return nil
}
func unmarshalHeader(v reflect.Value, header string, tag reflect.StructTag) error {
isJSONValue := tag.Get("type") == "jsonvalue"
if isJSONValue {
if len(header) == 0 {
return nil
}
} else if !v.IsValid() || (header == "" && v.Elem().Kind() != reflect.String) {
return nil
}
switch v.Interface().(type) {
case *string:
v.Set(reflect.ValueOf(&header))
case []byte:
b, err := base64.StdEncoding.DecodeString(header)
if err != nil {
return err
}
v.Set(reflect.ValueOf(&b))
case *bool:
b, err := strconv.ParseBool(header)
if err != nil {
return err
}
v.Set(reflect.ValueOf(&b))
case *int64:
i, err := strconv.ParseInt(header, 10, 64)
if err != nil {
return err
}
v.Set(reflect.ValueOf(&i))
case *float64:
f, err := strconv.ParseFloat(header, 64)
if err != nil {
return err
}
v.Set(reflect.ValueOf(&f))
case *time.Time:
t, err := time.Parse(RFC822, header)
if err != nil {
return err
}
v.Set(reflect.ValueOf(&t))
case aws.JSONValue:
escaping := protocol.NoEscape
if tag.Get("location") == "header" {
escaping = protocol.Base64Escape
}
m, err := protocol.DecodeJSONValue(header, escaping)
if err != nil {
return err
}
v.Set(reflect.ValueOf(m))
default:
err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
return err
}
return nil
}

View file

@ -0,0 +1,366 @@
// +build bench
package restxml_test
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"bytes"
"encoding/xml"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/private/protocol/restxml"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/aws/aws-sdk-go/service/s3"
)
var (
cloudfrontSvc *cloudfront.CloudFront
s3Svc *s3.S3
)
func TestMain(m *testing.M) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
sess := session.Must(session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials("Key", "Secret", "Token"),
Endpoint: aws.String(server.URL),
S3ForcePathStyle: aws.Bool(true),
DisableSSL: aws.Bool(true),
Region: aws.String(endpoints.UsWest2RegionID),
}))
cloudfrontSvc = cloudfront.New(sess)
s3Svc = s3.New(sess)
c := m.Run()
server.Close()
os.Exit(c)
}
func BenchmarkRESTXMLBuild_Complex_CFCreateDistro(b *testing.B) {
params := cloudfrontCreateDistributionInput()
benchRESTXMLBuild(b, func() *request.Request {
req, _ := cloudfrontSvc.CreateDistributionRequest(params)
return req
})
}
func BenchmarkRESTXMLBuild_Simple_CFDeleteDistro(b *testing.B) {
params := cloudfrontDeleteDistributionInput()
benchRESTXMLBuild(b, func() *request.Request {
req, _ := cloudfrontSvc.DeleteDistributionRequest(params)
return req
})
}
func BenchmarkRESTXMLBuild_REST_S3HeadObject(b *testing.B) {
params := s3HeadObjectInput()
benchRESTXMLBuild(b, func() *request.Request {
req, _ := s3Svc.HeadObjectRequest(params)
return req
})
}
func BenchmarkRESTXMLBuild_XML_S3PutObjectAcl(b *testing.B) {
params := s3PutObjectAclInput()
benchRESTXMLBuild(b, func() *request.Request {
req, _ := s3Svc.PutObjectAclRequest(params)
return req
})
}
func BenchmarkRESTXMLRequest_Complex_CFCreateDistro(b *testing.B) {
benchRESTXMLRequest(b, func() *request.Request {
req, _ := cloudfrontSvc.CreateDistributionRequest(cloudfrontCreateDistributionInput())
return req
})
}
func BenchmarkRESTXMLRequest_Simple_CFDeleteDistro(b *testing.B) {
benchRESTXMLRequest(b, func() *request.Request {
req, _ := cloudfrontSvc.DeleteDistributionRequest(cloudfrontDeleteDistributionInput())
return req
})
}
func BenchmarkRESTXMLRequest_REST_S3HeadObject(b *testing.B) {
benchRESTXMLRequest(b, func() *request.Request {
req, _ := s3Svc.HeadObjectRequest(s3HeadObjectInput())
return req
})
}
func BenchmarkRESTXMLRequest_XML_S3PutObjectAcl(b *testing.B) {
benchRESTXMLRequest(b, func() *request.Request {
req, _ := s3Svc.PutObjectAclRequest(s3PutObjectAclInput())
return req
})
}
func BenchmarkEncodingXML_Simple(b *testing.B) {
params := cloudfrontDeleteDistributionInput()
for i := 0; i < b.N; i++ {
buf := &bytes.Buffer{}
encoder := xml.NewEncoder(buf)
if err := encoder.Encode(params); err != nil {
b.Fatal("Unexpected error", err)
}
}
}
func benchRESTXMLBuild(b *testing.B, reqFn func() *request.Request) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := reqFn()
restxml.Build(req)
if req.Error != nil {
b.Fatal("Unexpected error", req.Error)
}
}
}
func benchRESTXMLRequest(b *testing.B, reqFn func() *request.Request) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := reqFn().Send()
if err != nil {
b.Fatal("Unexpected error", err)
}
}
}
func cloudfrontCreateDistributionInput() *cloudfront.CreateDistributionInput {
return &cloudfront.CreateDistributionInput{
DistributionConfig: &cloudfront.DistributionConfig{ // Required
CallerReference: aws.String("string"), // Required
Comment: aws.String("string"), // Required
DefaultCacheBehavior: &cloudfront.DefaultCacheBehavior{ // Required
ForwardedValues: &cloudfront.ForwardedValues{ // Required
Cookies: &cloudfront.CookiePreference{ // Required
Forward: aws.String("ItemSelection"), // Required
WhitelistedNames: &cloudfront.CookieNames{
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
},
QueryString: aws.Bool(true), // Required
Headers: &cloudfront.Headers{
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
},
MinTTL: aws.Int64(1), // Required
TargetOriginId: aws.String("string"), // Required
TrustedSigners: &cloudfront.TrustedSigners{ // Required
Enabled: aws.Bool(true), // Required
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
ViewerProtocolPolicy: aws.String("ViewerProtocolPolicy"), // Required
AllowedMethods: &cloudfront.AllowedMethods{
Items: []*string{ // Required
aws.String("Method"), // Required
// More values...
},
Quantity: aws.Int64(1), // Required
CachedMethods: &cloudfront.CachedMethods{
Items: []*string{ // Required
aws.String("Method"), // Required
// More values...
},
Quantity: aws.Int64(1), // Required
},
},
DefaultTTL: aws.Int64(1),
MaxTTL: aws.Int64(1),
SmoothStreaming: aws.Bool(true),
},
Enabled: aws.Bool(true), // Required
Origins: &cloudfront.Origins{ // Required
Quantity: aws.Int64(1), // Required
Items: []*cloudfront.Origin{
{ // Required
DomainName: aws.String("string"), // Required
Id: aws.String("string"), // Required
CustomOriginConfig: &cloudfront.CustomOriginConfig{
HTTPPort: aws.Int64(1), // Required
HTTPSPort: aws.Int64(1), // Required
OriginProtocolPolicy: aws.String("OriginProtocolPolicy"), // Required
},
OriginPath: aws.String("string"),
S3OriginConfig: &cloudfront.S3OriginConfig{
OriginAccessIdentity: aws.String("string"), // Required
},
},
// More values...
},
},
Aliases: &cloudfront.Aliases{
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
CacheBehaviors: &cloudfront.CacheBehaviors{
Quantity: aws.Int64(1), // Required
Items: []*cloudfront.CacheBehavior{
{ // Required
ForwardedValues: &cloudfront.ForwardedValues{ // Required
Cookies: &cloudfront.CookiePreference{ // Required
Forward: aws.String("ItemSelection"), // Required
WhitelistedNames: &cloudfront.CookieNames{
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
},
QueryString: aws.Bool(true), // Required
Headers: &cloudfront.Headers{
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
},
MinTTL: aws.Int64(1), // Required
PathPattern: aws.String("string"), // Required
TargetOriginId: aws.String("string"), // Required
TrustedSigners: &cloudfront.TrustedSigners{ // Required
Enabled: aws.Bool(true), // Required
Quantity: aws.Int64(1), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
ViewerProtocolPolicy: aws.String("ViewerProtocolPolicy"), // Required
AllowedMethods: &cloudfront.AllowedMethods{
Items: []*string{ // Required
aws.String("Method"), // Required
// More values...
},
Quantity: aws.Int64(1), // Required
CachedMethods: &cloudfront.CachedMethods{
Items: []*string{ // Required
aws.String("Method"), // Required
// More values...
},
Quantity: aws.Int64(1), // Required
},
},
DefaultTTL: aws.Int64(1),
MaxTTL: aws.Int64(1),
SmoothStreaming: aws.Bool(true),
},
// More values...
},
},
CustomErrorResponses: &cloudfront.CustomErrorResponses{
Quantity: aws.Int64(1), // Required
Items: []*cloudfront.CustomErrorResponse{
{ // Required
ErrorCode: aws.Int64(1), // Required
ErrorCachingMinTTL: aws.Int64(1),
ResponseCode: aws.String("string"),
ResponsePagePath: aws.String("string"),
},
// More values...
},
},
DefaultRootObject: aws.String("string"),
Logging: &cloudfront.LoggingConfig{
Bucket: aws.String("string"), // Required
Enabled: aws.Bool(true), // Required
IncludeCookies: aws.Bool(true), // Required
Prefix: aws.String("string"), // Required
},
PriceClass: aws.String("PriceClass"),
Restrictions: &cloudfront.Restrictions{
GeoRestriction: &cloudfront.GeoRestriction{ // Required
Quantity: aws.Int64(1), // Required
RestrictionType: aws.String("GeoRestrictionType"), // Required
Items: []*string{
aws.String("string"), // Required
// More values...
},
},
},
ViewerCertificate: &cloudfront.ViewerCertificate{
CloudFrontDefaultCertificate: aws.Bool(true),
IAMCertificateId: aws.String("string"),
MinimumProtocolVersion: aws.String("MinimumProtocolVersion"),
SSLSupportMethod: aws.String("SSLSupportMethod"),
},
},
}
}
func cloudfrontDeleteDistributionInput() *cloudfront.DeleteDistributionInput {
return &cloudfront.DeleteDistributionInput{
Id: aws.String("string"), // Required
IfMatch: aws.String("string"),
}
}
func s3HeadObjectInput() *s3.HeadObjectInput {
return &s3.HeadObjectInput{
Bucket: aws.String("somebucketname"),
Key: aws.String("keyname"),
VersionId: aws.String("someVersion"),
IfMatch: aws.String("IfMatch"),
}
}
func s3PutObjectAclInput() *s3.PutObjectAclInput {
return &s3.PutObjectAclInput{
Bucket: aws.String("somebucketname"),
Key: aws.String("keyname"),
AccessControlPolicy: &s3.AccessControlPolicy{
Grants: []*s3.Grant{
{
Grantee: &s3.Grantee{
DisplayName: aws.String("someName"),
EmailAddress: aws.String("someAddr"),
ID: aws.String("someID"),
Type: aws.String(s3.TypeCanonicalUser),
URI: aws.String("someURI"),
},
Permission: aws.String(s3.PermissionWrite),
},
},
Owner: &s3.Owner{
DisplayName: aws.String("howdy"),
ID: aws.String("someID"),
},
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,69 @@
// Package restxml provides RESTful XML serialization of AWS
// requests and responses.
package restxml
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/input/rest-xml.json build_test.go
//go:generate go run -tags codegen ../../../models/protocol_tests/generate.go ../../../models/protocol_tests/output/rest-xml.json unmarshal_test.go
import (
"bytes"
"encoding/xml"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/query"
"github.com/aws/aws-sdk-go/private/protocol/rest"
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
)
// BuildHandler is a named request handler for building restxml protocol requests
var BuildHandler = request.NamedHandler{Name: "awssdk.restxml.Build", Fn: Build}
// UnmarshalHandler is a named request handler for unmarshaling restxml protocol requests
var UnmarshalHandler = request.NamedHandler{Name: "awssdk.restxml.Unmarshal", Fn: Unmarshal}
// UnmarshalMetaHandler is a named request handler for unmarshaling restxml protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{Name: "awssdk.restxml.UnmarshalMeta", Fn: UnmarshalMeta}
// UnmarshalErrorHandler is a named request handler for unmarshaling restxml protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.restxml.UnmarshalError", Fn: UnmarshalError}
// Build builds a request payload for the REST XML protocol.
func Build(r *request.Request) {
rest.Build(r)
if t := rest.PayloadType(r.Params); t == "structure" || t == "" {
var buf bytes.Buffer
err := xmlutil.BuildXML(r.Params, xml.NewEncoder(&buf))
if err != nil {
r.Error = awserr.New("SerializationError", "failed to encode rest XML request", err)
return
}
r.SetBufferBody(buf.Bytes())
}
}
// Unmarshal unmarshals a payload response for the REST XML protocol.
func Unmarshal(r *request.Request) {
if t := rest.PayloadType(r.Data); t == "structure" || t == "" {
defer r.HTTPResponse.Body.Close()
decoder := xml.NewDecoder(r.HTTPResponse.Body)
err := xmlutil.UnmarshalXML(r.Data, decoder, "")
if err != nil {
r.Error = awserr.New("SerializationError", "failed to decode REST XML response", err)
return
}
} else {
rest.Unmarshal(r)
}
}
// UnmarshalMeta unmarshals response headers for the REST XML protocol.
func UnmarshalMeta(r *request.Request) {
rest.UnmarshalMeta(r)
}
// UnmarshalError unmarshals a response error for the REST XML protocol.
func UnmarshalError(r *request.Request) {
query.UnmarshalError(r)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
package protocol
import (
"io"
"io/ioutil"
"github.com/aws/aws-sdk-go/aws/request"
)
// UnmarshalDiscardBodyHandler is a named request handler to empty and close a response's body
var UnmarshalDiscardBodyHandler = request.NamedHandler{Name: "awssdk.shared.UnmarshalDiscardBody", Fn: UnmarshalDiscardBody}
// UnmarshalDiscardBody is a request handler to empty a response's body and closing it.
func UnmarshalDiscardBody(r *request.Request) {
if r.HTTPResponse == nil || r.HTTPResponse.Body == nil {
return
}
io.Copy(ioutil.Discard, r.HTTPResponse.Body)
r.HTTPResponse.Body.Close()
}

View file

@ -0,0 +1,40 @@
package protocol_test
import (
"net/http"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/stretchr/testify/assert"
)
type mockCloser struct {
*strings.Reader
Closed bool
}
func (m *mockCloser) Close() error {
m.Closed = true
return nil
}
func TestUnmarshalDrainBody(t *testing.T) {
b := &mockCloser{Reader: strings.NewReader("example body")}
r := &request.Request{HTTPResponse: &http.Response{
Body: b,
}}
protocol.UnmarshalDiscardBody(r)
assert.NoError(t, r.Error)
assert.Equal(t, 0, b.Len())
assert.True(t, b.Closed)
}
func TestUnmarshalDrainBodyNoBody(t *testing.T) {
r := &request.Request{HTTPResponse: &http.Response{}}
protocol.UnmarshalDiscardBody(r)
assert.NoError(t, r.Error)
}

View file

@ -0,0 +1,296 @@
// Package xmlutil provides XML serialization of AWS requests and responses.
package xmlutil
import (
"encoding/base64"
"encoding/xml"
"fmt"
"reflect"
"sort"
"strconv"
"time"
"github.com/aws/aws-sdk-go/private/protocol"
)
// BuildXML will serialize params into an xml.Encoder.
// Error will be returned if the serialization of any of the params or nested values fails.
func BuildXML(params interface{}, e *xml.Encoder) error {
b := xmlBuilder{encoder: e, namespaces: map[string]string{}}
root := NewXMLElement(xml.Name{})
if err := b.buildValue(reflect.ValueOf(params), root, ""); err != nil {
return err
}
for _, c := range root.Children {
for _, v := range c {
return StructToXML(e, v, false)
}
}
return nil
}
// Returns the reflection element of a value, if it is a pointer.
func elemOf(value reflect.Value) reflect.Value {
for value.Kind() == reflect.Ptr {
value = value.Elem()
}
return value
}
// A xmlBuilder serializes values from Go code to XML
type xmlBuilder struct {
encoder *xml.Encoder
namespaces map[string]string
}
// buildValue generic XMLNode builder for any type. Will build value for their specific type
// struct, list, map, scalar.
//
// Also takes a "type" tag value to set what type a value should be converted to XMLNode as. If
// type is not provided reflect will be used to determine the value's type.
func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
value = elemOf(value)
if !value.IsValid() { // no need to handle zero values
return nil
} else if tag.Get("location") != "" { // don't handle non-body location values
return nil
}
t := tag.Get("type")
if t == "" {
switch value.Kind() {
case reflect.Struct:
t = "structure"
case reflect.Slice:
t = "list"
case reflect.Map:
t = "map"
}
}
switch t {
case "structure":
if field, ok := value.Type().FieldByName("_"); ok {
tag = tag + reflect.StructTag(" ") + field.Tag
}
return b.buildStruct(value, current, tag)
case "list":
return b.buildList(value, current, tag)
case "map":
return b.buildMap(value, current, tag)
default:
return b.buildScalar(value, current, tag)
}
}
// buildStruct adds a struct and its fields to the current XMLNode. All fields any any nested
// types are converted to XMLNodes also.
func (b *xmlBuilder) buildStruct(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
if !value.IsValid() {
return nil
}
fieldAdded := false
// unwrap payloads
if payload := tag.Get("payload"); payload != "" {
field, _ := value.Type().FieldByName(payload)
tag = field.Tag
value = elemOf(value.FieldByName(payload))
if !value.IsValid() {
return nil
}
}
child := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
// there is an xmlNamespace associated with this struct
if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" {
ns := xml.Attr{
Name: xml.Name{Local: "xmlns"},
Value: uri,
}
if prefix != "" {
b.namespaces[prefix] = uri // register the namespace
ns.Name.Local = "xmlns:" + prefix
}
child.Attr = append(child.Attr, ns)
}
t := value.Type()
for i := 0; i < value.NumField(); i++ {
member := elemOf(value.Field(i))
field := t.Field(i)
if field.PkgPath != "" {
continue // ignore unexported fields
}
if field.Tag.Get("ignore") != "" {
continue
}
mTag := field.Tag
if mTag.Get("location") != "" { // skip non-body members
continue
}
if protocol.CanSetIdempotencyToken(value.Field(i), field) {
token := protocol.GetIdempotencyToken()
member = reflect.ValueOf(token)
}
memberName := mTag.Get("locationName")
if memberName == "" {
memberName = field.Name
mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`)
}
if err := b.buildValue(member, child, mTag); err != nil {
return err
}
fieldAdded = true
}
if fieldAdded { // only append this child if we have one ore more valid members
current.AddChild(child)
}
return nil
}
// buildList adds the value's list items to the current XMLNode as children nodes. All
// nested values in the list are converted to XMLNodes also.
func (b *xmlBuilder) buildList(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
if value.IsNil() { // don't build omitted lists
return nil
}
// check for unflattened list member
flattened := tag.Get("flattened") != ""
xname := xml.Name{Local: tag.Get("locationName")}
if flattened {
for i := 0; i < value.Len(); i++ {
child := NewXMLElement(xname)
current.AddChild(child)
if err := b.buildValue(value.Index(i), child, ""); err != nil {
return err
}
}
} else {
list := NewXMLElement(xname)
current.AddChild(list)
for i := 0; i < value.Len(); i++ {
iname := tag.Get("locationNameList")
if iname == "" {
iname = "member"
}
child := NewXMLElement(xml.Name{Local: iname})
list.AddChild(child)
if err := b.buildValue(value.Index(i), child, ""); err != nil {
return err
}
}
}
return nil
}
// buildMap adds the value's key/value pairs to the current XMLNode as children nodes. All
// nested values in the map are converted to XMLNodes also.
//
// Error will be returned if it is unable to build the map's values into XMLNodes
func (b *xmlBuilder) buildMap(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
if value.IsNil() { // don't build omitted maps
return nil
}
maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
current.AddChild(maproot)
current = maproot
kname, vname := "key", "value"
if n := tag.Get("locationNameKey"); n != "" {
kname = n
}
if n := tag.Get("locationNameValue"); n != "" {
vname = n
}
// sorting is not required for compliance, but it makes testing easier
keys := make([]string, value.Len())
for i, k := range value.MapKeys() {
keys[i] = k.String()
}
sort.Strings(keys)
for _, k := range keys {
v := value.MapIndex(reflect.ValueOf(k))
mapcur := current
if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps
child := NewXMLElement(xml.Name{Local: "entry"})
mapcur.AddChild(child)
mapcur = child
}
kchild := NewXMLElement(xml.Name{Local: kname})
kchild.Text = k
vchild := NewXMLElement(xml.Name{Local: vname})
mapcur.AddChild(kchild)
mapcur.AddChild(vchild)
if err := b.buildValue(v, vchild, ""); err != nil {
return err
}
}
return nil
}
// buildScalar will convert the value into a string and append it as a attribute or child
// of the current XMLNode.
//
// The value will be added as an attribute if tag contains a "xmlAttribute" attribute value.
//
// Error will be returned if the value type is unsupported.
func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
var str string
switch converted := value.Interface().(type) {
case string:
str = converted
case []byte:
if !value.IsNil() {
str = base64.StdEncoding.EncodeToString(converted)
}
case bool:
str = strconv.FormatBool(converted)
case int64:
str = strconv.FormatInt(converted, 10)
case int:
str = strconv.Itoa(converted)
case float64:
str = strconv.FormatFloat(converted, 'f', -1, 64)
case float32:
str = strconv.FormatFloat(float64(converted), 'f', -1, 32)
case time.Time:
const ISO8601UTC = "2006-01-02T15:04:05Z"
str = converted.UTC().Format(ISO8601UTC)
default:
return fmt.Errorf("unsupported value for param %s: %v (%s)",
tag.Get("locationName"), value.Interface(), value.Type().Name())
}
xname := xml.Name{Local: tag.Get("locationName")}
if tag.Get("xmlAttribute") != "" { // put into current node's attribute list
attr := xml.Attr{Name: xname, Value: str}
current.Attr = append(current.Attr, attr)
} else { // regular text node
current.AddChild(&XMLNode{Name: xname, Text: str})
}
return nil
}

View file

@ -0,0 +1,260 @@
package xmlutil
import (
"encoding/base64"
"encoding/xml"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"time"
)
// UnmarshalXML deserializes an xml.Decoder into the container v. V
// needs to match the shape of the XML expected to be decoded.
// If the shape doesn't match unmarshaling will fail.
func UnmarshalXML(v interface{}, d *xml.Decoder, wrapper string) error {
n, err := XMLToStruct(d, nil)
if err != nil {
return err
}
if n.Children != nil {
for _, root := range n.Children {
for _, c := range root {
if wrappedChild, ok := c.Children[wrapper]; ok {
c = wrappedChild[0] // pull out wrapped element
}
err = parse(reflect.ValueOf(v), c, "")
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
return nil
}
return nil
}
// parse deserializes any value from the XMLNode. The type tag is used to infer the type, or reflect
// will be used to determine the type from r.
func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
rtype := r.Type()
if rtype.Kind() == reflect.Ptr {
rtype = rtype.Elem() // check kind of actual element type
}
t := tag.Get("type")
if t == "" {
switch rtype.Kind() {
case reflect.Struct:
t = "structure"
case reflect.Slice:
t = "list"
case reflect.Map:
t = "map"
}
}
switch t {
case "structure":
if field, ok := rtype.FieldByName("_"); ok {
tag = field.Tag
}
return parseStruct(r, node, tag)
case "list":
return parseList(r, node, tag)
case "map":
return parseMap(r, node, tag)
default:
return parseScalar(r, node, tag)
}
}
// parseStruct deserializes a structure and its fields from an XMLNode. Any nested
// types in the structure will also be deserialized.
func parseStruct(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
t := r.Type()
if r.Kind() == reflect.Ptr {
if r.IsNil() { // create the structure if it's nil
s := reflect.New(r.Type().Elem())
r.Set(s)
r = s
}
r = r.Elem()
t = t.Elem()
}
// unwrap any payloads
if payload := tag.Get("payload"); payload != "" {
field, _ := t.FieldByName(payload)
return parseStruct(r.FieldByName(payload), node, field.Tag)
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if c := field.Name[0:1]; strings.ToLower(c) == c {
continue // ignore unexported fields
}
// figure out what this field is called
name := field.Name
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
name = field.Tag.Get("locationNameList")
} else if locName := field.Tag.Get("locationName"); locName != "" {
name = locName
}
// try to find the field by name in elements
elems := node.Children[name]
if elems == nil { // try to find the field in attributes
if val, ok := node.findElem(name); ok {
elems = []*XMLNode{{Text: val}}
}
}
member := r.FieldByName(field.Name)
for _, elem := range elems {
err := parse(member, elem, field.Tag)
if err != nil {
return err
}
}
}
return nil
}
// parseList deserializes a list of values from an XML node. Each list entry
// will also be deserialized.
func parseList(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
t := r.Type()
if tag.Get("flattened") == "" { // look at all item entries
mname := "member"
if name := tag.Get("locationNameList"); name != "" {
mname = name
}
if Children, ok := node.Children[mname]; ok {
if r.IsNil() {
r.Set(reflect.MakeSlice(t, len(Children), len(Children)))
}
for i, c := range Children {
err := parse(r.Index(i), c, "")
if err != nil {
return err
}
}
}
} else { // flattened list means this is a single element
if r.IsNil() {
r.Set(reflect.MakeSlice(t, 0, 0))
}
childR := reflect.Zero(t.Elem())
r.Set(reflect.Append(r, childR))
err := parse(r.Index(r.Len()-1), node, "")
if err != nil {
return err
}
}
return nil
}
// parseMap deserializes a map from an XMLNode. The direct children of the XMLNode
// will also be deserialized as map entries.
func parseMap(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
if r.IsNil() {
r.Set(reflect.MakeMap(r.Type()))
}
if tag.Get("flattened") == "" { // look at all child entries
for _, entry := range node.Children["entry"] {
parseMapEntry(r, entry, tag)
}
} else { // this element is itself an entry
parseMapEntry(r, node, tag)
}
return nil
}
// parseMapEntry deserializes a map entry from a XML node.
func parseMapEntry(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
kname, vname := "key", "value"
if n := tag.Get("locationNameKey"); n != "" {
kname = n
}
if n := tag.Get("locationNameValue"); n != "" {
vname = n
}
keys, ok := node.Children[kname]
values := node.Children[vname]
if ok {
for i, key := range keys {
keyR := reflect.ValueOf(key.Text)
value := values[i]
valueR := reflect.New(r.Type().Elem()).Elem()
parse(valueR, value, "")
r.SetMapIndex(keyR, valueR)
}
}
return nil
}
// parseScaller deserializes an XMLNode value into a concrete type based on the
// interface type of r.
//
// Error is returned if the deserialization fails due to invalid type conversion,
// or unsupported interface type.
func parseScalar(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
switch r.Interface().(type) {
case *string:
r.Set(reflect.ValueOf(&node.Text))
return nil
case []byte:
b, err := base64.StdEncoding.DecodeString(node.Text)
if err != nil {
return err
}
r.Set(reflect.ValueOf(b))
case *bool:
v, err := strconv.ParseBool(node.Text)
if err != nil {
return err
}
r.Set(reflect.ValueOf(&v))
case *int64:
v, err := strconv.ParseInt(node.Text, 10, 64)
if err != nil {
return err
}
r.Set(reflect.ValueOf(&v))
case *float64:
v, err := strconv.ParseFloat(node.Text, 64)
if err != nil {
return err
}
r.Set(reflect.ValueOf(&v))
case *time.Time:
const ISO8601UTC = "2006-01-02T15:04:05Z"
t, err := time.Parse(ISO8601UTC, node.Text)
if err != nil {
return err
}
r.Set(reflect.ValueOf(&t))
default:
return fmt.Errorf("unsupported value: %v (%s)", r.Interface(), r.Type())
}
return nil
}

View file

@ -0,0 +1,142 @@
package xmlutil
import (
"encoding/xml"
"fmt"
"io"
"reflect"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
type mockBody struct {
DoneErr error
Body io.Reader
}
func (m *mockBody) Read(p []byte) (int, error) {
n, err := m.Body.Read(p)
if (n == 0 || err == io.EOF) && m.DoneErr != nil {
return n, m.DoneErr
}
return n, err
}
type mockOutput struct {
_ struct{} `type:"structure"`
String *string `type:"string"`
Integer *int64 `type:"integer"`
Nested *mockNestedStruct `type:"structure"`
List []*mockListElem `locationName:"List" locationNameList:"Elem" type:"list"`
Closed *mockClosedTags `type:"structure"`
}
type mockNestedStruct struct {
_ struct{} `type:"structure"`
NestedString *string `type:"string"`
NestedInt *int64 `type:"integer"`
}
type mockClosedTags struct {
_ struct{} `type:"structure" xmlPrefix:"xsi" xmlURI:"http://www.w3.org/2001/XMLSchema-instance"`
Attr *string `locationName:"xsi:attrval" type:"string" xmlAttribute:"true"`
}
type mockListElem struct {
_ struct{} `type:"structure" xmlPrefix:"xsi" xmlURI:"http://www.w3.org/2001/XMLSchema-instance"`
String *string `type:"string"`
NestedElem *mockNestedListElem `type:"structure"`
}
type mockNestedListElem struct {
_ struct{} `type:"structure" xmlPrefix:"xsi" xmlURI:"http://www.w3.org/2001/XMLSchema-instance"`
String *string `type:"string"`
Type *string `locationName:"xsi:type" type:"string" xmlAttribute:"true"`
}
func TestUnmarshal(t *testing.T) {
const xmlBodyStr = `<?xml version="1.0" encoding="UTF-8"?>
<MockResponse xmlns="http://xmlns.example.com">
<String>string value</String>
<Integer>123</Integer>
<Closed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:attrval="attr value"/>
<Nested>
<NestedString>nested string value</NestedString>
<NestedInt>321</NestedInt>
</Nested>
<List>
<Elem>
<NestedElem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="type">
<String>nested elem string value</String>
</NestedElem>
<String>elem string value</String>
</Elem>
</List>
</MockResponse>`
expect := mockOutput{
String: aws.String("string value"),
Integer: aws.Int64(123),
Closed: &mockClosedTags{
Attr: aws.String("attr value"),
},
Nested: &mockNestedStruct{
NestedString: aws.String("nested string value"),
NestedInt: aws.Int64(321),
},
List: []*mockListElem{
{
String: aws.String("elem string value"),
NestedElem: &mockNestedListElem{
String: aws.String("nested elem string value"),
Type: aws.String("type"),
},
},
},
}
actual := mockOutput{}
decoder := xml.NewDecoder(strings.NewReader(xmlBodyStr))
err := UnmarshalXML(&actual, decoder, "")
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if !reflect.DeepEqual(expect, actual) {
t.Errorf("expect unmarshal to match\nExpect: %s\nActual: %s",
awsutil.Prettify(expect), awsutil.Prettify(actual))
}
}
func TestUnmarshal_UnexpectedEOF(t *testing.T) {
const partialXMLBody = `<?xml version="1.0" encoding="UTF-8"?>
<First>first value</First>
<Second>Second val`
out := struct {
First *string `locationName:"First" type:"string"`
Second *string `locationName:"Second" type:"string"`
}{}
expect := out
expect.First = aws.String("first")
expect.Second = aws.String("second")
expectErr := fmt.Errorf("expected read error")
body := &mockBody{
DoneErr: expectErr,
Body: strings.NewReader(partialXMLBody),
}
decoder := xml.NewDecoder(body)
err := UnmarshalXML(&out, decoder, "")
if err == nil {
t.Fatalf("expect error, got none")
}
if e, a := expectErr, err; e != a {
t.Errorf("expect %v error in %v, but was not", e, a)
}
}

View file

@ -0,0 +1,147 @@
package xmlutil
import (
"encoding/xml"
"fmt"
"io"
"sort"
)
// A XMLNode contains the values to be encoded or decoded.
type XMLNode struct {
Name xml.Name `json:",omitempty"`
Children map[string][]*XMLNode `json:",omitempty"`
Text string `json:",omitempty"`
Attr []xml.Attr `json:",omitempty"`
namespaces map[string]string
parent *XMLNode
}
// NewXMLElement returns a pointer to a new XMLNode initialized to default values.
func NewXMLElement(name xml.Name) *XMLNode {
return &XMLNode{
Name: name,
Children: map[string][]*XMLNode{},
Attr: []xml.Attr{},
}
}
// AddChild adds child to the XMLNode.
func (n *XMLNode) AddChild(child *XMLNode) {
if _, ok := n.Children[child.Name.Local]; !ok {
n.Children[child.Name.Local] = []*XMLNode{}
}
n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child)
}
// XMLToStruct converts a xml.Decoder stream to XMLNode with nested values.
func XMLToStruct(d *xml.Decoder, s *xml.StartElement) (*XMLNode, error) {
out := &XMLNode{}
for {
tok, err := d.Token()
if err != nil {
if err == io.EOF {
break
} else {
return out, err
}
}
if tok == nil {
break
}
switch typed := tok.(type) {
case xml.CharData:
out.Text = string(typed.Copy())
case xml.StartElement:
el := typed.Copy()
out.Attr = el.Attr
if out.Children == nil {
out.Children = map[string][]*XMLNode{}
}
name := typed.Name.Local
slice := out.Children[name]
if slice == nil {
slice = []*XMLNode{}
}
node, e := XMLToStruct(d, &el)
out.findNamespaces()
if e != nil {
return out, e
}
node.Name = typed.Name
node.findNamespaces()
tempOut := *out
// Save into a temp variable, simply because out gets squashed during
// loop iterations
node.parent = &tempOut
slice = append(slice, node)
out.Children[name] = slice
case xml.EndElement:
if s != nil && s.Name.Local == typed.Name.Local { // matching end token
return out, nil
}
out = &XMLNode{}
}
}
return out, nil
}
func (n *XMLNode) findNamespaces() {
ns := map[string]string{}
for _, a := range n.Attr {
if a.Name.Space == "xmlns" {
ns[a.Value] = a.Name.Local
}
}
n.namespaces = ns
}
func (n *XMLNode) findElem(name string) (string, bool) {
for node := n; node != nil; node = node.parent {
for _, a := range node.Attr {
namespace := a.Name.Space
if v, ok := node.namespaces[namespace]; ok {
namespace = v
}
if name == fmt.Sprintf("%s:%s", namespace, a.Name.Local) {
return a.Value, true
}
}
}
return "", false
}
// StructToXML writes an XMLNode to a xml.Encoder as tokens.
func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
e.EncodeToken(xml.StartElement{Name: node.Name, Attr: node.Attr})
if node.Text != "" {
e.EncodeToken(xml.CharData([]byte(node.Text)))
} else if sorted {
sortedNames := []string{}
for k := range node.Children {
sortedNames = append(sortedNames, k)
}
sort.Strings(sortedNames)
for _, k := range sortedNames {
for _, v := range node.Children[k] {
StructToXML(e, v, sorted)
}
}
} else {
for _, c := range node.Children {
for _, v := range c {
StructToXML(e, v, sorted)
}
}
}
e.EncodeToken(xml.EndElement{Name: node.Name})
return e.Flush()
}