diff --git a/.changelog/6b25cc1a38cf4e229728e8e169b9152b.json b/.changelog/6b25cc1a38cf4e229728e8e169b9152b.json new file mode 100644 index 0000000000..d8558a439b --- /dev/null +++ b/.changelog/6b25cc1a38cf4e229728e8e169b9152b.json @@ -0,0 +1,8 @@ +{ + "id": "6b25cc1a-38cf-4e22-9728-e8e169b9152b", + "type": "feature", + "description": "Add awsQueryCompatible error code translation support", + "modules": [ + "." + ] +} \ No newline at end of file diff --git a/private/model/api/api.go b/private/model/api/api.go index b3bf35728b..e888979cd1 100644 --- a/private/model/api/api.go +++ b/private/model/api/api.go @@ -96,8 +96,11 @@ type Metadata struct { ServiceID string NoResolveEndpoint bool + AWSQueryCompatible *awsQueryCompatible } +type awsQueryCompatible struct {} + // ProtocolSettings define how the SDK should handle requests in the context // of of a protocol. type ProtocolSettings struct { @@ -639,7 +642,11 @@ func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, {{- if and $.WithGeneratedTypedErrors (gt (len $.ShapeListErrors) 0) }} {{- $_ := $.AddSDKImport "private/protocol" }} svc.Handlers.UnmarshalError.PushBackNamed( + {{- if .Metadata.AWSQueryCompatible }} + protocol.NewUnmarshalErrorHandler({{ .ProtocolPackage }}.NewUnmarshalTypedErrorWithOptions(exceptionFromCode, {{ .ProtocolPackage }}.WithQueryCompatibility(queryExceptionFromCode))).NamedHandler(), + {{- else }} protocol.NewUnmarshalErrorHandler({{ .ProtocolPackage }}.NewUnmarshalTypedError(exceptionFromCode)).NamedHandler(), + {{- end}} ) {{- else }} svc.Handlers.UnmarshalError.PushBackNamed({{ .ProtocolPackage }}.UnmarshalErrorHandler) @@ -933,6 +940,13 @@ const ( "{{ $s.ErrorName }}": newError{{ $s.ShapeName }}, {{- end }} } + {{- if .Metadata.AWSQueryCompatible }} + var queryExceptionFromCode = map[string]func(protocol.ResponseMetadata, string)error { + {{- range $_, $s := $.ShapeListErrors }} + "{{ $s.ErrorName }}": newQueryCompatibleError{{ $s.ShapeName }}, + {{- end }} + } + {{- end }} {{- end }} `)) diff --git a/private/model/api/codegentest/models/awsquerycompatible/api-2.json b/private/model/api/codegentest/models/awsquerycompatible/api-2.json new file mode 100644 index 0000000000..eaca1fb20b --- /dev/null +++ b/private/model/api/codegentest/models/awsquerycompatible/api-2.json @@ -0,0 +1,75 @@ + + { + "version":"2.0", + "metadata":{ + "apiVersion":"2012-11-05", + "awsQueryCompatible": { + + }, + "endpointPrefix":"awsqc-exampleendpoint", + "jsonVersion":"1.1", + "protocol":"json", + "serviceAbbreviation":"AwsQueryCompatible", + "serviceFullName":"AWSQuery Compatible Service", + "serviceId":"AWSQueryCompatible", + "signatureVersion":"v4", + "uid":"awsquerycompatible-2012-11-05", + "xmlNamespace":"http://queue.amazonaws.com/doc/2012-11-05/" + }, + "operations":{ + "CreateQueue":{ + "name":"CreateQueue", + "http":{ + "method":"POST", + "requestUri":"/" + }, + "input":{"shape":"CreateQueueRequest"}, + "output":{ + "shape":"CreateQueueResult", + "resultWrapper":"CreateQueueResult" + }, + "errors":[ + {"shape":"QueueDeletedRecently"}, + {"shape":"QueueNameExists"} + ] + } + }, + "shapes":{ + "CreateQueueRequest":{ + "type":"structure", + "required":["QueueName"], + "members":{ + "QueueName":{"shape":"String"} + } + }, + "CreateQueueResult":{ + "type":"structure", + "members":{ + "QueueUrl":{"shape":"String"} + } + }, + "QueueDeletedRecently":{ + "type":"structure", + "members":{ + }, + "error":{ + "code":"AWS.SimpleQueueService.QueueDeletedRecently", + "httpStatusCode":400, + "senderFault":true + }, + "exception":true + }, + "QueueNameExists":{ + "type":"structure", + "members":{ + }, + "error":{ + "code":"QueueAlreadyExists", + "httpStatusCode":400, + "senderFault":true + }, + "exception":true + }, + "String":{"type":"string"} + } +} diff --git a/private/model/api/codegentest/service/awsquerycompatible/api.go b/private/model/api/codegentest/service/awsquerycompatible/api.go new file mode 100644 index 0000000000..f7323ddae7 --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/api.go @@ -0,0 +1,310 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +package awsquerycompatible + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/private/protocol" +) + +const opCreateQueue = "CreateQueue" + +// CreateQueueRequest generates a "aws/request.Request" representing the +// client's request for the CreateQueue operation. The "output" return +// value will be populated with the request's response once the request completes +// successfully. +// +// Use "Send" method on the returned Request to send the API call to the service. +// the "output" return value is not valid until after Send returns without error. +// +// See CreateQueue for more information on using the CreateQueue +// API call, and error handling. +// +// This method is useful when you want to inject custom logic or configuration +// into the SDK's request lifecycle. Such as custom headers, or retry logic. +// +// +// // Example sending a request using the CreateQueueRequest method. +// req, resp := client.CreateQueueRequest(params) +// +// err := req.Send() +// if err == nil { // resp is now filled +// fmt.Println(resp) +// } +// +// See also, https://docs.aws.amazon.com/goto/WebAPI/awsquerycompatible-2012-11-05/CreateQueue +func (c *AwsQueryCompatible) CreateQueueRequest(input *CreateQueueInput) (req *request.Request, output *CreateQueueOutput) { + op := &request.Operation{ + Name: opCreateQueue, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &CreateQueueInput{} + } + + output = &CreateQueueOutput{} + req = c.newRequest(op, input, output) + return +} + +// CreateQueue API operation for AWSQuery Compatible Service. +// +// Returns awserr.Error for service API and SDK errors. Use runtime type assertions +// with awserr.Error's Code and Message methods to get detailed information about +// the error. +// +// See the AWS API reference guide for AWSQuery Compatible Service's +// API operation CreateQueue for usage and error information. +// +// Returned Error Types: +// * QueueDeletedRecently +// +// * QueueNameExists +// +// See also, https://docs.aws.amazon.com/goto/WebAPI/awsquerycompatible-2012-11-05/CreateQueue +func (c *AwsQueryCompatible) CreateQueue(input *CreateQueueInput) (*CreateQueueOutput, error) { + req, out := c.CreateQueueRequest(input) + return out, req.Send() +} + +// CreateQueueWithContext is the same as CreateQueue with the addition of +// the ability to pass a context and additional request options. +// +// See CreateQueue for details on how to use this API operation. +// +// The context must be non-nil and will be used for request cancellation. If +// the context is nil a panic will occur. In the future the SDK may create +// sub-contexts for http.Requests. See https://golang.org/pkg/context/ +// for more information on using Contexts. +func (c *AwsQueryCompatible) CreateQueueWithContext(ctx aws.Context, input *CreateQueueInput, opts ...request.Option) (*CreateQueueOutput, error) { + req, out := c.CreateQueueRequest(input) + req.SetContext(ctx) + req.ApplyOptions(opts...) + return out, req.Send() +} + +type CreateQueueInput struct { + _ struct{} `type:"structure"` + + // QueueName is a required field + QueueName *string `type:"string" required:"true"` +} + +// String returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s CreateQueueInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s CreateQueueInput) GoString() string { + return s.String() +} + +// Validate inspects the fields of the type to determine if they are valid. +func (s *CreateQueueInput) Validate() error { + invalidParams := request.ErrInvalidParams{Context: "CreateQueueInput"} + if s.QueueName == nil { + invalidParams.Add(request.NewErrParamRequired("QueueName")) + } + + if invalidParams.Len() > 0 { + return invalidParams + } + return nil +} + +// SetQueueName sets the QueueName field's value. +func (s *CreateQueueInput) SetQueueName(v string) *CreateQueueInput { + s.QueueName = &v + return s +} + +type CreateQueueOutput struct { + _ struct{} `type:"structure"` + + QueueUrl *string `type:"string"` +} + +// String returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s CreateQueueOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s CreateQueueOutput) GoString() string { + return s.String() +} + +// SetQueueUrl sets the QueueUrl field's value. +func (s *CreateQueueOutput) SetQueueUrl(v string) *CreateQueueOutput { + s.QueueUrl = &v + return s +} + +type QueueDeletedRecently struct { + _ struct{} `type:"structure"` + RespMetadata protocol.ResponseMetadata `json:"-" xml:"-"` + Code_ *string + + Message_ *string `locationName:"message" type:"string"` +} + +// String returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s QueueDeletedRecently) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s QueueDeletedRecently) GoString() string { + return s.String() +} + +func newErrorQueueDeletedRecently(v protocol.ResponseMetadata) error { + return &QueueDeletedRecently{ + RespMetadata: v, + } +} +func newQueryCompatibleErrorQueueDeletedRecently(v protocol.ResponseMetadata, code string) error { + return &QueueDeletedRecently{ + RespMetadata: v, + Code_: &code, + } +} + +// Code returns the exception type name. +func (s *QueueDeletedRecently) Code() string { + if s.Code_ != nil { + return *s.Code_ + } + return "QueueDeletedRecently" +} + +// Message returns the exception's message. +func (s *QueueDeletedRecently) Message() string { + if s.Message_ != nil { + return *s.Message_ + } + return "" +} + +// OrigErr always returns nil, satisfies awserr.Error interface. +func (s *QueueDeletedRecently) OrigErr() error { + return nil +} + +func (s *QueueDeletedRecently) Error() string { + return fmt.Sprintf("%s: %s", s.Code(), s.Message()) +} + +// Status code returns the HTTP status code for the request's response error. +func (s *QueueDeletedRecently) StatusCode() int { + return s.RespMetadata.StatusCode +} + +// RequestID returns the service's response RequestID for request. +func (s *QueueDeletedRecently) RequestID() string { + return s.RespMetadata.RequestID +} + +type QueueNameExists struct { + _ struct{} `type:"structure"` + RespMetadata protocol.ResponseMetadata `json:"-" xml:"-"` + Code_ *string + + Message_ *string `locationName:"message" type:"string"` +} + +// String returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s QueueNameExists) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation. +// +// API parameter values that are decorated as "sensitive" in the API will not +// be included in the string output. The member name will be present, but the +// value will be replaced with "sensitive". +func (s QueueNameExists) GoString() string { + return s.String() +} + +func newErrorQueueNameExists(v protocol.ResponseMetadata) error { + return &QueueNameExists{ + RespMetadata: v, + } +} +func newQueryCompatibleErrorQueueNameExists(v protocol.ResponseMetadata, code string) error { + return &QueueNameExists{ + RespMetadata: v, + Code_: &code, + } +} + +// Code returns the exception type name. +func (s *QueueNameExists) Code() string { + if s.Code_ != nil { + return *s.Code_ + } + return "QueueNameExists" +} + +// Message returns the exception's message. +func (s *QueueNameExists) Message() string { + if s.Message_ != nil { + return *s.Message_ + } + return "" +} + +// OrigErr always returns nil, satisfies awserr.Error interface. +func (s *QueueNameExists) OrigErr() error { + return nil +} + +func (s *QueueNameExists) Error() string { + return fmt.Sprintf("%s: %s", s.Code(), s.Message()) +} + +// Status code returns the HTTP status code for the request's response error. +func (s *QueueNameExists) StatusCode() int { + return s.RespMetadata.StatusCode +} + +// RequestID returns the service's response RequestID for request. +func (s *QueueNameExists) RequestID() string { + return s.RespMetadata.RequestID +} diff --git a/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatible_test.go b/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatible_test.go new file mode 100644 index 0000000000..d924141f81 --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatible_test.go @@ -0,0 +1,111 @@ +//go:build go1.13 +// +build go1.13 + +package awsquerycompatible + +import ( + "errors" + "io" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/corehandlers" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/awstesting/unit" +) + +func TestAWSQuery(t *testing.T) { + cases := map[string]struct { + statusCode int + responseBody interface { + io.Reader + Len() int + } + headers map[string]string + expectErrorCode string + }{ + "when header is present": { + statusCode: 400, + responseBody: strings.NewReader(`{"__type":"com.amazonaws.awsquerycompatible#QueueDeletedRecently", "message":"Some user-visible message"}`), + expectErrorCode: "AWS.SimpleQueueService.QueueDeletedRecently", + headers: map[string]string{"x-amzn-query-error": "AWS.SimpleQueueService.QueueDeletedRecently;Sender"}, + }, + "for unmodeled error code": { + statusCode: 400, + responseBody: strings.NewReader(`{"__type":"com.amazonaws.awsquerycompatible#AccessDeniedException", "message":"Some user-visible message"}`), + expectErrorCode: "AccessDenied", + headers: map[string]string{"x-amzn-query-error": "AccessDenied;Sender"}, + }, + "when header is not present": { + statusCode: 400, + responseBody: strings.NewReader(`{"__type":"com.amazonaws.awsquerycompatible#AccessDeniedException", "message":"Some user-visible message"}`), + expectErrorCode: "AccessDeniedException", + headers: map[string]string{}, + }, + "when header is nil": { + statusCode: 400, + responseBody: strings.NewReader(`{"__type":"com.amazonaws.awsquerycompatible#AccessDeniedException", "message":"Some user-visible message"}`), + expectErrorCode: "AccessDeniedException", + headers: nil, + }, + "when header is malformed": { + statusCode: 400, + responseBody: strings.NewReader(`{"__type":"com.amazonaws.awsquerycompatible#QueueDeletedRecently", "message":"Some user-visible message"}`), + expectErrorCode: "QueueDeletedRecently", + headers: map[string]string{"x-amzn-query-error": "AWS.SimpleQueueService.QueueDeletedRecently-Sender"}, + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + client := New(unit.Session.Copy(), &aws.Config{ + MaxRetries: aws.Int(0), + }) + + client.Handlers.Send.Swap(corehandlers.SendHandler.Name, request.NamedHandler{ + Name: corehandlers.SendHandler.Name, + Fn: func(r *request.Request) { + r.HTTPResponse = &http.Response{ + StatusCode: c.statusCode, + ContentLength: func() int64 { + if c.responseBody == nil { + return 0 + } + return int64(c.responseBody.Len()) + }(), + Header: func() http.Header { + h := http.Header{} + if c.headers != nil { + for key, value := range c.headers { + h.Set(key, value) + } + } + return h + }(), + Body: ioutil.NopCloser(c.responseBody), + } + }, + }) + + _, err := client.CreateQueue(&CreateQueueInput{ + QueueName: aws.String("queueName"), + }) + if err == nil { + t.Fatalf("expect error, got none") + } + + var awsErr awserr.RequestFailure + if !errors.As(err, &awsErr) { + t.Fatalf("expect RequestFailure error, got %#v", err) + } + + if e, a := c.expectErrorCode, awsErr.Code(); e != a { + t.Errorf("expect %v code, got %v", e, a) + } + }) + } +} diff --git a/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatibleiface/interface.go b/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatibleiface/interface.go new file mode 100644 index 0000000000..f46fc1120f --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/awsquerycompatibleiface/interface.go @@ -0,0 +1,68 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +// Package awsquerycompatibleiface provides an interface to enable mocking the AWSQuery Compatible Service service client +// for testing your code. +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. +package awsquerycompatibleiface + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/private/model/api/codegentest/service/awsquerycompatible" +) + +// AwsQueryCompatibleAPI provides an interface to enable mocking the +// awsquerycompatible.AwsQueryCompatible service client's API operation, +// paginators, and waiters. This make unit testing your code that calls out +// to the SDK's service client's calls easier. +// +// The best way to use this interface is so the SDK's service client's calls +// can be stubbed out for unit testing your code with the SDK without needing +// to inject custom request handlers into the SDK's request pipeline. +// +// // myFunc uses an SDK service client to make a request to +// // AWSQuery Compatible Service. +// func myFunc(svc awsquerycompatibleiface.AwsQueryCompatibleAPI) bool { +// // Make svc.CreateQueue request +// } +// +// func main() { +// sess := session.New() +// svc := awsquerycompatible.New(sess) +// +// myFunc(svc) +// } +// +// In your _test.go file: +// +// // Define a mock struct to be used in your unit tests of myFunc. +// type mockAwsQueryCompatibleClient struct { +// awsquerycompatibleiface.AwsQueryCompatibleAPI +// } +// func (m *mockAwsQueryCompatibleClient) CreateQueue(input *awsquerycompatible.CreateQueueInput) (*awsquerycompatible.CreateQueueOutput, error) { +// // mock response/functionality +// } +// +// func TestMyFunc(t *testing.T) { +// // Setup Test +// mockSvc := &mockAwsQueryCompatibleClient{} +// +// myfunc(mockSvc) +// +// // Verify myFunc's functionality +// } +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. Its suggested to use the pattern above for testing, or using +// tooling to generate mocks to satisfy the interfaces. +type AwsQueryCompatibleAPI interface { + CreateQueue(*awsquerycompatible.CreateQueueInput) (*awsquerycompatible.CreateQueueOutput, error) + CreateQueueWithContext(aws.Context, *awsquerycompatible.CreateQueueInput, ...request.Option) (*awsquerycompatible.CreateQueueOutput, error) + CreateQueueRequest(*awsquerycompatible.CreateQueueInput) (*request.Request, *awsquerycompatible.CreateQueueOutput) +} + +var _ AwsQueryCompatibleAPI = (*awsquerycompatible.AwsQueryCompatible)(nil) diff --git a/private/model/api/codegentest/service/awsquerycompatible/doc.go b/private/model/api/codegentest/service/awsquerycompatible/doc.go new file mode 100644 index 0000000000..703d1f0357 --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/doc.go @@ -0,0 +1,26 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +// Package awsquerycompatible provides the client and types for making API +// requests to AWSQuery Compatible Service. +// +// See https://docs.aws.amazon.com/goto/WebAPI/awsquerycompatible-2012-11-05 for more information on this service. +// +// See awsquerycompatible package documentation for more information. +// https://docs.aws.amazon.com/sdk-for-go/api/service/awsquerycompatible/ +// +// Using the Client +// +// To contact AWSQuery Compatible Service with the SDK use the New function to create +// a new service client. With that client you can make API requests to the service. +// These clients are safe to use concurrently. +// +// See the SDK's documentation for more information on how to use the SDK. +// https://docs.aws.amazon.com/sdk-for-go/api/ +// +// See aws.Config documentation for more information on configuring SDK clients. +// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config +// +// See the AWSQuery Compatible Service client AwsQueryCompatible for more +// information on creating client for this service. +// https://docs.aws.amazon.com/sdk-for-go/api/service/awsquerycompatible/#New +package awsquerycompatible diff --git a/private/model/api/codegentest/service/awsquerycompatible/errors.go b/private/model/api/codegentest/service/awsquerycompatible/errors.go new file mode 100644 index 0000000000..574b909ffb --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/errors.go @@ -0,0 +1,27 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +package awsquerycompatible + +import ( + "github.com/aws/aws-sdk-go/private/protocol" +) + +const ( + + // ErrCodeQueueDeletedRecently for service response error code + // "QueueDeletedRecently". + ErrCodeQueueDeletedRecently = "QueueDeletedRecently" + + // ErrCodeQueueNameExists for service response error code + // "QueueNameExists". + ErrCodeQueueNameExists = "QueueNameExists" +) + +var exceptionFromCode = map[string]func(protocol.ResponseMetadata) error{ + "QueueDeletedRecently": newErrorQueueDeletedRecently, + "QueueNameExists": newErrorQueueNameExists, +} +var queryExceptionFromCode = map[string]func(protocol.ResponseMetadata, string) error{ + "QueueDeletedRecently": newQueryCompatibleErrorQueueDeletedRecently, + "QueueNameExists": newQueryCompatibleErrorQueueNameExists, +} diff --git a/private/model/api/codegentest/service/awsquerycompatible/service.go b/private/model/api/codegentest/service/awsquerycompatible/service.go new file mode 100644 index 0000000000..1e8cdacf4a --- /dev/null +++ b/private/model/api/codegentest/service/awsquerycompatible/service.go @@ -0,0 +1,107 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +package awsquerycompatible + +import ( + "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/private/protocol" + "github.com/aws/aws-sdk-go/private/protocol/jsonrpc" +) + +// AwsQueryCompatible provides the API operation methods for making requests to +// AWSQuery Compatible Service. See this package's package overview docs +// for details on the service. +// +// AwsQueryCompatible methods are safe to use concurrently. It is not safe to +// modify mutate any of the struct's properties though. +type AwsQueryCompatible struct { + *client.Client +} + +// Used for custom client initialization logic +var initClient func(*client.Client) + +// Used for custom request initialization logic +var initRequest func(*request.Request) + +// Service information constants +const ( + ServiceName = "AWSQueryCompatible" // Name of service. + EndpointsID = "awsqc-exampleendpoint" // ID to lookup a service endpoint with. + ServiceID = "AWSQueryCompatible" // ServiceID is a unique identifier of a specific service. +) + +// New creates a new instance of the AwsQueryCompatible client with a session. +// If additional configuration is needed for the client instance use the optional +// aws.Config parameter to add your extra config. +// +// Example: +// mySession := session.Must(session.NewSession()) +// +// // Create a AwsQueryCompatible client from just a session. +// svc := awsquerycompatible.New(mySession) +// +// // Create a AwsQueryCompatible client with additional configuration +// svc := awsquerycompatible.New(mySession, aws.NewConfig().WithRegion("us-west-2")) +func New(p client.ConfigProvider, cfgs ...*aws.Config) *AwsQueryCompatible { + c := p.ClientConfig(EndpointsID, cfgs...) + if c.SigningNameDerived || len(c.SigningName) == 0 { + c.SigningName = EndpointsID + // No Fallback + } + return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName, c.ResolvedRegion) +} + +// newClient creates, initializes and returns a new service client instance. +func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName, resolvedRegion string) *AwsQueryCompatible { + svc := &AwsQueryCompatible{ + Client: client.New( + cfg, + metadata.ClientInfo{ + ServiceName: ServiceName, + ServiceID: ServiceID, + SigningName: signingName, + SigningRegion: signingRegion, + PartitionID: partitionID, + Endpoint: endpoint, + APIVersion: "2012-11-05", + ResolvedRegion: resolvedRegion, + JSONVersion: "1.1", + }, + handlers, + ), + } + + // Handlers + svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler) + svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler) + svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler) + svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler) + svc.Handlers.UnmarshalError.PushBackNamed( + protocol.NewUnmarshalErrorHandler(jsonrpc.NewUnmarshalTypedErrorWithOptions(exceptionFromCode, jsonrpc.WithQueryCompatibility(queryExceptionFromCode))).NamedHandler(), + ) + + // Run custom client initialization if present + if initClient != nil { + initClient(svc.Client) + } + + return svc +} + +// newRequest creates a new request for a AwsQueryCompatible operation and runs any +// custom request initialization. +func (c *AwsQueryCompatible) newRequest(op *request.Operation, params, data interface{}) *request.Request { + req := c.NewRequest(op, params, data) + + // Run custom request initialization if present + if initRequest != nil { + initRequest(req) + } + + return req +} diff --git a/private/model/api/shape.go b/private/model/api/shape.go index e4795ce885..f46cff9a84 100644 --- a/private/model/api/shape.go +++ b/private/model/api/shape.go @@ -783,6 +783,9 @@ type {{ $.ShapeName }} struct { {{- if $.Exception }} {{- $_ := $.API.AddSDKImport "private/protocol" }} RespMetadata protocol.ResponseMetadata` + "`json:\"-\" xml:\"-\"`" + ` + {{- if $.API.Metadata.AWSQueryCompatible }} + Code_ *string + {{- end }} {{- end }} {{- if $.OutputEventStreamAPI }} @@ -922,8 +925,22 @@ func newError{{ $.ShapeName }}(v protocol.ResponseMetadata) error { } } +{{- if $.API.Metadata.AWSQueryCompatible }} +func newQueryCompatibleError{{ $.ShapeName }}(v protocol.ResponseMetadata, code string) error { + return &{{ $.ShapeName }}{ + RespMetadata: v, + Code_: &code, + } +} +{{- end }} + // Code returns the exception type name. func (s *{{ $.ShapeName }}) Code() string { + {{- if $.API.Metadata.AWSQueryCompatible }} + if s.Code_ != nil { + return *s.Code_ + } + {{- end }} return "{{ $.ErrorName }}" } diff --git a/private/protocol/jsonrpc/unmarshal_error.go b/private/protocol/jsonrpc/unmarshal_error.go index c0c52e2db0..4f933f2a1c 100644 --- a/private/protocol/jsonrpc/unmarshal_error.go +++ b/private/protocol/jsonrpc/unmarshal_error.go @@ -13,10 +13,17 @@ import ( "github.com/aws/aws-sdk-go/private/protocol/json/jsonutil" ) +const ( + awsQueryError = "x-amzn-query-error" + // A valid header example - "x-amzn-query-error": ";" + awsQueryErrorPartsCount = 2 +) + // UnmarshalTypedError provides unmarshaling errors API response errors // for both typed and untyped errors. type UnmarshalTypedError struct { exceptions map[string]func(protocol.ResponseMetadata) error + queryExceptions map[string]func(protocol.ResponseMetadata, string) error } // NewUnmarshalTypedError returns an UnmarshalTypedError initialized for the @@ -24,6 +31,21 @@ type UnmarshalTypedError struct { func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError { return &UnmarshalTypedError{ exceptions: exceptions, + queryExceptions: map[string]func(protocol.ResponseMetadata, string) error{}, + } +} + +func NewUnmarshalTypedErrorWithOptions(exceptions map[string]func(protocol.ResponseMetadata) error, optFns ...func(*UnmarshalTypedError)) *UnmarshalTypedError { + unmarshaledError := NewUnmarshalTypedError(exceptions) + for _, fn := range optFns { + fn(unmarshaledError) + } + return unmarshaledError +} + +func WithQueryCompatibility(queryExceptions map[string]func(protocol.ResponseMetadata, string) error) func(*UnmarshalTypedError) { + return func(typedError *UnmarshalTypedError) { + typedError.queryExceptions = queryExceptions } } @@ -50,18 +72,32 @@ func (u *UnmarshalTypedError) UnmarshalError( code := codeParts[len(codeParts)-1] msg := jsonErr.Message + queryCodeParts := queryCodeParts(resp, u) + if fn, ok := u.exceptions[code]; ok { - // If exception code is know, use associated constructor to get a value + // If query-compatible exceptions are found and query-error-header is found, + // then use associated constructor to get exception with query error code. + // + // If exception code is known, use associated constructor to get a value // for the exception that the JSON body can be unmarshaled into. - v := fn(respMeta) + var v error + queryErrFn, queryExceptionsFound := u.queryExceptions[code] + if len(queryCodeParts) == awsQueryErrorPartsCount && queryExceptionsFound { + v = queryErrFn(respMeta, queryCodeParts[0]) + } else { + v = fn(respMeta) + } err := jsonutil.UnmarshalJSONCaseInsensitive(v, body) if err != nil { return nil, err } - return v, nil } + if len(queryCodeParts) == awsQueryErrorPartsCount && len(u.queryExceptions) > 0 { + code = queryCodeParts[0] + } + // fallback to unmodeled generic exceptions return awserr.NewRequestFailure( awserr.New(code, msg, nil), @@ -70,6 +106,16 @@ func (u *UnmarshalTypedError) UnmarshalError( ), nil } +// A valid header example - "x-amzn-query-error": ";" +func queryCodeParts(resp *http.Response, u *UnmarshalTypedError) []string { + queryCodeHeader := resp.Header.Get(awsQueryError) + var queryCodeParts []string + if queryCodeHeader != "" && len(u.queryExceptions) > 0 { + queryCodeParts = strings.Split(queryCodeHeader, ";") + } + return queryCodeParts +} + // UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc // protocol request errors var UnmarshalErrorHandler = request.NamedHandler{