package attributevalue

import (
	"fmt"
	"math"
	"reflect"
	"testing"

	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

type simpleMarshalStruct struct {
	Byte      []byte
	String    string
	PtrString *string
	Int       int
	Uint      uint
	Float32   float32
	Float64   float64
	Bool      bool
	Null      *interface{}
}

type complexMarshalStruct struct {
	Simple []simpleMarshalStruct
}

type myByteStruct struct {
	Byte []byte
}

type myByteSetStruct struct {
	ByteSet [][]byte
}

type marshallerTestInput struct {
	input    interface{}
	expected interface{}
	err      error
}

var trueValue = true
var falseValue = false

var marshalerScalarInputs = map[string]marshallerTestInput{
	"nil": {
		input:    nil,
		expected: &types.AttributeValueMemberNULL{Value: true},
	},
	"string": {
		input:    "some string",
		expected: &types.AttributeValueMemberS{Value: "some string"},
	},
	"bool": {
		input:    true,
		expected: &types.AttributeValueMemberBOOL{Value: true},
	},
	"bool false": {
		input:    false,
		expected: &types.AttributeValueMemberBOOL{Value: false},
	},
	"float": {
		input:    3.14,
		expected: &types.AttributeValueMemberN{Value: "3.14"},
	},
	"max float32": {
		input:    math.MaxFloat32,
		expected: &types.AttributeValueMemberN{Value: "340282346638528860000000000000000000000"},
	},
	"max float64": {
		input:    math.MaxFloat64,
		expected: &types.AttributeValueMemberN{Value: "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
	},
	"integer": {
		input:    12,
		expected: &types.AttributeValueMemberN{Value: "12"},
	},
	"number integer": {
		input:    Number("12"),
		expected: &types.AttributeValueMemberN{Value: "12"},
	},
	"zero values": {
		input: simpleMarshalStruct{},
		expected: &types.AttributeValueMemberM{
			Value: map[string]types.AttributeValue{
				"Byte":      &types.AttributeValueMemberNULL{Value: true},
				"Bool":      &types.AttributeValueMemberBOOL{Value: false},
				"Float32":   &types.AttributeValueMemberN{Value: "0"},
				"Float64":   &types.AttributeValueMemberN{Value: "0"},
				"Int":       &types.AttributeValueMemberN{Value: "0"},
				"Null":      &types.AttributeValueMemberNULL{Value: true},
				"String":    &types.AttributeValueMemberS{Value: ""},
				"PtrString": &types.AttributeValueMemberNULL{Value: true},
				"Uint":      &types.AttributeValueMemberN{Value: "0"},
			},
		},
	},
}

var marshallerMapTestInputs = map[string]marshallerTestInput{
	// Scalar tests
	"nil": {
		input:    nil,
		expected: map[string]types.AttributeValue{},
	},
	"string": {
		input:    map[string]interface{}{"string": "some string"},
		expected: map[string]types.AttributeValue{"string": &types.AttributeValueMemberS{Value: "some string"}},
	},
	"bool": {
		input:    map[string]interface{}{"bool": true},
		expected: map[string]types.AttributeValue{"bool": &types.AttributeValueMemberBOOL{Value: true}},
	},
	"bool false": {
		input:    map[string]interface{}{"bool": false},
		expected: map[string]types.AttributeValue{"bool": &types.AttributeValueMemberBOOL{Value: false}},
	},
	"null": {
		input:    map[string]interface{}{"null": nil},
		expected: map[string]types.AttributeValue{"null": &types.AttributeValueMemberNULL{Value: true}},
	},
	"float": {
		input:    map[string]interface{}{"float": 3.14},
		expected: map[string]types.AttributeValue{"float": &types.AttributeValueMemberN{Value: "3.14"}},
	},
	"float32": {
		input:    map[string]interface{}{"float": math.MaxFloat32},
		expected: map[string]types.AttributeValue{"float": &types.AttributeValueMemberN{Value: "340282346638528860000000000000000000000"}},
	},
	"float64": {
		input:    map[string]interface{}{"float": math.MaxFloat64},
		expected: map[string]types.AttributeValue{"float": &types.AttributeValueMemberN{Value: "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}},
	},
	"decimal number": {
		input:    map[string]interface{}{"num": 12.},
		expected: map[string]types.AttributeValue{"num": &types.AttributeValueMemberN{Value: "12"}},
	},
	"byte": {
		input:    map[string]interface{}{"byte": []byte{48, 49}},
		expected: map[string]types.AttributeValue{"byte": &types.AttributeValueMemberB{Value: []byte{48, 49}}},
	},
	"nested blob": {
		input:    struct{ Byte []byte }{Byte: []byte{48, 49}},
		expected: map[string]types.AttributeValue{"Byte": &types.AttributeValueMemberB{Value: []byte{48, 49}}},
	},
	"map nested blob": {
		input:    map[string]interface{}{"byte_set": [][]byte{{48, 49}, {50, 51}}},
		expected: map[string]types.AttributeValue{"byte_set": &types.AttributeValueMemberBS{Value: [][]byte{{48, 49}, {50, 51}}}},
	},
	"bytes set": {
		input:    struct{ ByteSet [][]byte }{ByteSet: [][]byte{{48, 49}, {50, 51}}},
		expected: map[string]types.AttributeValue{"ByteSet": &types.AttributeValueMemberBS{Value: [][]byte{{48, 49}, {50, 51}}}},
	},
	"list": {
		input: map[string]interface{}{"list": []interface{}{"a string", 12., 3.14, true, nil, false}},
		expected: map[string]types.AttributeValue{
			"list": &types.AttributeValueMemberL{
				Value: []types.AttributeValue{
					&types.AttributeValueMemberS{Value: "a string"},
					&types.AttributeValueMemberN{Value: "12"},
					&types.AttributeValueMemberN{Value: "3.14"},
					&types.AttributeValueMemberBOOL{Value: true},
					&types.AttributeValueMemberNULL{Value: true},
					&types.AttributeValueMemberBOOL{Value: false},
				},
			},
		},
	},
	"map": {
		input: map[string]interface{}{"map": map[string]interface{}{"nestednum": 12.}},
		expected: map[string]types.AttributeValue{
			"map": &types.AttributeValueMemberM{
				Value: map[string]types.AttributeValue{
					"nestednum": &types.AttributeValueMemberN{Value: "12"},
				},
			},
		},
	},
	"struct": {
		input: simpleMarshalStruct{},
		expected: map[string]types.AttributeValue{
			"Byte":      &types.AttributeValueMemberNULL{Value: true},
			"Bool":      &types.AttributeValueMemberBOOL{Value: false},
			"Float32":   &types.AttributeValueMemberN{Value: "0"},
			"Float64":   &types.AttributeValueMemberN{Value: "0"},
			"Int":       &types.AttributeValueMemberN{Value: "0"},
			"Null":      &types.AttributeValueMemberNULL{Value: true},
			"String":    &types.AttributeValueMemberS{Value: ""},
			"PtrString": &types.AttributeValueMemberNULL{Value: true},
			"Uint":      &types.AttributeValueMemberN{Value: "0"},
		},
	},
	"nested struct": {
		input: complexMarshalStruct{},
		expected: map[string]types.AttributeValue{
			"Simple": &types.AttributeValueMemberNULL{Value: true},
		},
	},
	"nested nil slice": {
		input: struct {
			Simple []string `dynamodbav:"simple"`
		}{},
		expected: map[string]types.AttributeValue{
			"simple": &types.AttributeValueMemberNULL{Value: true},
		},
	},
	"nested nil slice omit empty": {
		input: struct {
			Simple []string `dynamodbav:"simple,omitempty"`
		}{},
		expected: map[string]types.AttributeValue{},
	},
	"nested ignored field": {
		input: struct {
			Simple []string `dynamodbav:"-"`
		}{},
		expected: map[string]types.AttributeValue{},
	},
	"complex struct members with zero": {
		input: complexMarshalStruct{Simple: []simpleMarshalStruct{{Int: -2}, {Uint: 5}}},
		expected: map[string]types.AttributeValue{
			"Simple": &types.AttributeValueMemberL{
				Value: []types.AttributeValue{
					&types.AttributeValueMemberM{
						Value: map[string]types.AttributeValue{
							"Byte":      &types.AttributeValueMemberNULL{Value: true},
							"Bool":      &types.AttributeValueMemberBOOL{Value: false},
							"Float32":   &types.AttributeValueMemberN{Value: "0"},
							"Float64":   &types.AttributeValueMemberN{Value: "0"},
							"Int":       &types.AttributeValueMemberN{Value: "-2"},
							"Null":      &types.AttributeValueMemberNULL{Value: true},
							"String":    &types.AttributeValueMemberS{Value: ""},
							"PtrString": &types.AttributeValueMemberNULL{Value: true},
							"Uint":      &types.AttributeValueMemberN{Value: "0"},
						},
					},
					&types.AttributeValueMemberM{
						Value: map[string]types.AttributeValue{
							"Byte":      &types.AttributeValueMemberNULL{Value: true},
							"Bool":      &types.AttributeValueMemberBOOL{Value: false},
							"Float32":   &types.AttributeValueMemberN{Value: "0"},
							"Float64":   &types.AttributeValueMemberN{Value: "0"},
							"Int":       &types.AttributeValueMemberN{Value: "0"},
							"Null":      &types.AttributeValueMemberNULL{Value: true},
							"String":    &types.AttributeValueMemberS{Value: ""},
							"PtrString": &types.AttributeValueMemberNULL{Value: true},
							"Uint":      &types.AttributeValueMemberN{Value: "5"},
						},
					},
				},
			},
		},
	},
}

var marshallerListTestInputs = map[string]marshallerTestInput{
	"nil": {
		input:    nil,
		expected: []types.AttributeValue{},
	},
	"empty interface": {
		input:    []interface{}{},
		expected: []types.AttributeValue{},
	},
	"empty struct": {
		input:    []simpleMarshalStruct{},
		expected: []types.AttributeValue{},
	},
	"various types": {
		input: []interface{}{"a string", 12., 3.14, true, nil, false},
		expected: []types.AttributeValue{
			&types.AttributeValueMemberS{Value: "a string"},
			&types.AttributeValueMemberN{Value: "12"},
			&types.AttributeValueMemberN{Value: "3.14"},
			&types.AttributeValueMemberBOOL{Value: true},
			&types.AttributeValueMemberNULL{Value: true},
			&types.AttributeValueMemberBOOL{Value: false},
		},
	},
	"nested zero values": {
		input: []simpleMarshalStruct{{}},
		expected: []types.AttributeValue{
			&types.AttributeValueMemberM{
				Value: map[string]types.AttributeValue{
					"Byte":      &types.AttributeValueMemberNULL{Value: true},
					"Bool":      &types.AttributeValueMemberBOOL{Value: false},
					"Float32":   &types.AttributeValueMemberN{Value: "0"},
					"Float64":   &types.AttributeValueMemberN{Value: "0"},
					"Int":       &types.AttributeValueMemberN{Value: "0"},
					"Null":      &types.AttributeValueMemberNULL{Value: true},
					"String":    &types.AttributeValueMemberS{Value: ""},
					"PtrString": &types.AttributeValueMemberNULL{Value: true},
					"Uint":      &types.AttributeValueMemberN{Value: "0"},
				},
			},
		},
	},
}

func Test_New_Marshal(t *testing.T) {
	for name, test := range marshalerScalarInputs {
		t.Run(name, func(t *testing.T) {
			actual, err := Marshal(test.input)
			if test.err != nil {
				if err == nil {
					t.Errorf("Marshal with input %#v returned %#v, expected error `%s`",
						test.input, actual, test.err)
				} else if err.Error() != test.err.Error() {
					t.Errorf("Marshal with input %#v returned error `%s`, expected error `%s`",
						test.input, err, test.err)
				}
			} else {
				if err != nil {
					t.Errorf("Marshal with input %#v returned error `%s`", test.input, err)
				}
				compareObjects(t, test.expected, actual)
			}
		})
	}
}

func testMarshal(t *testing.T, test marshallerTestInput) {
}

func Test_New_Unmarshal(t *testing.T) {
	// Using the same inputs from Marshal, test the reverse mapping.
	for name, test := range marshalerScalarInputs {
		t.Run(name, func(t *testing.T) {
			if test.input == nil {
				t.Skip()
			}
			actual := reflect.New(reflect.TypeOf(test.input)).Interface()
			if err := Unmarshal(test.expected.(types.AttributeValue), actual); err != nil {
				t.Errorf("Unmarshal with input %#v returned error `%s`", test.expected, err)
			}
			compareObjects(t, test.input, reflect.ValueOf(actual).Elem().Interface())
		})
	}
}

func Test_New_UnmarshalError(t *testing.T) {
	// Test that we get an error using Unmarshal to convert to a nil value.
	expected := &InvalidUnmarshalError{Type: reflect.TypeOf(nil)}
	if err := Unmarshal(nil, nil); err == nil {
		t.Errorf("Unmarshal with input %T returned no error, expected error `%v`", nil, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("Unmarshal with input %T returned error `%v`, expected error `%v`", nil, err, expected)
	}

	// Test that we get an error using Unmarshal to convert to a non-pointer value.
	var actual map[string]interface{}
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual)}
	if err := Unmarshal(nil, actual); err == nil {
		t.Errorf("Unmarshal with input %T returned no error, expected error `%v`", actual, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("Unmarshal with input %T returned error `%v`, expected error `%v`", actual, err, expected)
	}

	// Test that we get an error using Unmarshal to convert to nil struct.
	var actual2 *struct{ A int }
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual2)}
	if err := Unmarshal(nil, actual2); err == nil {
		t.Errorf("Unmarshal with input %T returned no error, expected error `%v`", actual2, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("Unmarshal with input %T returned error `%v`, expected error `%v`", actual2, err, expected)
	}
}

func Test_New_MarshalMap(t *testing.T) {
	for name, test := range marshallerMapTestInputs {
		t.Run(name, func(t *testing.T) {
			actual, err := MarshalMap(test.input)
			if test.err != nil {
				if err == nil {
					t.Errorf("MarshalMap with input %#v returned %#v, expected error `%s`",
						test.input, actual, test.err)
				} else if err.Error() != test.err.Error() {
					t.Errorf("MarshalMap with input %#v returned error `%s`, expected error `%s`",
						test.input, err, test.err)
				}
			} else {
				if err != nil {
					t.Errorf("MarshalMap with input %#v returned error `%s`", test.input, err)
				}
				compareObjects(t, test.expected, actual)
			}
		})
	}
}

func Test_New_UnmarshalMap(t *testing.T) {
	// Using the same inputs from MarshalMap, test the reverse mapping.
	for name, test := range marshallerMapTestInputs {
		t.Run(name, func(t *testing.T) {
			if test.input == nil {
				t.Skip()
			}
			actual := reflect.New(reflect.TypeOf(test.input)).Interface()
			if err := UnmarshalMap(test.expected.(map[string]types.AttributeValue), actual); err != nil {
				t.Errorf("Unmarshal with input %#v returned error `%s`", test.expected, err)
			}
			compareObjects(t, test.input, reflect.ValueOf(actual).Elem().Interface())
		})
	}
}

func Test_New_UnmarshalMapError(t *testing.T) {
	// Test that we get an error using UnmarshalMap to convert to a nil value.
	expected := &InvalidUnmarshalError{Type: reflect.TypeOf(nil)}
	if err := UnmarshalMap(nil, nil); err == nil {
		t.Errorf("UnmarshalMap with input %T returned no error, expected error `%v`", nil, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalMap with input %T returned error `%v`, expected error `%v`", nil, err, expected)
	}

	// Test that we get an error using UnmarshalMap to convert to a non-pointer value.
	var actual map[string]interface{}
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual)}
	if err := UnmarshalMap(nil, actual); err == nil {
		t.Errorf("UnmarshalMap with input %T returned no error, expected error `%v`", actual, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalMap with input %T returned error `%v`, expected error `%v`", actual, err, expected)
	}

	// Test that we get an error using UnmarshalMap to convert to nil struct.
	var actual2 *struct{ A int }
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual2)}
	if err := UnmarshalMap(nil, actual2); err == nil {
		t.Errorf("UnmarshalMap with input %T returned no error, expected error `%v`", actual2, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalMap with input %T returned error `%v`, expected error `%v`", actual2, err, expected)
	}
}

func Test_New_MarshalList(t *testing.T) {
	for name, c := range marshallerListTestInputs {
		t.Run(name, func(t *testing.T) {
			actual, err := MarshalList(c.input)
			if c.err != nil {
				if err == nil {
					t.Fatalf("marshalList with input %#v returned %#v, expected error `%s`",
						c.input, actual, c.err)
				} else if err.Error() != c.err.Error() {
					t.Fatalf("marshalList with input %#v returned error `%s`, expected error `%s`",
						c.input, err, c.err)
				}
				return
			}
			if err != nil {
				t.Fatalf("MarshalList with input %#v returned error `%s`", c.input, err)
			}

			compareObjects(t, c.expected, actual)

		})
	}
}

func Test_New_UnmarshalList(t *testing.T) {
	// Using the same inputs from MarshalList, test the reverse mapping.
	for name, c := range marshallerListTestInputs {
		t.Run(name, func(t *testing.T) {
			if c.input == nil {
				t.Skip()
			}

			iv := reflect.ValueOf(c.input)

			actual := reflect.New(iv.Type())
			if iv.Kind() == reflect.Slice {
				actual.Elem().Set(reflect.MakeSlice(iv.Type(), iv.Len(), iv.Cap()))
			}

			if err := UnmarshalList(c.expected.([]types.AttributeValue), actual.Interface()); err != nil {
				t.Errorf("unmarshal with input %#v returned error `%s`", c.expected, err)
			}
			compareObjects(t, c.input, actual.Elem().Interface())
		})
	}
}

func Test_New_UnmarshalListError(t *testing.T) {
	// Test that we get an error using UnmarshalList to convert to a nil value.
	expected := &InvalidUnmarshalError{Type: reflect.TypeOf(nil)}
	if err := UnmarshalList(nil, nil); err == nil {
		t.Errorf("UnmarshalList with input %T returned no error, expected error `%v`", nil, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalList with input %T returned error `%v`, expected error `%v`", nil, err, expected)
	}

	// Test that we get an error using UnmarshalList to convert to a non-pointer value.
	var actual map[string]interface{}
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual)}
	if err := UnmarshalList(nil, actual); err == nil {
		t.Errorf("UnmarshalList with input %T returned no error, expected error `%v`", actual, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalList with input %T returned error `%v`, expected error `%v`", actual, err, expected)
	}

	// Test that we get an error using UnmarshalList to convert to nil struct.
	var actual2 *struct{ A int }
	expected = &InvalidUnmarshalError{Type: reflect.TypeOf(actual2)}
	if err := UnmarshalList(nil, actual2); err == nil {
		t.Errorf("UnmarshalList with input %T returned no error, expected error `%v`", actual2, expected)
	} else if err.Error() != expected.Error() {
		t.Errorf("UnmarshalList with input %T returned error `%v`, expected error `%v`", actual2, err, expected)
	}
}

func compareObjects(t *testing.T, expected interface{}, actual interface{}) {
	t.Helper()
	if !reflect.DeepEqual(expected, actual) {
		ev := reflect.ValueOf(expected)
		av := reflect.ValueOf(actual)
		if diff := cmpDiff(expected, actual); len(diff) != 0 {
			t.Errorf("expect kind(%s, %T) match actual kind(%s, %T)\n%s",
				ev.Kind(), ev.Interface(), av.Kind(), av.Interface(), diff)
		}
	}
}

func BenchmarkMarshalOneMember(b *testing.B) {
	fieldCache = &fieldCacher{}

	simple := simpleMarshalStruct{
		String:  "abc",
		Int:     123,
		Uint:    123,
		Float32: 123.321,
		Float64: 123.321,
		Bool:    true,
		Null:    nil,
	}
	type MyCompositeStruct struct {
		A simpleMarshalStruct `dynamodbav:"a"`
	}

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			if _, err := Marshal(MyCompositeStruct{
				A: simple,
			}); err != nil {
				b.Error("unexpected error:", err)
			}
		}
	})
}

func BenchmarkList20Ints(b *testing.B) {
	input := []int{}
	for i := 0; i < 20; i++ {
		input = append(input, i)
	}

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_, err := Marshal(input)
			if err != nil {
				b.Fatal(err)
			}
		}
	})
}

func BenchmarkStruct10Fields(b *testing.B) {

	type struct10Fields struct {
		Field1  int
		Field2  string
		Field3  int
		Field4  string
		Field5  string
		Field6  string
		Field7  int
		Field8  string
		Field9  int
		Field10 int
	}

	input := struct10Fields{
		Field1:  10,
		Field2:  "ASD",
		Field3:  70,
		Field4:  "qqqqq",
		Field5:  "AAA",
		Field6:  "bbb",
		Field7:  63,
		Field8:  "aa",
		Field9:  10,
		Field10: 63,
	}

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_, err := Marshal(input)
			if err != nil {
				b.Fatal(err)
			}
		}
	})
}

func BenchmarkMarshalTwoMembers(b *testing.B) {
	fieldCache = &fieldCacher{}

	simple := simpleMarshalStruct{
		String:  "abc",
		Int:     123,
		Uint:    123,
		Float32: 123.321,
		Float64: 123.321,
		Bool:    true,
		Null:    nil,
	}

	type MyCompositeStruct struct {
		A simpleMarshalStruct `dynamodbav:"a"`
		B simpleMarshalStruct `dynamodbav:"b"`
	}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			if _, err := Marshal(MyCompositeStruct{
				A: simple,
				B: simple,
			}); err != nil {
				b.Error("unexpected error:", err)
			}
		}
	})
}

func BenchmarkUnmarshalOneMember(b *testing.B) {
	fieldCache = &fieldCacher{}

	myStructAVMap, _ := Marshal(simpleMarshalStruct{
		String:  "abc",
		Int:     123,
		Uint:    123,
		Float32: 123.321,
		Float64: 123.321,
		Bool:    true,
		Null:    nil,
	})

	type MyCompositeStructOne struct {
		A simpleMarshalStruct `dynamodbav:"a"`
	}
	var out MyCompositeStructOne
	avMap := map[string]types.AttributeValue{
		"a": myStructAVMap,
	}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			if err := Unmarshal(&types.AttributeValueMemberM{Value: avMap}, &out); err != nil {
				b.Error("unexpected error:", err)
			}
		}
	})
}

func BenchmarkUnmarshalTwoMembers(b *testing.B) {
	fieldCache = &fieldCacher{}

	myStructAVMap, _ := Marshal(simpleMarshalStruct{
		String:  "abc",
		Int:     123,
		Uint:    123,
		Float32: 123.321,
		Float64: 123.321,
		Bool:    true,
		Null:    nil,
	})

	type MyCompositeStructTwo struct {
		A simpleMarshalStruct `dynamodbav:"a"`
		B simpleMarshalStruct `dynamodbav:"b"`
	}
	var out MyCompositeStructTwo
	avMap := map[string]types.AttributeValue{
		"a": myStructAVMap,
		"b": myStructAVMap,
	}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			if err := Unmarshal(&types.AttributeValueMemberM{Value: avMap}, &out); err != nil {
				b.Error("unexpected error:", err)
			}
		}
	})
}

func Test_Encode_YAML_TagKey(t *testing.T) {
	type Embedded struct {
		String string `yaml:"string"`
	}

	input := struct {
		String      string         `yaml:"string"`
		EmptyString string         `yaml:"empty"`
		OmitString  string         `yaml:"omitted,omitempty"`
		Ignored     string         `yaml:"-"`
		Byte        []byte         `yaml:"byte"`
		Float32     float32        `yaml:"float32"`
		Float64     float64        `yaml:"float64"`
		Int         int            `yaml:"int"`
		Uint        uint           `yaml:"uint"`
		Slice       []string       `yaml:"slice"`
		Map         map[string]int `yaml:"map"`
		NoTag       string
		Embedded    `yaml:"embedded"`
	}{
		String:  "String",
		Ignored: "Ignored",
		Slice:   []string{"one", "two"},
		Map: map[string]int{
			"one": 1,
			"two": 2,
		},
		NoTag: "NoTag",
		Embedded: Embedded{
			String: "String",
		},
	}

	expected := &types.AttributeValueMemberM{
		Value: map[string]types.AttributeValue{
			"string":  &types.AttributeValueMemberS{Value: "String"},
			"empty":   &types.AttributeValueMemberS{Value: ""},
			"byte":    &types.AttributeValueMemberNULL{Value: true},
			"float32": &types.AttributeValueMemberN{Value: "0"},
			"float64": &types.AttributeValueMemberN{Value: "0"},
			"int":     &types.AttributeValueMemberN{Value: "0"},
			"uint":    &types.AttributeValueMemberN{Value: "0"},
			"slice": &types.AttributeValueMemberL{
				Value: []types.AttributeValue{
					&types.AttributeValueMemberS{Value: "one"},
					&types.AttributeValueMemberS{Value: "two"},
				},
			},
			"map": &types.AttributeValueMemberM{
				Value: map[string]types.AttributeValue{
					"one": &types.AttributeValueMemberN{Value: "1"},
					"two": &types.AttributeValueMemberN{Value: "2"},
				},
			},
			"NoTag": &types.AttributeValueMemberS{Value: "NoTag"},
			"embedded": &types.AttributeValueMemberM{
				Value: map[string]types.AttributeValue{
					"string": &types.AttributeValueMemberS{Value: "String"},
				},
			},
		},
	}

	enc := NewEncoder(func(o *EncoderOptions) {
		o.TagKey = "yaml"
	})

	actual, err := enc.Encode(input)
	if err != nil {
		t.Errorf("Encode with input %#v returned error `%s`, expected nil", input, err)
	}

	compareObjects(t, expected, actual)
}

func cmpDiff(e, a interface{}) string {
	if !reflect.DeepEqual(e, a) {
		return fmt.Sprintf("%v != %v", e, a)
	}
	return ""
}
