-
Notifications
You must be signed in to change notification settings - Fork 670
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #792 from aws/route53-unmarshalError
- Loading branch information
Showing
9 changed files
with
315 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
.../java/software/amazon/smithy/aws/go/codegen/customization/Route53ErrorCustomizations.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package software.amazon.smithy.aws.go.codegen.customization; | ||
|
||
import java.util.List; | ||
import software.amazon.smithy.aws.traits.ServiceTrait; | ||
import software.amazon.smithy.go.codegen.SymbolUtils; | ||
import software.amazon.smithy.go.codegen.integration.GoIntegration; | ||
import software.amazon.smithy.go.codegen.integration.MiddlewareRegistrar; | ||
import software.amazon.smithy.go.codegen.integration.RuntimeClientPlugin; | ||
import software.amazon.smithy.model.Model; | ||
import software.amazon.smithy.model.shapes.OperationShape; | ||
import software.amazon.smithy.model.shapes.ServiceShape; | ||
import software.amazon.smithy.utils.ListUtils; | ||
|
||
public class Route53ErrorCustomizations implements GoIntegration { | ||
private static String ADD_ERROR_HANDLER_INTERNAL = "HandleCustomErrorDeserialization"; | ||
|
||
@Override | ||
public byte getOrder() { | ||
// The associated customization ordering is relative to operation deserializers | ||
// and thus the integration should be added at the end. | ||
return 127; | ||
} | ||
|
||
@Override | ||
public List<RuntimeClientPlugin> getClientPlugins() { | ||
return ListUtils.of( | ||
RuntimeClientPlugin.builder() | ||
.operationPredicate(Route53ErrorCustomizations::supportsCustomError) | ||
.registerMiddleware(MiddlewareRegistrar.builder() | ||
.resolvedFunction(SymbolUtils.createValueSymbolBuilder(ADD_ERROR_HANDLER_INTERNAL, | ||
AwsCustomGoDependency.ROUTE53_CUSTOMIZATION).build()) | ||
.build()) | ||
.build() | ||
); | ||
} | ||
|
||
// returns true if the operation supports custom route53 error response | ||
private static boolean supportsCustomError(Model model, ServiceShape service, OperationShape operation){ | ||
if (!isRoute53Service(model, service)) { | ||
return false; | ||
} | ||
|
||
return operation.getId().getName().equalsIgnoreCase("ChangeResourceRecordSets"); | ||
} | ||
|
||
// returns true if service is route53 | ||
private static boolean isRoute53Service(Model model, ServiceShape service) { | ||
String serviceId= service.expectTrait(ServiceTrait.class).getSdkId(); | ||
return serviceId.equalsIgnoreCase("Route 53"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
service/route53/internal/customizations/custom_error_deser.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package customizations | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/xml" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"strings" | ||
|
||
"github.com/awslabs/smithy-go" | ||
"github.com/awslabs/smithy-go/middleware" | ||
"github.com/awslabs/smithy-go/ptr" | ||
smithyhttp "github.com/awslabs/smithy-go/transport/http" | ||
smithyxml "github.com/awslabs/smithy-go/xml" | ||
|
||
awsmiddle "github.com/aws/aws-sdk-go-v2/aws/middleware" | ||
"github.com/aws/aws-sdk-go-v2/service/route53/types" | ||
) | ||
|
||
// HandleCustomErrorDeserialization check if Route53 response is an error and needs | ||
// custom error deserialization. | ||
// | ||
func HandleCustomErrorDeserialization(stack *middleware.Stack) { | ||
stack.Deserialize.Insert(&processResponseMiddleware{}, "OperationDeserializer", middleware.After) | ||
} | ||
|
||
// middleware to process raw response and look for error response with InvalidChangeBatch error tag | ||
type processResponseMiddleware struct{} | ||
|
||
// ID returns the middleware ID. | ||
func (*processResponseMiddleware) ID() string { return "Route53:ProcessResponseForCustomErrorResponse" } | ||
|
||
func (m *processResponseMiddleware) HandleDeserialize( | ||
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( | ||
out middleware.DeserializeOutput, metadata middleware.Metadata, err error, | ||
) { | ||
out, metadata, err = next.HandleDeserialize(ctx, in) | ||
if err != nil { | ||
return out, metadata, err | ||
} | ||
|
||
response, ok := out.RawResponse.(*smithyhttp.Response) | ||
if !ok { | ||
return out, metadata, &smithy.DeserializationError{Err: fmt.Errorf("unknown transport type %T", out.RawResponse)} | ||
} | ||
|
||
// check if success response | ||
if response.StatusCode >= 200 && response.StatusCode < 300 { | ||
return | ||
} | ||
|
||
var readBuff bytes.Buffer | ||
body := io.TeeReader(response.Body, &readBuff) | ||
|
||
rootDecoder := xml.NewDecoder(body) | ||
t, err := smithyxml.FetchRootElement(rootDecoder) | ||
if err == io.EOF { | ||
return out, metadata, nil | ||
} | ||
|
||
// rewind response body | ||
response.Body = ioutil.NopCloser(io.MultiReader(&readBuff, response.Body)) | ||
|
||
// if start tag is "InvalidChangeBatch", the error response needs custom unmarshaling. | ||
if strings.EqualFold(t.Name.Local, "InvalidChangeBatch") { | ||
return out, metadata, route53CustomErrorDeser(&metadata, response) | ||
} | ||
|
||
return out, metadata, err | ||
} | ||
|
||
// error type for invalidChangeBatchError | ||
type invalidChangeBatchError struct { | ||
Messages []string `xml:"Messages>Message"` | ||
RequestID string `xml:"RequestId"` | ||
} | ||
|
||
func route53CustomErrorDeser(metadata *middleware.Metadata, response *smithyhttp.Response) error { | ||
err := invalidChangeBatchError{} | ||
xml.NewDecoder(response.Body).Decode(&err) | ||
|
||
// set request id in metadata | ||
if len(err.RequestID) != 0 { | ||
awsmiddle.SetRequestIDMetadata(metadata, err.RequestID) | ||
} | ||
|
||
return &types.InvalidChangeBatch{ | ||
Message: ptr.String("ChangeBatch errors occurred"), | ||
Messages: ptr.StringSlice(err.Messages), | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
service/route53/internal/customizations/custom_error_deser_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package customizations_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/route53" | ||
"github.com/aws/aws-sdk-go-v2/service/route53/types" | ||
) | ||
|
||
func TestCustomErrorDeserialization(t *testing.T) { | ||
cases := map[string]struct { | ||
responseStatus int | ||
responseBody []byte | ||
expectedError string | ||
expectedRequestID string | ||
expectedResponseID string | ||
}{ | ||
"invalidChangeBatchError": { | ||
responseStatus: 500, | ||
responseBody: []byte(`<?xml version="1.0" encoding="UTF-8"?> | ||
<InvalidChangeBatch xmlns="https://route53.amazonaws.com/doc/2013-04-01/"> | ||
<Messages> | ||
<Message>Tried to create resource record set duplicate.example.com. type A, but it already exists</Message> | ||
</Messages> | ||
<RequestId>b25f48e8-84fd-11e6-80d9-574e0c4664cb</RequestId> | ||
</InvalidChangeBatch>`), | ||
expectedError: "InvalidChangeBatch: ChangeBatch errors occurred", | ||
expectedRequestID: "b25f48e8-84fd-11e6-80d9-574e0c4664cb", | ||
}, | ||
|
||
"standardRestXMLError": { | ||
responseStatus: 500, | ||
responseBody: []byte(`<?xml version="1.0"?> | ||
<ErrorResponse xmlns="http://route53.amazonaws.com/doc/2016-09-07/"> | ||
<Error> | ||
<Type>Sender</Type> | ||
<Code>MalformedXML</Code> | ||
<Message>1 validation error detected: Value null at 'route53#ChangeSet' failed to satisfy constraint: Member must not be null</Message> | ||
</Error> | ||
<RequestId>b25f48e8-84fd-11e6-80d9-574e0c4664cb</RequestId> | ||
</ErrorResponse> | ||
`), | ||
expectedError: "1 validation error detected:", | ||
expectedRequestID: "b25f48e8-84fd-11e6-80d9-574e0c4664cb", | ||
}, | ||
|
||
"Success response": { | ||
responseStatus: 200, | ||
responseBody: []byte(`<?xml version="1.0" encoding="UTF-8"?> | ||
<ChangeResourceRecordSetsResponse> | ||
<ChangeInfo> | ||
<Comment>mockComment</Comment> | ||
<Id>mockID</Id> | ||
</ChangeInfo> | ||
</ChangeResourceRecordSetsResponse>`), | ||
expectedResponseID: "mockID", | ||
}, | ||
} | ||
|
||
for name, c := range cases { | ||
server := httptest.NewServer(http.HandlerFunc( | ||
func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(c.responseStatus) | ||
w.Write(c.responseBody) | ||
})) | ||
defer server.Close() | ||
|
||
t.Run(name, func(t *testing.T) { | ||
svc := route53.NewFromConfig(aws.Config{ | ||
Region: "us-east-1", | ||
EndpointResolver: aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) { | ||
return aws.Endpoint{ | ||
URL: server.URL, | ||
SigningName: "route53", | ||
}, nil | ||
}), | ||
Retryer: aws.NoOpRetryer{}, | ||
}) | ||
resp, err := svc.ChangeResourceRecordSets(context.Background(), &route53.ChangeResourceRecordSetsInput{ | ||
ChangeBatch: &types.ChangeBatch{ | ||
Changes: []*types.Change{}, | ||
Comment: aws.String("mock"), | ||
}, | ||
HostedZoneId: aws.String("zone"), | ||
}) | ||
|
||
if err == nil && len(c.expectedError) != 0 { | ||
t.Fatalf("expected err, got none") | ||
} | ||
|
||
if len(c.expectedError) != 0 { | ||
if e, a := c.expectedError, err.Error(); !strings.Contains(a, e) { | ||
t.Fatalf("expected error to be %s, got %s", e, a) | ||
} | ||
|
||
var responseError interface { | ||
ServiceRequestID() string | ||
} | ||
|
||
if !errors.As(err, &responseError) { | ||
t.Fatalf("expected error to be of type %T, was not", responseError) | ||
} | ||
|
||
if e, a := c.expectedRequestID, responseError.ServiceRequestID(); !strings.EqualFold(e, a) { | ||
t.Fatalf("expected request id to be %s, got %s", e, a) | ||
} | ||
} | ||
|
||
if len(c.expectedResponseID) != 0 { | ||
if e, a := c.expectedResponseID, *resp.ChangeInfo.Id; !strings.EqualFold(e, a) { | ||
t.Fatalf("expected response to have id %v, got %v", e, a) | ||
} | ||
} | ||
|
||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Package customizations provides customizations for the Amazon Route53 API client. | ||
// | ||
// This package provides support for following customizations | ||
// | ||
// Process Response Middleware: used for custom error deserializing | ||
// | ||
// | ||
// Process Response Middleware | ||
// | ||
// Route53 operation "ChangeResourceRecordSets" can have an error response returned in | ||
// a slightly different format. This customization is only applicable to | ||
// ChangeResourceRecordSets operation of Route53. | ||
// | ||
// Here's a sample error response: | ||
// | ||
// <?xml version="1.0" encoding="UTF-8"?> | ||
// <InvalidChangeBatch xmlns="https://route53.amazonaws.com/doc/2013-04-01/"> | ||
// <Messages> | ||
// <Message>Tried to create resource record set duplicate.example.com. type A, but it already exists</Message> | ||
// </Messages> | ||
// </InvalidChangeBatch> | ||
// | ||
// | ||
// The processResponse middleware customizations enables SDK to check for an error | ||
// response starting with "InvalidChangeBatch" tag prior to deserialization. | ||
// | ||
// As this check in error response needs to be performed earlier than response | ||
// deserialization. Since the behavior of Deserialization is in | ||
// reverse order to the other stack steps its easier to consider that "after" means | ||
// "before". | ||
// | ||
// Middleware layering: | ||
// | ||
// HTTP Response -> process response error -> deserialize | ||
// | ||
// | ||
// In case the returned error response has `InvalidChangeBatch` format, the error is | ||
// deserialized and returned. The operation deserializer does not attempt to deserialize | ||
// as an error is returned by the process response error middleware. | ||
// | ||
package customizations |