Skip to content
New issue

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

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

Already on GitHub? # to your account

Method level reader extensions #1513

Merged
merged 1 commit into from
Aug 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/Reader.java
Original file line number Diff line number Diff line change
Expand Up @@ -856,9 +856,21 @@ private Operation parseMethod(Class<?> cls, Method method, List<Parameter> globa
Response response = new Response().description(SUCCESSFUL_OPERATION);
operation.defaultResponse(response);
}

processOperationDecorator(operation, method);

return operation;
}

private void processOperationDecorator(Operation operation, Method method) {
final Iterator<SwaggerExtension> chain = SwaggerExtensions.chain();
if (chain.hasNext()) {
SwaggerExtension extension = chain.next();
LOGGER.debug("trying to decorate operation: " + extension);
extension.decorateOperation(operation, method, chain);
}
}

private void addResponse(Operation operation, ApiResponse apiResponse) {
Map<String, Property> responseHeaders = parseResponseHeaders(apiResponse.responseHeaders());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;

import io.swagger.annotations.ApiOperation;
import io.swagger.models.Operation;
import io.swagger.models.parameters.Parameter;

import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -33,6 +35,13 @@ public List<Parameter> extractParameters(List<Annotation> annotations, Type type
return Collections.emptyList();
}
}

@Override
public void decorateOperation(Operation operation, Method method, Iterator<SwaggerExtension> chain) {
if (chain.hasNext()) {
chain.next().decorateOperation(operation, method, chain);
}
}

protected boolean shouldIgnoreClass(Class<?> cls) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.swagger.jaxrs.ext;

import io.swagger.annotations.ApiOperation;
import io.swagger.models.Operation;
import io.swagger.models.parameters.Parameter;

import java.lang.annotation.Annotation;
Expand All @@ -11,7 +12,17 @@
import java.util.Set;

public interface SwaggerExtension {

String extractOperationMethod(ApiOperation apiOperation, Method method, Iterator<SwaggerExtension> chain);

List<Parameter> extractParameters(List<Annotation> annotations, Type type, Set<Type> typesToSkip, Iterator<SwaggerExtension> chain);

/**
* Decorates operation with additional vendor based extensions.
*
* @param operation the operation, build from swagger definition
* @param method the method for additional scan
* @param chain the chain with swagger extensions to process
*/
void decorateOperation(Operation operation, Method method, Iterator<SwaggerExtension> chain);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.swagger;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import io.swagger.jaxrs.Reader;
import io.swagger.jaxrs.ext.AbstractSwaggerExtension;
import io.swagger.jaxrs.ext.SwaggerExtension;
import io.swagger.jaxrs.ext.SwaggerExtensions;
import io.swagger.models.Operation;
import io.swagger.models.Response;
import io.swagger.models.Swagger;
import io.swagger.resources.SimpleResourceWithVendorAnnotation;
import io.swagger.resources.SimpleResourceWithVendorAnnotation.VendorFunnyAnnotation;

import java.lang.reflect.Method;
import java.util.Iterator;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
* Scanner example for custom operation decorator extension.
*/
public class SimpleScannerWithDecoratorExtensionTest {

private static final String RESPONSE_DESCRIPTION = "Some vendor error description";

private static final String RESPONSE_STATUS_401 = "401";

private static final SwaggerExtension customExtension = new AbstractSwaggerExtension() {

@Override
public void decorateOperation(final Operation operation, final Method method, final Iterator<SwaggerExtension> chain) {
method.getDeclaredAnnotations();
final VendorFunnyAnnotation myFunyError = method.getAnnotation(SimpleResourceWithVendorAnnotation.VendorFunnyAnnotation.class);
if (myFunyError != null) {
/*
* Extend swagger model by new error response description, with additional data received from vendor
* based annotation. This example overwrite existing response from swagger annotation, but it is only
* for demo.
*/
final Response value = new Response();
value.setDescription(RESPONSE_DESCRIPTION);
operation.getResponses().put(RESPONSE_STATUS_401, value);
}
}
};

private Swagger getSwagger(final Class<?> cls) {
return new Reader(new Swagger()).read(cls);
}

private Operation getGet(final Swagger swagger, final String path) {
return swagger.getPaths().get(path).getGet();
}

@BeforeMethod()
public void addCustomExtension() {
SwaggerExtensions.getExtensions().add(customExtension);
}

@AfterMethod()
public void removeCustomExtension() {
SwaggerExtensions.getExtensions().remove(customExtension);
}

/**
* Test for method annotated with vendor annotation which could be used for swagger documentation.
*/
@Test(description = "scan a simple resource with custom decorator")
public void scanSimpleResourceWithDecorator() {
final Swagger swagger = getSwagger(SimpleResourceWithVendorAnnotation.class);

assertEquals(swagger.getPaths().size(), 2);

final Operation get = getGet(swagger, "/{id}");
assertNotNull(get);
assertEquals(get.getParameters().size(), 2);

final Response response = get.getResponses().get(RESPONSE_STATUS_401);
assertNotNull(response);
assertEquals(response.getDescription(), RESPONSE_DESCRIPTION);
}

/**
* Test for method annotated without vendor annotation.
*/
@Test(description = "scan a simple resource without custom decorator")
public void scanSimpleResourceWithoutDecorator() {
final Swagger swagger = getSwagger(SimpleResourceWithVendorAnnotation.class);

assertEquals(swagger.getPaths().size(), 2);

final Operation get = getGet(swagger, "/{id}/value");
assertNotNull(get);
assertEquals(get.getParameters().size(), 0);

final Response response = get.getResponses().get(RESPONSE_STATUS_401);
assertNull(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.swagger.resources;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.models.NotFoundModel;
import io.swagger.models.Sample;

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

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

@Path("/")
@Api(value = "/basic", description = "Basic resource")
@Produces({ "application/xml" })
public class SimpleResourceWithVendorAnnotation {

@VendorFunnyAnnotation
@GET
@Path("/{id}")
@ApiOperation(value = "Get object by ID", notes = "No details provided", response = Sample.class, position = 0)
@ApiResponses({ @ApiResponse(code = 400, message = "Invalid ID", response = NotFoundModel.class), @ApiResponse(code = 404, message = "object not found") })
public Response getTest(
@ApiParam(value = "sample param data", required = true, allowableValues = "range[0,10]") @DefaultValue("5") @PathParam("id") final String id,
@QueryParam("limit") final Integer limit) throws WebApplicationException {
final Sample out = new Sample();
out.setName("foo");
out.setValue("bar");
return Response.ok().entity(out).build();
}

@GET
@Path("/{id}/value")
@Produces({ "text/plain" })
@ApiOperation(value = "Get simple string value", notes = "No details provided", response = String.class, position = 0)
@ApiResponses({ @ApiResponse(code = 400, message = "Invalid ID", response = NotFoundModel.class), @ApiResponse(code = 404, message = "object not found") })
public Response getStringValue() throws WebApplicationException {
return Response.ok().entity("ok").build();
}

/**
* Annotation processed by some vendor libraries. It could be used by swagger because the result of that processing
* could return with rest error response.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface VendorFunnyAnnotation {

}
}