Update Go AWS SDK to the latest version

This commit is contained in:
Andrey Smirnov
2019-07-13 00:03:55 +03:00
committed by Andrey Smirnov
parent d08be990ef
commit 94a72b23ff
2183 changed files with 885887 additions and 228114 deletions
@@ -0,0 +1,66 @@
# AWS DynamoDB Transaction Error Aware Client for Go
The client provides a workaround for [this bug](https://github.com/aws/aws-sdk-go/issues/2318)
## How to use
This example shows how to use the client to read transaction error cancellation reasons.
```go
sess := session.Must(session.NewSession())
svc := NewTxErrorAwareDynamoDBClient(sess)
input := &dynamodb.TransactWriteItemsInput{
//...
}
if _, err := svc.TransactWriteItems(input); err != nil {
txErr := err.(TxRequestFailure)
fmt.Println(txErr.CancellationReasons())
}
```
Sample response of the Println statement
```
{com.amazonaws.dynamodb.v20120810#TransactionCanceledException Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None] [{
Code: "ConditionalCheckFailed",
Item: {
AlbumTitle: {
S: "========== 43"
},
Artist: {
S: "Acme Band 14"
},
Year: {
N: "2017"
},
SongTitle: {
S: "Happy Day 12"
}
},
Message: "The conditional request failed"
} {
Code: "None"
} {
Code: "None"
}]}
[{
Code: "ConditionalCheckFailed",
Item: {
AlbumTitle: {
S: "========== 43"
},
Artist: {
S: "Acme Band 14"
},
Year: {
N: "2017"
},
SongTitle: {
S: "Happy Day 12"
}
},
Message: "The conditional request failed"
} {
Code: "None"
} {
Code: "None"
}]
```
@@ -0,0 +1,121 @@
// +build example
package transaction
import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
const TxAwareErrorUnmarshallerName = "awssdk.jsonrpc.TxAwareErrorUnmarshaller"
// New creates a new instance of the DynamoDB client with a session.
// The client's behaviour is same as what is returned by dynamodb.New(), except for richer error reasons.
func NewTxErrorAwareDynamoDBClient(p client.ConfigProvider, cfgs ...*aws.Config) *dynamodb.DynamoDB {
c := dynamodb.New(p, cfgs...)
// NOTE: Ignore if swap failed. Returning nil might fail app startup which is worse than inadequate error details.
c.Handlers.UnmarshalError.Swap(jsonrpc.UnmarshalErrorHandler.Name, request.NamedHandler{
Name: TxAwareErrorUnmarshallerName,
Fn: TxAwareUnmarshalError,
})
return c
}
// A RequestFailure is an interface to extract request failure information from an Error.
type TxRequestFailure interface {
awserr.RequestFailure
CancellationReasons() []dynamodb.CancellationReason
}
// TxAwareUnmarshalError unmarshals an error response for a JSON RPC service.
// This is exactly same as jsonrpc.UnmarshalError, except for attempt to parse CancellationReasons
func TxAwareUnmarshalError(req *request.Request) {
defer req.HTTPResponse.Body.Close()
var jsonErr jsonTxErrorResponse
err := json.NewDecoder(req.HTTPResponse.Body).Decode(&jsonErr)
if err == io.EOF {
req.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, req.HTTPResponse.Status, nil),
req.HTTPResponse.StatusCode,
req.RequestID,
)
return
} else if err != nil {
req.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization,
"failed decoding JSON RPC error response", err),
req.HTTPResponse.StatusCode,
req.RequestID,
)
return
}
codes := strings.SplitN(jsonErr.Code, "#", 2)
req.Error = newTxRequestError(
awserr.New(codes[len(codes)-1], jsonErr.Message, nil),
req.HTTPResponse.StatusCode,
req.RequestID,
jsonErr.CancellationReasons,
)
}
type jsonTxErrorResponse struct {
Code string `json:"__type"`
Message string `json:"message"`
CancellationReasons []dynamodb.CancellationReason `json:"CancellationReasons"`
}
// So that the Error interface type can be included as an anonymous field
// in the requestError struct and not conflict with the error.Error() method.
type awsError awserr.Error
// A TxRequestError wraps a request or service error.
// TxRequestError is awserr.requestError with additional cancellationReasons field
type txRequestError struct {
awsError
statusCode int
requestID string
cancellationReasons []dynamodb.CancellationReason
}
func newTxRequestError(err awserr.Error, statusCode int, requestID string, cancellationReasons []dynamodb.CancellationReason) TxRequestFailure {
return &txRequestError{
awsError: err,
statusCode: statusCode,
requestID: requestID,
cancellationReasons: cancellationReasons,
}
}
func (r txRequestError) Error() string {
extra := fmt.Sprintf("status code: %d, request id: %s",
r.statusCode, r.requestID)
return awserr.SprintError(r.Code(), r.Message(), extra, r.OrigErr())
}
func (r txRequestError) String() string {
return r.Error()
}
func (r txRequestError) StatusCode() int {
return r.statusCode
}
func (r txRequestError) RequestID() string {
return r.requestID
}
func (r txRequestError) CancellationReasons() []dynamodb.CancellationReason {
return r.cancellationReasons
}
@@ -0,0 +1,171 @@
// +build example
package transaction
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/awstesting/unit"
)
const errStatusCode = 400
const requestId = "requestId1"
func TestNewTxErrorAwareDynamoDBClient(t *testing.T) {
sess := unit.Session
svc := NewTxErrorAwareDynamoDBClient(sess)
if svc.Handlers.UnmarshalError.Len() != 1 {
t.Errorf("expected 1 UnmarshallErrorHandler, got %v", svc.Handlers.UnmarshalError.Len())
}
if svc.Handlers.UnmarshalError.Swap(TxAwareErrorUnmarshallerName, request.NamedHandler{}) == false {
t.Errorf("expected to contain %s, got none", TxAwareErrorUnmarshallerName)
}
}
func TestTxAwareUnmarshalError(t *testing.T) {
input := map[string]struct {
enc interface{}
err TxRequestFailure
}{
"Error response without CancellationReasons": {
jsonErrorResponse{
Code: "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException",
Message: "Requested resource not found",
},
txRequestError{
awsError: awserr.New("ResourceNotFoundException", "Requested resource not found", nil),
statusCode: errStatusCode,
requestID: requestId,
cancellationReasons: nil,
},
},
"Error response with empty CancellationReasons": {
jsonTxErrorResponse{
Code: "com.amazonaws.dynamodb.v20120810#TransactionCanceledException",
Message: "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]",
CancellationReasons: []dynamodb.CancellationReason{},
},
txRequestError{
awsError: awserr.New("TransactionCanceledException", "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]", nil),
statusCode: errStatusCode,
requestID: requestId,
cancellationReasons: []dynamodb.CancellationReason{},
},
},
"Error response with non-empty CancellationReasons": {
jsonTxErrorResponse{
Code: "com.amazonaws.dynamodb.v20120810#TransactionCanceledException",
Message: "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]",
CancellationReasons: []dynamodb.CancellationReason{
{
Code: aws.String("ConditionalCheckFailed"),
Item: map[string]*dynamodb.AttributeValue{
"hk": {S: aws.String("hkVal1")},
"attr": {S: aws.String("attrVal1")},
},
Message: aws.String("The conditional request failed"),
},
},
},
txRequestError{
awsError: awserr.New("TransactionCanceledException", "Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None, None]", nil),
statusCode: errStatusCode,
requestID: requestId,
cancellationReasons: []dynamodb.CancellationReason{
{
Code: aws.String("ConditionalCheckFailed"),
Item: map[string]*dynamodb.AttributeValue{
"hk": {S: aws.String("hkVal1")},
"attr": {S: aws.String("attrVal1")},
},
Message: aws.String("The conditional request failed"),
},
},
},
},
}
for name, in := range input {
t.Run(name, func(t *testing.T) {
if err := validateUnmarshallError(in.enc, in.err); err != nil {
t.Errorf("%s: expected nil, got %v", name, err)
}
})
}
}
func validateUnmarshallError(enc interface{}, err TxRequestFailure) error {
req := &request.Request{
HTTPResponse: &http.Response{
StatusCode: errStatusCode,
Body: newBufferCloser(encode(enc)),
},
RequestID: requestId,
}
TxAwareUnmarshalError(req)
if aerr, ok := req.Error.(TxRequestFailure); ok {
if err.RequestID() != aerr.RequestID() {
return fmt.Errorf("expected %v, got %v", err.RequestID(), aerr.RequestID())
}
if err.StatusCode() != aerr.StatusCode() {
return fmt.Errorf("expected %v, got %v", err.StatusCode(), aerr.StatusCode())
}
if err.Message() != aerr.Message() {
return fmt.Errorf("expected %v, got %v", err.Message(), aerr.Message())
}
if err.Code() != aerr.Code() {
return fmt.Errorf("expected %v, got %v", err.Code(), aerr.Code())
}
if err.OrigErr() != aerr.OrigErr() {
return fmt.Errorf("expected %v, got %v", err.OrigErr(), aerr.OrigErr())
}
if !reflect.DeepEqual(err.Error(), aerr.Error()) {
return fmt.Errorf("expected %v, got %v", err.Error(), aerr.Error())
}
if !reflect.DeepEqual(err.CancellationReasons(), aerr.CancellationReasons()) {
return fmt.Errorf("expected %v, got %v", err.CancellationReasons(), aerr.CancellationReasons())
}
} else {
return fmt.Errorf("expected type 'TxRequestFailure', got %T", req.Error)
}
return nil
}
func encode(v interface{}) []byte {
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(&v)
return buf.Bytes()
}
// Implementation of io.ReadCloser backed by bytes.Buffer
type bufferCloser struct {
bytes.Buffer
}
func newBufferCloser(data []byte) *bufferCloser {
return &bufferCloser{*bytes.NewBuffer(data)}
}
func (b *bufferCloser) Close() error {
b.Reset()
return nil
}
// Define error response without the cancellation reason
type jsonErrorResponse struct {
Code string `json:"__type"`
Message string `json:"message"`
}