/* * Copyright © 2012 The Feign Authors (feign@commonhaus.dev) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 feign.error; import static org.assertj.core.api.Assertions.assertThat; import feign.Request; import feign.codec.Decoder; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DeclaredDefaultConstructorException; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DeclaredDefaultConstructorWithOtherConstructorsException; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefaultConstructorException; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForBodyAndHeaders; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForBodyAndHeadersSecondOrder; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForHeaders; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForHeadersButNotForBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForNonSupportedBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithAnnotationForOptionalBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithNoAnnotationForBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithRequest; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithRequestAndAnnotationForResponseBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithRequestAndResponseBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.DefinedConstructorWithRequestAndResponseHeadersAndResponseBody; import feign.error.AnnotationErrorDecoderExceptionConstructorsTest.TestClientInterfaceWithDifferentExceptionConstructors.ParametersException; import feign.optionals.OptionalDecoder; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; public class AnnotationErrorDecoderExceptionConstructorsTest extends AbstractAnnotationErrorDecoderTest< TestClientInterfaceWithDifferentExceptionConstructors> { private static final String NO_BODY = "NO BODY"; private static final Object NULL_BODY = null; private static final String NON_NULL_BODY = "A GIVEN BODY"; private static final feign.Request REQUEST = feign.Request.create( feign.Request.HttpMethod.GET, "http://test", Collections.emptyMap(), Request.Body.empty(), null); private static final feign.Request NO_REQUEST = null; private static final Map> NON_NULL_HEADERS = new HashMap<>(); private static final Map> NO_HEADERS = null; @Override public Class interfaceAtTest() { return TestClientInterfaceWithDifferentExceptionConstructors.class; } public static Iterable data() { return Arrays.asList( new Object[][] { { "Test Default Constructor", 500, DefaultConstructorException.class, NO_REQUEST, NO_BODY, NO_HEADERS }, { "test Default Constructor", 501, DeclaredDefaultConstructorException.class, NO_REQUEST, NO_BODY, NO_HEADERS }, { "test Default Constructor", 502, DeclaredDefaultConstructorWithOtherConstructorsException.class, NO_REQUEST, NO_BODY, NO_HEADERS }, { "test Declared Constructor", 503, DefinedConstructorWithNoAnnotationForBody.class, NO_REQUEST, NON_NULL_BODY, NO_HEADERS }, { "test Declared Constructor", 504, DefinedConstructorWithAnnotationForBody.class, NO_REQUEST, NON_NULL_BODY, NO_HEADERS }, { "test Declared Constructor", 505, DefinedConstructorWithAnnotationForBodyAndHeaders.class, NO_REQUEST, NON_NULL_BODY, NON_NULL_HEADERS }, { "test Declared Constructor", 506, DefinedConstructorWithAnnotationForBodyAndHeadersSecondOrder.class, NO_REQUEST, NON_NULL_BODY, NON_NULL_HEADERS }, { "test Declared Constructor", 507, DefinedConstructorWithAnnotationForHeaders.class, NO_REQUEST, NO_BODY, NON_NULL_HEADERS }, { "test Declared Constructor", 508, DefinedConstructorWithAnnotationForHeadersButNotForBody.class, NO_REQUEST, NON_NULL_BODY, NON_NULL_HEADERS }, { "test Declared Constructor", 509, DefinedConstructorWithAnnotationForNonSupportedBody.class, NO_REQUEST, NULL_BODY, NO_HEADERS }, { "test Declared Constructor", 510, DefinedConstructorWithAnnotationForOptionalBody.class, NO_REQUEST, Optional.of(NON_NULL_BODY), NO_HEADERS }, { "test Declared Constructor", 511, DefinedConstructorWithRequest.class, REQUEST, NO_BODY, NO_HEADERS }, { "test Declared Constructor", 512, DefinedConstructorWithRequestAndResponseBody.class, REQUEST, NON_NULL_BODY, NO_HEADERS }, { "test Declared Constructor", 513, DefinedConstructorWithRequestAndAnnotationForResponseBody.class, REQUEST, NON_NULL_BODY, NO_HEADERS }, { "test Declared Constructor", 514, DefinedConstructorWithRequestAndResponseHeadersAndResponseBody.class, REQUEST, NON_NULL_BODY, NON_NULL_HEADERS }, { "test Declared Constructor", 515, DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody.class, REQUEST, Optional.of(NON_NULL_BODY), NON_NULL_HEADERS } }); } // first data value (0) is default public String testName; public int errorCode; public Class expectedExceptionClass; public Object expectedRequest; public Object expectedBody; public Map> expectedHeaders; @MethodSource("data") @ParameterizedTest( name = "{0}: When error code ({1}) on method ({2}) should return exception type ({3})") void test( String testName, int errorCode, Class expectedExceptionClass, Object expectedRequest, Object expectedBody, Map> expectedHeaders) throws Exception { initAnnotationErrorDecoderExceptionConstructorsTest( testName, errorCode, expectedExceptionClass, expectedRequest, expectedBody, expectedHeaders); AnnotationErrorDecoder decoder = AnnotationErrorDecoder.builderFor( TestClientInterfaceWithDifferentExceptionConstructors.class) .withResponseBodyDecoder(new OptionalDecoder(new Decoder.Default())) .build(); Exception genericException = decoder.decode( feignConfigKey("method1Test"), testResponse(errorCode, NON_NULL_BODY, NON_NULL_HEADERS)); assertThat(genericException).isInstanceOf(expectedExceptionClass); ParametersException exception = (ParametersException) genericException; assertThat(exception.body()).isEqualTo(expectedBody); assertThat(exception.headers()).isEqualTo(expectedHeaders); } @MethodSource("data") @ParameterizedTest( name = "{0}: When error code ({1}) on method ({2}) should return exception type ({3})") void ifExceptionIsNotInTheList( String testName, int errorCode, Class expectedExceptionClass, Object expectedRequest, Object expectedBody, Map> expectedHeaders) throws Exception { initAnnotationErrorDecoderExceptionConstructorsTest( testName, errorCode, expectedExceptionClass, expectedRequest, expectedBody, expectedHeaders); AnnotationErrorDecoder decoder = AnnotationErrorDecoder.builderFor( TestClientInterfaceWithDifferentExceptionConstructors.class) .withResponseBodyDecoder(new OptionalDecoder(new Decoder.Default())) .build(); Exception genericException = decoder.decode( feignConfigKey("method1Test"), testResponse(-1, NON_NULL_BODY, NON_NULL_HEADERS)); assertThat(genericException) .isInstanceOf(ErrorHandling.NO_DEFAULT.class) .hasMessage("Endpoint responded with -1, reason: null"); } interface TestClientInterfaceWithDifferentExceptionConstructors { @ErrorHandling( codeSpecific = { @ErrorCodes( codes = {500}, generate = DefaultConstructorException.class), @ErrorCodes( codes = {501}, generate = DeclaredDefaultConstructorException.class), @ErrorCodes( codes = {502}, generate = DeclaredDefaultConstructorWithOtherConstructorsException.class), @ErrorCodes( codes = {503}, generate = DefinedConstructorWithNoAnnotationForBody.class), @ErrorCodes( codes = {504}, generate = DefinedConstructorWithAnnotationForBody.class), @ErrorCodes( codes = {505}, generate = DefinedConstructorWithAnnotationForBodyAndHeaders.class), @ErrorCodes( codes = {506}, generate = DefinedConstructorWithAnnotationForBodyAndHeadersSecondOrder.class), @ErrorCodes( codes = {507}, generate = DefinedConstructorWithAnnotationForHeaders.class), @ErrorCodes( codes = {508}, generate = DefinedConstructorWithAnnotationForHeadersButNotForBody.class), @ErrorCodes( codes = {509}, generate = DefinedConstructorWithAnnotationForNonSupportedBody.class), @ErrorCodes( codes = {510}, generate = DefinedConstructorWithAnnotationForOptionalBody.class), @ErrorCodes( codes = {511}, generate = DefinedConstructorWithRequest.class), @ErrorCodes( codes = {512}, generate = DefinedConstructorWithRequestAndResponseBody.class), @ErrorCodes( codes = {513}, generate = DefinedConstructorWithRequestAndAnnotationForResponseBody.class), @ErrorCodes( codes = {514}, generate = DefinedConstructorWithRequestAndResponseHeadersAndResponseBody.class), @ErrorCodes( codes = {515}, generate = DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody.class) }) void method1Test(); class ParametersException extends Exception { public Object body() { return NO_BODY; } public feign.Request request() { return null; } public Map> headers() { return null; } } class DefaultConstructorException extends ParametersException {} class DeclaredDefaultConstructorException extends ParametersException { public DeclaredDefaultConstructorException() {} } class DeclaredDefaultConstructorWithOtherConstructorsException extends ParametersException { public DeclaredDefaultConstructorWithOtherConstructorsException() {} public DeclaredDefaultConstructorWithOtherConstructorsException(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } public DeclaredDefaultConstructorWithOtherConstructorsException(Throwable cause) { throw new UnsupportedOperationException("Should not be called"); } public DeclaredDefaultConstructorWithOtherConstructorsException( String message, Throwable cause) { throw new UnsupportedOperationException("Should not be called"); } } class DefinedConstructorWithNoAnnotationForBody extends ParametersException { String body; public DefinedConstructorWithNoAnnotationForBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithNoAnnotationForBody(String body) { this.body = body; } public DefinedConstructorWithNoAnnotationForBody(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public Object body() { return body; } } class DefinedConstructorWithRequest extends ParametersException { feign.Request request; public DefinedConstructorWithRequest() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithRequest(feign.Request request) { this.request = request; } public DefinedConstructorWithRequest(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public feign.Request request() { return request; } } class DefinedConstructorWithRequestAndResponseBody extends ParametersException { feign.Request request; String body; public DefinedConstructorWithRequestAndResponseBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithRequestAndResponseBody(feign.Request request, String body) { this.request = request; this.body = body; } public DefinedConstructorWithRequestAndResponseBody(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public feign.Request request() { return request; } @Override public String body() { return body; } } class DefinedConstructorWithRequestAndAnnotationForResponseBody extends ParametersException { feign.Request request; String body; public DefinedConstructorWithRequestAndAnnotationForResponseBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithRequestAndAnnotationForResponseBody( feign.Request request, @ResponseBody String body) { this.request = request; this.body = body; } public DefinedConstructorWithRequestAndAnnotationForResponseBody(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public feign.Request request() { return request; } @Override public String body() { return body; } } class DefinedConstructorWithRequestAndResponseHeadersAndResponseBody extends ParametersException { feign.Request request; String body; Map headers; public DefinedConstructorWithRequestAndResponseHeadersAndResponseBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithRequestAndResponseHeadersAndResponseBody( feign.Request request, @ResponseHeaders Map headers, @ResponseBody String body) { this.request = request; this.body = body; this.headers = headers; } public DefinedConstructorWithRequestAndResponseHeadersAndResponseBody(TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public feign.Request request() { return request; } @Override public Map headers() { return headers; } @Override public String body() { return body; } } class DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody extends ParametersException { feign.Request request; Optional body; Map headers; public DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody( feign.Request request, @ResponseHeaders Map headers, @ResponseBody Optional body) { this.request = request; this.body = body; this.headers = headers; } public DefinedConstructorWithRequestAndResponseHeadersAndOptionalResponseBody( TestPojo testPojo) { throw new UnsupportedOperationException("Should not be called"); } @Override public feign.Request request() { return request; } @Override public Map headers() { return headers; } @Override public Object body() { return body; } } class DefinedConstructorWithAnnotationForBody extends ParametersException { String body; public DefinedConstructorWithAnnotationForBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithAnnotationForBody(@ResponseBody String body) { this.body = body; } @Override public Object body() { return body; } } class DefinedConstructorWithAnnotationForOptionalBody extends ParametersException { Optional body; public DefinedConstructorWithAnnotationForOptionalBody() { throw new UnsupportedOperationException("Should not be called"); } @FeignExceptionConstructor public DefinedConstructorWithAnnotationForOptionalBody(@ResponseBody Optional body) { this.body = body; } @Override public Object body() { return body; } } class DefinedConstructorWithAnnotationForNonSupportedBody extends ParametersException { TestPojo body; @FeignExceptionConstructor public DefinedConstructorWithAnnotationForNonSupportedBody(@ResponseBody TestPojo body) { this.body = body; } @Override public Object body() { return body; } } class DefinedConstructorWithAnnotationForBodyAndHeaders extends ParametersException { String body; Map> headers; @FeignExceptionConstructor public DefinedConstructorWithAnnotationForBodyAndHeaders( @ResponseBody String body, @ResponseHeaders Map> headers) { this.body = body; this.headers = headers; } @Override public Object body() { return body; } @Override public Map> headers() { return headers; } } class DefinedConstructorWithAnnotationForBodyAndHeadersSecondOrder extends ParametersException { String body; Map> headers; @FeignExceptionConstructor public DefinedConstructorWithAnnotationForBodyAndHeadersSecondOrder( @ResponseHeaders Map> headers, @ResponseBody String body) { this.body = body; this.headers = headers; } @Override public Object body() { return body; } @Override public Map> headers() { return headers; } } class DefinedConstructorWithAnnotationForHeaders extends ParametersException { Map> headers; @FeignExceptionConstructor public DefinedConstructorWithAnnotationForHeaders( @ResponseHeaders Map> headers) { this.headers = headers; } @Override public Map> headers() { return headers; } } class DefinedConstructorWithAnnotationForHeadersButNotForBody extends ParametersException { String body; Map> headers; @FeignExceptionConstructor public DefinedConstructorWithAnnotationForHeadersButNotForBody( @ResponseHeaders Map> headers, String body) { this.body = body; this.headers = headers; } @Override public Object body() { return body; } @Override public Map> headers() { return headers; } } } public void initAnnotationErrorDecoderExceptionConstructorsTest( String testName, int errorCode, Class expectedExceptionClass, Object expectedRequest, Object expectedBody, Map> expectedHeaders) { this.testName = testName; this.errorCode = errorCode; this.expectedExceptionClass = expectedExceptionClass; this.expectedRequest = expectedRequest; this.expectedBody = expectedBody; this.expectedHeaders = expectedHeaders; } }