Skip to content

Commit

Permalink
Added support for api errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Marty Pitt committed May 6, 2012
1 parent 5cea266 commit 76de369
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 8 deletions.
14 changes: 14 additions & 0 deletions src/main/java/com/mangofactory/swagger/ApiError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mangofactory.swagger;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ApiError {

int code();
String reason();
}
21 changes: 21 additions & 0 deletions src/main/java/com/mangofactory/swagger/ApiErrors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mangofactory.swagger;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* An extension to the default Swagger @ApiError annotation.
*
* Allows documentation to be applied at the class level of the
* declared exception.
*
* @author martypitt
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiErrors {
Class<? extends Throwable>[] value() default {};
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.mangofactory.swagger.springmvc;

import static org.springframework.test.web.server.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.management.OperationsException;

import lombok.Getter;
import lombok.val;
Expand All @@ -18,10 +20,13 @@
import org.springframework.web.method.HandlerMethod;

import com.google.common.collect.Lists;
import com.wordnik.swagger.core.ApiError;
import com.wordnik.swagger.core.ApiErrors;
import com.wordnik.swagger.core.ApiOperation;
import com.wordnik.swagger.core.ApiParam;
import com.wordnik.swagger.core.DocumentationAllowableListValues;
import com.wordnik.swagger.core.DocumentationAllowableValues;
import com.wordnik.swagger.core.DocumentationError;
import com.wordnik.swagger.core.DocumentationOperation;
import com.wordnik.swagger.core.DocumentationParameter;

Expand All @@ -40,12 +45,15 @@ public class ApiMethodReader {
private String nickname;

private boolean deprecated;
@Getter
private final List<DocumentationError> errors = Lists.newArrayList();
private final List<DocumentationParameter> parameters = Lists.newArrayList();

public ApiMethodReader(HandlerMethod handlerMethod) {
this.handlerMethod = handlerMethod;
documentOperation();
documentParameters();
documentExceptions();
}

private void documentOperation() {
Expand All @@ -58,7 +66,6 @@ private void documentOperation() {
}
nickname = handlerMethod.getMethod().getName();
deprecated = handlerMethod.getMethodAnnotation(Deprecated.class) != null;

}

public DocumentationOperation getOperation(RequestMethod requestMethod) {
Expand All @@ -68,6 +75,9 @@ public DocumentationOperation getOperation(RequestMethod requestMethod) {
for (DocumentationParameter parameter : parameters)
operation.addParameter(parameter);
setTags(operation);

for (DocumentationError error : errors)
operation.addErrorResponse(error);
return operation;
}
private void setTags(DocumentationOperation operation) {
Expand Down Expand Up @@ -140,4 +150,46 @@ protected DocumentationAllowableValues convertToAllowableValues(String csvString
val params = Arrays.asList(csvString.split(","));
return new DocumentationAllowableListValues(params);
}

private void documentExceptions() {
discoverSwaggerAnnotatedExceptions();
discoverSpringMvcExceptions();
discoverThrowsExceptions();
}

private void discoverThrowsExceptions() {
Class<?>[] exceptionTypes = handlerMethod.getMethod().getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes)
{
appendErrorFromClass((Class<? extends Throwable>) exceptionType);
}
}

private void discoverSpringMvcExceptions() {
com.mangofactory.swagger.ApiErrors apiErrors = handlerMethod.getMethodAnnotation(com.mangofactory.swagger.ApiErrors.class);
if (apiErrors == null)
return;
for (Class<? extends Throwable> exceptionClass : apiErrors.value())
{
appendErrorFromClass(exceptionClass);
}

}

void appendErrorFromClass(Class<? extends Throwable> exceptionClass) {
com.mangofactory.swagger.ApiError apiError = exceptionClass.getAnnotation(com.mangofactory.swagger.ApiError.class);
if (apiError == null)
return;
errors.add(new DocumentationError(apiError.code(),apiError.reason()));
}

private void discoverSwaggerAnnotatedExceptions() {
ApiErrors apiErrors = handlerMethod.getMethodAnnotation(ApiErrors.class);
if (apiErrors == null)
return;
for (ApiError apiError : apiErrors.value())
{
errors.add(new DocumentationError(apiError.code(), apiError.reason()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;

import com.mangofactory.swagger.ApiError;
import com.mangofactory.swagger.ApiErrors;
import com.wordnik.swagger.core.ApiParam;
import com.wordnik.swagger.core.DocumentationError;
import com.wordnik.swagger.core.DocumentationOperation;
import com.wordnik.swagger.core.DocumentationParameter;
import com.wordnik.swagger.sample.exception.BadRequestException;
import com.wordnik.swagger.sample.exception.NotFoundException;

public class ApiMethodReaderTests {

Expand Down Expand Up @@ -63,17 +68,70 @@ public void apiParamNameTakesFirstPriority()
// assertThat(parameters.get(3).getName(), equalTo("variableD"));
}

private final class SampleClass
@Test
public void detectsErrorsUsingSwaggerDeclaration()
{
methodReader = getExceptionMethod("exceptionMethodB");
List<DocumentationError> errors = methodReader.getErrors();
assertThat(errors, hasSize(2));
DocumentationError error = errors.get(0);
assertThat(error.code(), equalTo(302));
assertThat(error.reason(), equalTo("Malformed request"));
}
@Test
public void detectsErrorsUsingSpringMVCDeclaration()
{
methodReader = getExceptionMethod("exceptionMethodA");
List<DocumentationError> errors = methodReader.getErrors();
assertThat(errors, hasSize(2));
DocumentationError error = errors.get(0);
assertThat(error.code(), equalTo(404));
assertThat(error.reason(), equalToIgnoringCase("Invalid ID supplied"));
}
@Test
public void detectsErrorsUsingThrowsDeclaration()
{
methodReader = getExceptionMethod("exceptionMethodC");
List<DocumentationError> errors = methodReader.getErrors();
assertThat(errors, hasSize(1));
DocumentationError error = errors.get(0);
assertThat(error.code(), equalTo(404));
assertThat(error.reason(), equalToIgnoringCase("Invalid ID supplied"));
}




/// TEST SUPPORT
@SneakyThrows
private ApiMethodReader getExceptionMethod(String methodName) {
SampleClass instance = new SampleClass();
Method method = instance.getClass().getMethod(methodName);
handlerMethod = new HandlerMethod(instance, method);
methodReader = new ApiMethodReader(handlerMethod);
return methodReader;

}

@SuppressWarnings("unused")
private final class SampleClass
{
public void sampleMethod(
@ApiParam(name="documentationNameA") @PathVariable("mvcNameA") String variableA,
@PathVariable("mvcNameB") String variableB,
@ModelAttribute("modelAttributeC") String variableC,
String variableD
)
{
}
String variableD) {}

@ApiErrors({NotFoundException.class,BadRequestException.class})
public void exceptionMethodA() {};

@com.wordnik.swagger.core.ApiErrors({
@com.wordnik.swagger.core.ApiError(code=302,reason="Malformed request"),
@com.wordnik.swagger.core.ApiError(code=404,reason="Not found")}
)
public void exceptionMethodB() {};

public void exceptionMethodC() throws NotFoundException {};

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public class PetService {
@ApiOperation(value = "Find pet by ID", notes = "Returns a pet when ID < 10. "
+ "ID > 10 or nonintegers will simulate API error conditions", responseClass = "com.wordnik.swagger.sample.model.Pet"
)
@ApiErrors(value = { @ApiError(code = 400, reason = "Invalid ID supplied"),
@ApiError(code = 404, reason = "Pet not found") })
@com.mangofactory.swagger.ApiErrors(NotFoundException.class)
public Pet getPetById (
@ApiParam(value = "ID of pet that needs to be fetched", allowableValues = "range[1,5]", required = true) @PathVariable("petId") String petId)
throws NotFoundException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.wordnik.swagger.sample.exception;

import com.mangofactory.swagger.ApiError;

@ApiError(code=302,reason="Malformed request")
public class BadRequestException extends ApiException{
private int code;
public BadRequestException (int code, String msg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.wordnik.swagger.sample.exception;

import com.mangofactory.swagger.ApiError;

@ApiError(code=404,reason="Invalid ID Supplied")
public class NotFoundException extends ApiException {
private int code;
public NotFoundException (int code, String msg) {
Expand Down

0 comments on commit 76de369

Please sign in to comment.