Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meta annotations support #30

Closed
wants to merge 10 commits into from
26 changes: 21 additions & 5 deletions src/main/java/feign/error/AnnotationErrorDecoder.java
Expand Up @@ -3,7 +3,8 @@
import feign.Response;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -67,16 +68,18 @@ Map<String, MethodErrorHandler> generateErrorHandlerMapFromApi(Class<?> apiType)
.build();
Map<Integer, ExceptionGenerator> classLevelStatusCodeDefinitions = new HashMap<Integer, ExceptionGenerator>();

if(apiType.isAnnotationPresent(ErrorHandling.class)) {
ErrorHandlingDefinition classErrorHandlingDefinition = readAnnotation(apiType.getAnnotation(ErrorHandling.class), responseBodyDecoder);
final ErrorHandling classLevelAnnotation = getAnnotation(apiType);
if(classLevelAnnotation != null) {
ErrorHandlingDefinition classErrorHandlingDefinition = readAnnotation(classLevelAnnotation, responseBodyDecoder);
classLevelDefault = classErrorHandlingDefinition.defaultThrow;
classLevelStatusCodeDefinitions = classErrorHandlingDefinition.statusCodesMap;
}

Map<String, MethodErrorHandler> methodErrorHandlerMap = new HashMap<String, MethodErrorHandler>();
for(Method method : apiType.getMethods()) {
if(method.isAnnotationPresent(ErrorHandling.class)) {
ErrorHandlingDefinition methodErrorHandling = readAnnotation(method.getAnnotation(ErrorHandling.class), responseBodyDecoder);
final ErrorHandling methodLevelAnnotation = getAnnotation(method);
if(methodLevelAnnotation != null) {
ErrorHandlingDefinition methodErrorHandling = readAnnotation(methodLevelAnnotation, responseBodyDecoder);
ExceptionGenerator methodDefault = methodErrorHandling.defaultThrow;
if(methodDefault.getExceptionType().equals(ErrorHandling.NO_DEFAULT.class)) {
methodDefault = classLevelDefault;
Expand Down Expand Up @@ -118,6 +121,19 @@ static ErrorHandlingDefinition readAnnotation(ErrorHandling errorHandling, Decod
return new ErrorHandlingDefinition(defaultException, statusCodesDefinition);
}

private ErrorHandling getAnnotation(AnnotatedElement element) {
ErrorHandling annotation = element.getAnnotation(ErrorHandling.class);
if (annotation == null) {
for (Annotation metaAnnotation : element.getAnnotations()) {
annotation = metaAnnotation.annotationType().getAnnotation(ErrorHandling.class);
if (annotation != null) {
break;
}
}
}
return annotation;
}

private static class ErrorHandlingDefinition {
private final ExceptionGenerator defaultThrow;
private final Map<Integer, ExceptionGenerator> statusCodesMap;
Expand Down
@@ -0,0 +1,92 @@
package feign.error;

import static org.assertj.core.api.Assertions.assertThat;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class AnnotationErrorDecoderAnnotationInheritanceTest
extends
AbstractAnnotationErrorDecoderTest<AnnotationErrorDecoderAnnotationInheritanceTest.TestClientInterfaceWithWithMetaAnnotation> {

@Override
public Class<TestClientInterfaceWithWithMetaAnnotation> interfaceAtTest() {
return TestClientInterfaceWithWithMetaAnnotation.class;
}

@Parameters(
name = "{0}: When error code ({1}) on method ({2}) should return exception type ({3})")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
{"Test Code Specific At Method", 404, "method1Test", MethodLevelNotFoundException.class},
{"Test Code Specific At Method", 403, "method1Test", MethodLevelDefaultException.class},
{"Test Code Specific At Method", 404, "method2Test", ClassLevelNotFoundException.class},
{"Test Code Specific At Method", 403, "method2Test", ClassLevelDefaultException.class},
});
}

@Parameter // first data value (0) is default
public String testType;

@Parameter(1)
public int errorCode;

@Parameter(2)
public String method;

@Parameter(3)
public Class<? extends Exception> expectedExceptionClass;

@Test
public void test() throws Exception {
AnnotationErrorDecoder decoder =
AnnotationErrorDecoder.builderFor(TestClientInterfaceWithWithMetaAnnotation.class).build();

assertThat(decoder.decode(feignConfigKey(method), testResponse(errorCode)).getClass())
.isEqualTo(expectedExceptionClass);
}

@ClassError
interface TestClientInterfaceWithWithMetaAnnotation {
@MethodError
void method1Test();

@ErrorHandling
void method2Test();
}

@ErrorHandling(codeSpecific = {
@ErrorCodes(codes = {404}, generate = ClassLevelNotFoundException.class),
},
defaultException = ClassLevelDefaultException.class)
@Retention(RetentionPolicy.RUNTIME)
@interface ClassError {
}

@ErrorHandling(codeSpecific = {
@ErrorCodes(codes = {404}, generate = MethodLevelNotFoundException.class),
},
defaultException = MethodLevelDefaultException.class)
@Retention(RetentionPolicy.RUNTIME)
@interface MethodError {
}

static class ClassLevelNotFoundException extends Exception {
public ClassLevelNotFoundException() {}
}
static class ClassLevelDefaultException extends Exception {
public ClassLevelDefaultException() {}
}
static class MethodLevelNotFoundException extends Exception {
public MethodLevelNotFoundException() {}
}
static class MethodLevelDefaultException extends Exception {
public MethodLevelDefaultException() {}
}
}