From 8425865283d0a3bcafea46a92346d10dce7e6fe5 Mon Sep 17 00:00:00 2001 From: Anna-Karin Salander Date: Wed, 23 Sep 2020 16:10:05 -0700 Subject: [PATCH] Adds support for parsing errors with a top level error root XML structure such as InvalidRequest errors --- .../bugfix-AWSS3Control-1ad7752.json | 5 + services/s3control/pom.xml | 10 + .../S3ControlIntegrationTest.java | 25 ++ .../TopLevelXMLErrorInterceptor.java | 91 +++++++ .../services/s3control/execution.interceptors | 3 +- ...MLErrorTypesTranslationFunctionalTest.java | 232 ++++++++++++++++++ .../TopLevelXMLErrorInterceptorTest.java | 145 +++++++++++ .../src/test/resources/log4j.properties | 33 +++ 8 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 .changes/next-release/bugfix-AWSS3Control-1ad7752.json create mode 100644 services/s3control/src/main/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptor.java create mode 100644 services/s3control/src/test/java/software/amazon/awssdk/services/s3control/functionaltests/XMLErrorTypesTranslationFunctionalTest.java create mode 100644 services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptorTest.java create mode 100644 services/s3control/src/test/resources/log4j.properties diff --git a/.changes/next-release/bugfix-AWSS3Control-1ad7752.json b/.changes/next-release/bugfix-AWSS3Control-1ad7752.json new file mode 100644 index 000000000000..42d2da4c751c --- /dev/null +++ b/.changes/next-release/bugfix-AWSS3Control-1ad7752.json @@ -0,0 +1,5 @@ +{ + "category": "AWS S3 Control", + "type": "bugfix", + "description": "Adds support for parsing errors with a top level error root XML structure such as InvalidRequest errors" +} diff --git a/services/s3control/pom.xml b/services/s3control/pom.xml index 712ecfd627aa..2583ef06ae17 100644 --- a/services/s3control/pom.xml +++ b/services/s3control/pom.xml @@ -51,6 +51,11 @@ aws-xml-protocol ${awsjavasdk.version} + + software.amazon.awssdk + aws-query-protocol + ${awsjavasdk.version} + software.amazon.awssdk protocol-core @@ -78,5 +83,10 @@ ${awsjavasdk.version} test + + com.github.tomakehurst + wiremock + test + diff --git a/services/s3control/src/it/java/software.amazon.awssdk.services.s3control/S3ControlIntegrationTest.java b/services/s3control/src/it/java/software.amazon.awssdk.services.s3control/S3ControlIntegrationTest.java index 48954def06cd..02a7621a23d6 100644 --- a/services/s3control/src/it/java/software.amazon.awssdk.services.s3control/S3ControlIntegrationTest.java +++ b/services/s3control/src/it/java/software.amazon.awssdk.services.s3control/S3ControlIntegrationTest.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.services.s3control.model.DeletePublicAccessBlockRequest; import software.amazon.awssdk.services.s3control.model.GetPublicAccessBlockResponse; +import software.amazon.awssdk.services.s3control.model.InvalidRequestException; import software.amazon.awssdk.services.s3control.model.NoSuchPublicAccessBlockConfigurationException; import software.amazon.awssdk.services.s3control.model.PutPublicAccessBlockResponse; import software.amazon.awssdk.services.s3control.model.S3ControlException; @@ -91,6 +92,30 @@ public void putPublicAccessBlock_NoSuchAccount() { } } + @Test + public void describeJob_NotFound() { + try { + client.describeJob(r -> r.accountId(accountId).jobId("jobid")); + fail("Expected exception"); + } catch (InvalidRequestException e) { + assertEquals("InvalidRequest", e.awsErrorDetails().errorCode()); + assertEquals("Job not found", e.awsErrorDetails().errorMessage()); + assertNotNull(e.requestId()); + } + } + + @Test + public void listJobs_IncorrectStatus() { + try { + client.listJobs(r -> r.jobStatusesWithStrings("TEST").accountId(accountId)); + fail("Expected exception"); + } catch (InvalidRequestException e) { + assertEquals("InvalidRequest", e.awsErrorDetails().errorCode()); + assertEquals("Request invalid", e.awsErrorDetails().errorMessage()); + assertNotNull(e.requestId()); + } + } + @Test public void getPublicAccessBlock_NoSuchAccount() { try { diff --git a/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptor.java b/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptor.java new file mode 100644 index 000000000000..7f706fec0af8 --- /dev/null +++ b/services/s3control/src/main/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptor.java @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3control.internal.interceptors; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.protocols.query.unmarshall.XmlDomParser; +import software.amazon.awssdk.protocols.query.unmarshall.XmlElement; +import software.amazon.awssdk.services.s3control.model.InvalidRequestException; +import software.amazon.awssdk.services.s3control.model.S3ControlException; + +/** + * Translate S3 style exceptions, which have the Error tag at root instead of wrapped in ErrorResponse. + * If the exception follows this structure but isn't known, create an S3ControlException with the + * error code and message. + */ +@SdkInternalApi +public final class TopLevelXMLErrorInterceptor implements ExecutionInterceptor { + + private static final String XML_ERROR_ROOT = "Error"; + private static final String XML_ELEMENT_CODE = "Code"; + private static final String XML_ELEMENT_MESSAGE = "Message"; + + private static final String INVALID_REQUEST_CODE = "InvalidRequest"; + + @Override + public Throwable modifyException(Context.FailedExecution context, ExecutionAttributes executionAttributes) { + S3ControlException exception = (S3ControlException) (context.exception()); + AwsErrorDetails awsErrorDetails = exception.awsErrorDetails(); + + if (!(exception.getMessage().contains("null"))) { + return context.exception(); + } + + XmlElement errorXml = XmlDomParser.parse(awsErrorDetails.rawResponse().asInputStream()); + if (!XML_ERROR_ROOT.equals(errorXml.elementName())) { + return context.exception(); + } + + String errorCode = getChildElement(errorXml, XML_ELEMENT_CODE); + String errorMessage = getChildElement(errorXml, XML_ELEMENT_MESSAGE); + + S3ControlException.Builder builder = findMatchingBuilder(errorCode); + copyErrorDetails(exception, builder); + return builder + .message(errorMessage) + .awsErrorDetails(copyAwsErrorDetails(awsErrorDetails, errorCode, errorMessage)) + .build(); + } + + private String getChildElement(XmlElement root, String elementName) { + return root.getOptionalElementByName(elementName) + .map(XmlElement::textContent) + .orElse(null); + } + + private S3ControlException.Builder findMatchingBuilder(String errorCode) { + return INVALID_REQUEST_CODE.equals(errorCode) ? + InvalidRequestException.builder() : + S3ControlException.builder(); + } + + private void copyErrorDetails(S3ControlException exception, S3ControlException.Builder builder) { + builder.cause(exception.getCause()); + builder.requestId(exception.requestId()); + builder.extendedRequestId(exception.extendedRequestId()); + } + + private AwsErrorDetails copyAwsErrorDetails(AwsErrorDetails original, String errorCode, String errorMessage) { + return original.toBuilder() + .errorMessage(errorMessage) + .errorCode(errorCode) + .build(); + } +} diff --git a/services/s3control/src/main/resources/software/amazon/awssdk/services/s3control/execution.interceptors b/services/s3control/src/main/resources/software/amazon/awssdk/services/s3control/execution.interceptors index b19e9f79a356..e6f9225c634e 100644 --- a/services/s3control/src/main/resources/software/amazon/awssdk/services/s3control/execution.interceptors +++ b/services/s3control/src/main/resources/software/amazon/awssdk/services/s3control/execution.interceptors @@ -1,2 +1,3 @@ software.amazon.awssdk.services.s3control.internal.interceptors.EndpointAddressInterceptor -software.amazon.awssdk.services.s3control.internal.interceptors.PayloadSigningInterceptor \ No newline at end of file +software.amazon.awssdk.services.s3control.internal.interceptors.PayloadSigningInterceptor +software.amazon.awssdk.services.s3control.internal.interceptors.TopLevelXMLErrorInterceptor \ No newline at end of file diff --git a/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/functionaltests/XMLErrorTypesTranslationFunctionalTest.java b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/functionaltests/XMLErrorTypesTranslationFunctionalTest.java new file mode 100644 index 000000000000..9680e84073bf --- /dev/null +++ b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/functionaltests/XMLErrorTypesTranslationFunctionalTest.java @@ -0,0 +1,232 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3control.functionaltests; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.net.URI; +import java.util.concurrent.CompletionException; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import org.junit.Rule; +import org.junit.Test; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3control.S3ControlAsyncClient; +import software.amazon.awssdk.services.s3control.S3ControlAsyncClientBuilder; +import software.amazon.awssdk.services.s3control.S3ControlClient; +import software.amazon.awssdk.services.s3control.S3ControlClientBuilder; +import software.amazon.awssdk.services.s3control.model.InvalidRequestException; +import software.amazon.awssdk.services.s3control.model.NoSuchPublicAccessBlockConfigurationException; +import software.amazon.awssdk.services.s3control.model.S3ControlException; + +public class XMLErrorTypesTranslationFunctionalTest { + + private static final URI HTTP_LOCALHOST_URI = URI.create("http://localhost:8080/"); + + @Rule + public WireMockRule wireMock = new WireMockRule(); + + private S3ControlClientBuilder getSyncClientBuilder() { + return S3ControlClient.builder() + .region(Region.US_EAST_1) + .overrideConfiguration(c -> c.addExecutionInterceptor(new LocalhostEndpointAddressInterceptor())) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("key", "secret"))); + } + + private S3ControlAsyncClientBuilder getAsyncClientBuilder() { + return S3ControlAsyncClient.builder() + .region(Region.US_EAST_1) + .overrideConfiguration(c -> c.addExecutionInterceptor(new LocalhostEndpointAddressInterceptor())) + .credentialsProvider( + StaticCredentialsProvider.create( + AwsBasicCredentials.create("key", "secret"))); + } + + @Test + public void standardErrorXML_translated_correctly_with_syncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "\n" + + "Account-Id\n" + + "NoSuchPublicAccessBlockConfiguration\n" + + "The public access block configuration was not found\n" + + "\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlClient s3Client = getSyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.getPublicAccessBlock(r -> r.accountId(accountId))) + .isInstanceOf(S3ControlException.class) + .isInstanceOf(NoSuchPublicAccessBlockConfigurationException.class) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorCode()) + .isEqualTo("NoSuchPublicAccessBlockConfiguration")) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorMessage()).contains("block")); + } + + @Test + public void standardErrorXML_translated_correctly_with_asyncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "\n" + + "Account-Id\n" + + "NoSuchPublicAccessBlockConfiguration\n" + + "The public access block configuration was not found\n" + + "\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlAsyncClient s3Client = getAsyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.createJob(r -> r.accountId(accountId)).join()) + .isInstanceOf(CompletionException.class) + .hasCauseExactlyInstanceOf(NoSuchPublicAccessBlockConfigurationException.class) + .satisfies(e -> { + S3ControlException s3ControlException = (S3ControlException) e.getCause(); + assertThat(s3ControlException.awsErrorDetails().errorCode()) + .isEqualTo("NoSuchPublicAccessBlockConfiguration"); + assertThat(s3ControlException.awsErrorDetails().errorMessage()).contains("block"); + }); + } + + @Test + public void xmlRootError_specificException_translated_correctly_with_syncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "InvalidRequest\n" + + "Missing role arn\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlClient s3Client = getSyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.createJob(r -> r.accountId(accountId))) + .isInstanceOf(S3ControlException.class) + .isInstanceOf(InvalidRequestException.class) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorCode()).isEqualTo("InvalidRequest")) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorMessage()).isEqualTo("Missing role arn")); + } + + @Test + public void xmlRootError_specificException_translated_correctly_with_asyncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "InvalidRequest\n" + + "Missing role arn\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlAsyncClient s3Client = getAsyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.createJob(r -> r.accountId(accountId)).join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(S3ControlException.class) + .hasCauseInstanceOf(InvalidRequestException.class) + .satisfies(e -> { + S3ControlException s3ControlException = (S3ControlException) e.getCause(); + assertThat(s3ControlException.awsErrorDetails().errorCode()).isEqualTo("InvalidRequest"); + assertThat(s3ControlException.awsErrorDetails().errorMessage()).isEqualTo("Missing role arn"); + }); + } + + @Test + public void xmlRootError_unknownException_translated_correctly_with_syncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "UnrecognizedCode\n" + + "Error message\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlClient s3Client = getSyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.createJob(r -> r.accountId(accountId))) + .isInstanceOf(S3ControlException.class) + .isNotInstanceOf(InvalidRequestException.class) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorCode()).isEqualTo("UnrecognizedCode")) + .satisfies(e -> assertThat(((S3ControlException) e).awsErrorDetails().errorMessage()).isEqualTo("Error message")); + } + + @Test + public void xmlRootError_unknownException_translated_correctly_with_asyncClient() { + String accountId = "Account-Id"; + String xmlResponseBody = "\n" + + "\n" + + "UnrecognizedCode\n" + + "Error message\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + + stubFor(any(anyUrl()).willReturn(aResponse().withStatus(400).withBody(xmlResponseBody))); + + S3ControlAsyncClient s3Client = getAsyncClientBuilder().build(); + + assertThatThrownBy(() -> s3Client.createJob(r -> r.accountId(accountId)).join()) + .isInstanceOf(CompletionException.class) + .hasCauseExactlyInstanceOf(S3ControlException.class) + .satisfies(e -> { + S3ControlException s3ControlException = (S3ControlException) e.getCause(); + assertThat(s3ControlException.awsErrorDetails().errorCode()).isEqualTo("UnrecognizedCode"); + assertThat(s3ControlException.awsErrorDetails().errorMessage()).isEqualTo("Error message"); + }); + } + + private static final class LocalhostEndpointAddressInterceptor implements ExecutionInterceptor { + + @Override + public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) { + return context.httpRequest() + .toBuilder() + .protocol(HTTP_LOCALHOST_URI.getScheme()) + .host(HTTP_LOCALHOST_URI.getHost()) + .port(HTTP_LOCALHOST_URI.getPort()) + .build(); + } + } +} diff --git a/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptorTest.java b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptorTest.java new file mode 100644 index 000000000000..d81097f7cbb0 --- /dev/null +++ b/services/s3control/src/test/java/software/amazon/awssdk/services/s3control/internal/interceptors/TopLevelXMLErrorInterceptorTest.java @@ -0,0 +1,145 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package software.amazon.awssdk.services.s3control.internal.interceptors; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; +import org.junit.Test; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SdkResponse; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.services.s3control.model.InvalidRequestException; +import software.amazon.awssdk.services.s3control.model.S3ControlException; + +public class TopLevelXMLErrorInterceptorTest { + + @Test + public void when_correctlyParsedException_returnsExceptionUnmodified() { + AwsServiceException originalException = S3ControlException.builder() + .message("This is a correctly parsed error") + .build(); + TopLevelXMLErrorInterceptor interceptor = new TopLevelXMLErrorInterceptor(); + Throwable translatedException = interceptor.modifyException(new Context(originalException), new ExecutionAttributes()); + assertThat(translatedException).isEqualTo(originalException); + } + + @Test + public void when_incorrectlyParsedException_wrongXMLStructure_returnsExceptionUnmodified() { + String xmlResponseBody = "\n" + + "\n" + + "Value\n" + + ""; + AwsErrorDetails awsErrorDetails = AwsErrorDetails.builder() + .rawResponse(SdkBytes.fromUtf8String(xmlResponseBody)) + .build(); + + AwsServiceException originalException = S3ControlException.builder() + .message("Error message with null") + .awsErrorDetails(awsErrorDetails) + .build(); + TopLevelXMLErrorInterceptor interceptor = new TopLevelXMLErrorInterceptor(); + Throwable translatedException = interceptor.modifyException(new Context(originalException), new ExecutionAttributes()); + assertThat(translatedException).isEqualTo(originalException); + } + + @Test + public void when_incorrectlyParsedException_correctXMLStructure_returnsSpecificException() { + String xmlResponseBody = "\n" + + "\n" + + "InvalidRequest\n" + + "Missing role arn\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + AwsErrorDetails awsErrorDetails = AwsErrorDetails.builder() + .rawResponse(SdkBytes.fromUtf8String(xmlResponseBody)) + .build(); + + AwsServiceException originalException = S3ControlException.builder() + .message("Error message with null") + .awsErrorDetails(awsErrorDetails) + .build(); + TopLevelXMLErrorInterceptor interceptor = new TopLevelXMLErrorInterceptor(); + Throwable translatedException = interceptor.modifyException(new Context(originalException), new ExecutionAttributes()); + assertThat(translatedException).isNotEqualTo(originalException); + assertThat(translatedException).isInstanceOf(InvalidRequestException.class); + InvalidRequestException s3ControlException = (InvalidRequestException) translatedException; + assertThat(s3ControlException.awsErrorDetails().errorCode()).isEqualTo("InvalidRequest"); + assertThat(s3ControlException.awsErrorDetails().errorMessage()).isEqualTo("Missing role arn"); + } + + @Test + public void when_incorrectlyParsedException_correctXMLStructure_returnsGenericException() { + String xmlResponseBody = "\n" + + "\n" + + "SomeOtherException\n" + + "The exception message\n" + + "656c76696e6727732072657175657374\n" + + "Uuag1LuByRx9e6j5Onimru9pO4ZVKnJ2Qz7/C1NPcfTWAtRPfTaOFg==\n" + + ""; + AwsErrorDetails awsErrorDetails = AwsErrorDetails.builder() + .rawResponse(SdkBytes.fromUtf8String(xmlResponseBody)) + .build(); + + AwsServiceException originalException = S3ControlException.builder() + .message("Error message with null") + .awsErrorDetails(awsErrorDetails) + .build(); + TopLevelXMLErrorInterceptor interceptor = new TopLevelXMLErrorInterceptor(); + Throwable translatedException = interceptor.modifyException(new Context(originalException), new ExecutionAttributes()); + assertThat(translatedException).isNotEqualTo(originalException); + assertThat(translatedException).isInstanceOf(S3ControlException.class); + S3ControlException s3ControlException = (S3ControlException) translatedException; + assertThat(s3ControlException.awsErrorDetails().errorCode()).isEqualTo("SomeOtherException"); + assertThat(s3ControlException.awsErrorDetails().errorMessage()).isEqualTo("The exception message"); + } + + public static final class Context implements software.amazon.awssdk.core.interceptor.Context.FailedExecution { + + private final Throwable exception; + + public Context(Throwable exception) { + this.exception = exception; + } + + @Override + public Throwable exception() { return exception; } + + @Override + public SdkRequest request() { + return null; + } + + @Override + public Optional response() { return Optional.empty(); } + + @Override + public Optional httpRequest() { + return Optional.empty(); + } + + @Override + public Optional httpResponse() { + return Optional.empty(); + } + + } +} diff --git a/services/s3control/src/test/resources/log4j.properties b/services/s3control/src/test/resources/log4j.properties new file mode 100644 index 000000000000..9aad44287cbe --- /dev/null +++ b/services/s3control/src/test/resources/log4j.properties @@ -0,0 +1,33 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://aws.amazon.com/apache2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# + +log4j.rootLogger=WARN, A1 +log4j.appender.A1=org.apache.log4j.ConsoleAppender +log4j.appender.A1.layout=org.apache.log4j.PatternLayout + +# Print the date in ISO 8601 format +log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c - %m%n + +# Adjust to see more / less logging +#log4j.logger.com.amazonaws.ec2=DEBUG + +# HttpClient 3 Wire Logging +#log4j.logger.httpclient.wire=DEBUG + +# HttpClient 4 Wire Logging +#log4j.logger.org.apache.http.wire=DEBUG +log4j.logger.org.apache.http=DEBUG +log4j.logger.org.apache.http.wire=DEBUG +log4j.logger.software.amazon.awssdk=WARN