Skip to content

Commit 7702596

Browse files
committed
swagger-api#1513 Method level reader extensions
1 parent aa2acce commit 7702596

File tree

5 files changed

+198
-0
lines changed

5 files changed

+198
-0
lines changed

modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/Reader.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,21 @@ private Operation parseMethod(Class<?> cls, Method method, List<Parameter> globa
872872
Response response = new Response().description(SUCCESSFUL_OPERATION);
873873
operation.defaultResponse(response);
874874
}
875+
876+
processOperationDecorator(operation, method);
877+
875878
return operation;
876879
}
877880

881+
private void processOperationDecorator(Operation operation, Method method) {
882+
final Iterator<SwaggerExtension> chain = SwaggerExtensions.chain();
883+
if (chain.hasNext()) {
884+
SwaggerExtension extension = chain.next();
885+
LOGGER.debug("trying to decorate operation: " + extension);
886+
extension.decorateOperation(operation, method, chain);
887+
}
888+
}
889+
878890
private void addResponse (Operation operation, ApiResponse apiResponse) {
879891
Map<String, Property> responseHeaders = parseResponseHeaders(apiResponse.responseHeaders());
880892

modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/ext/AbstractSwaggerExtension.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import com.fasterxml.jackson.databind.JavaType;
44
import com.fasterxml.jackson.databind.type.TypeFactory;
5+
56
import io.swagger.annotations.ApiOperation;
7+
import io.swagger.models.Operation;
68
import io.swagger.models.parameters.Parameter;
79

810
import java.lang.annotation.Annotation;
@@ -33,6 +35,13 @@ public List<Parameter> extractParameters(List<Annotation> annotations, Type type
3335
return Collections.emptyList();
3436
}
3537
}
38+
39+
@Override
40+
public void decorateOperation(Operation operation, Method method, Iterator<SwaggerExtension> chain) {
41+
if (chain.hasNext()) {
42+
chain.next().decorateOperation(operation, method, chain);
43+
}
44+
}
3645

3746
protected boolean shouldIgnoreClass(Class<?> cls) {
3847
return false;

modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/ext/SwaggerExtension.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.swagger.jaxrs.ext;
22

33
import io.swagger.annotations.ApiOperation;
4+
import io.swagger.models.Operation;
45
import io.swagger.models.parameters.Parameter;
56

67
import java.lang.annotation.Annotation;
@@ -11,7 +12,17 @@
1112
import java.util.Set;
1213

1314
public interface SwaggerExtension {
15+
1416
String extractOperationMethod(ApiOperation apiOperation, Method method, Iterator<SwaggerExtension> chain);
1517

1618
List<Parameter> extractParameters(List<Annotation> annotations, Type type, Set<Type> typesToSkip, Iterator<SwaggerExtension> chain);
19+
20+
/**
21+
* Decorates operation with additional vendor based extensions.
22+
*
23+
* @param operation the operation, build from swagger definition
24+
* @param method the method for additional scan
25+
* @param chain the chain with swagger extensions to process
26+
*/
27+
void decorateOperation(Operation operation, Method method, Iterator<SwaggerExtension> chain);
1728
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.swagger;
2+
3+
import static org.testng.Assert.assertEquals;
4+
import static org.testng.Assert.assertNotNull;
5+
import static org.testng.Assert.assertNull;
6+
import io.swagger.jaxrs.Reader;
7+
import io.swagger.jaxrs.ext.AbstractSwaggerExtension;
8+
import io.swagger.jaxrs.ext.SwaggerExtension;
9+
import io.swagger.jaxrs.ext.SwaggerExtensions;
10+
import io.swagger.models.Operation;
11+
import io.swagger.models.Response;
12+
import io.swagger.models.Swagger;
13+
import io.swagger.resources.SimpleResourceWithVendorAnnotation;
14+
import io.swagger.resources.SimpleResourceWithVendorAnnotation.VendorFunnyAnnotation;
15+
16+
import java.lang.reflect.Method;
17+
import java.util.Iterator;
18+
19+
import org.testng.annotations.AfterMethod;
20+
import org.testng.annotations.BeforeMethod;
21+
import org.testng.annotations.Test;
22+
23+
/**
24+
* Scanner example for custom operation decorator extension.
25+
*/
26+
public class SimpleScannerWithDecoratorExtensionTest {
27+
28+
private static final String RESPONSE_DESCRIPTION = "Some vendor error description";
29+
30+
private static final String RESPONSE_STATUS_401 = "401";
31+
32+
private static final SwaggerExtension customExtension = new AbstractSwaggerExtension() {
33+
34+
@Override
35+
public void decorateOperation(final Operation operation, final Method method, final Iterator<SwaggerExtension> chain) {
36+
method.getDeclaredAnnotations();
37+
final VendorFunnyAnnotation myFunyError = method.getAnnotation(SimpleResourceWithVendorAnnotation.VendorFunnyAnnotation.class);
38+
if (myFunyError != null) {
39+
/*
40+
* Extend swagger model by new error response description, with additional data received from vendor
41+
* based annotation. This example overwrite existing response from swagger annotation, but it is only
42+
* for demo.
43+
*/
44+
final Response value = new Response();
45+
value.setDescription(RESPONSE_DESCRIPTION);
46+
operation.getResponses().put(RESPONSE_STATUS_401, value);
47+
}
48+
}
49+
};
50+
51+
private Swagger getSwagger(final Class<?> cls) {
52+
return new Reader(new Swagger()).read(cls);
53+
}
54+
55+
private Operation getGet(final Swagger swagger, final String path) {
56+
return swagger.getPaths().get(path).getGet();
57+
}
58+
59+
@BeforeMethod()
60+
public void addCustomExtension() {
61+
SwaggerExtensions.getExtensions().add(customExtension);
62+
}
63+
64+
@AfterMethod()
65+
public void removeCustomExtension() {
66+
SwaggerExtensions.getExtensions().remove(customExtension);
67+
}
68+
69+
/**
70+
* Test for method annotated with vendor annotation which could be used for swagger documentation.
71+
*/
72+
@Test(description = "scan a simple resource with custom decorator")
73+
public void scanSimpleResourceWithDecorator() {
74+
final Swagger swagger = getSwagger(SimpleResourceWithVendorAnnotation.class);
75+
76+
assertEquals(swagger.getPaths().size(), 2);
77+
78+
final Operation get = getGet(swagger, "/{id}");
79+
assertNotNull(get);
80+
assertEquals(get.getParameters().size(), 2);
81+
82+
final Response response = get.getResponses().get(RESPONSE_STATUS_401);
83+
assertNotNull(response);
84+
assertEquals(response.getDescription(), RESPONSE_DESCRIPTION);
85+
}
86+
87+
/**
88+
* Test for method annotated without vendor annotation.
89+
*/
90+
@Test(description = "scan a simple resource without custom decorator")
91+
public void scanSimpleResourceWithoutDecorator() {
92+
final Swagger swagger = getSwagger(SimpleResourceWithVendorAnnotation.class);
93+
94+
assertEquals(swagger.getPaths().size(), 2);
95+
96+
final Operation get = getGet(swagger, "/{id}/value");
97+
assertNotNull(get);
98+
assertEquals(get.getParameters().size(), 0);
99+
100+
final Response response = get.getResponses().get(RESPONSE_STATUS_401);
101+
assertNull(response);
102+
}
103+
104+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.swagger.resources;
2+
3+
import io.swagger.annotations.Api;
4+
import io.swagger.annotations.ApiOperation;
5+
import io.swagger.annotations.ApiParam;
6+
import io.swagger.annotations.ApiResponse;
7+
import io.swagger.annotations.ApiResponses;
8+
import io.swagger.models.NotFoundModel;
9+
import io.swagger.models.Sample;
10+
11+
import java.lang.annotation.ElementType;
12+
import java.lang.annotation.Retention;
13+
import java.lang.annotation.RetentionPolicy;
14+
import java.lang.annotation.Target;
15+
16+
import javax.ws.rs.DefaultValue;
17+
import javax.ws.rs.GET;
18+
import javax.ws.rs.Path;
19+
import javax.ws.rs.PathParam;
20+
import javax.ws.rs.Produces;
21+
import javax.ws.rs.QueryParam;
22+
import javax.ws.rs.WebApplicationException;
23+
import javax.ws.rs.core.Response;
24+
25+
@Path("/")
26+
@Api(value = "/basic", description = "Basic resource")
27+
@Produces({ "application/xml" })
28+
public class SimpleResourceWithVendorAnnotation {
29+
30+
@VendorFunnyAnnotation
31+
@GET
32+
@Path("/{id}")
33+
@ApiOperation(value = "Get object by ID", notes = "No details provided", response = Sample.class, position = 0)
34+
@ApiResponses({ @ApiResponse(code = 400, message = "Invalid ID", response = NotFoundModel.class), @ApiResponse(code = 404, message = "object not found") })
35+
public Response getTest(
36+
@ApiParam(value = "sample param data", required = true, allowableValues = "range[0,10]") @DefaultValue("5") @PathParam("id") final String id,
37+
@QueryParam("limit") final Integer limit) throws WebApplicationException {
38+
final Sample out = new Sample();
39+
out.setName("foo");
40+
out.setValue("bar");
41+
return Response.ok().entity(out).build();
42+
}
43+
44+
@GET
45+
@Path("/{id}/value")
46+
@Produces({ "text/plain" })
47+
@ApiOperation(value = "Get simple string value", notes = "No details provided", response = String.class, position = 0)
48+
@ApiResponses({ @ApiResponse(code = 400, message = "Invalid ID", response = NotFoundModel.class), @ApiResponse(code = 404, message = "object not found") })
49+
public Response getStringValue() throws WebApplicationException {
50+
return Response.ok().entity("ok").build();
51+
}
52+
53+
/**
54+
* Annotation processed by some vendor libraries. It could be used by swagger because the result of that processing
55+
* could return with rest error response.
56+
*/
57+
@Target(ElementType.METHOD)
58+
@Retention(RetentionPolicy.RUNTIME)
59+
public static @interface VendorFunnyAnnotation {
60+
61+
}
62+
}

0 commit comments

Comments
 (0)