mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-07 22:20:24 +00:00
Update Go AWS SDK to the latest version
This commit is contained in:
committed by
Andrey Smirnov
parent
d08be990ef
commit
94a72b23ff
Generated
Vendored
+66
@@ -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"
|
||||
}]
|
||||
```
|
||||
Generated
Vendored
+121
@@ -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
|
||||
}
|
||||
|
||||
Generated
Vendored
+171
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user