diff --git a/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java b/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java index 1a0e61b..1871126 100644 --- a/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java +++ b/src/main/java/io/sinistral/proteus/server/swagger/AnnotationHelper.java @@ -7,43 +7,83 @@ import java.lang.reflect.Parameter; import javax.ws.rs.FormParam; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Example; /** * @author jbauer - * */ public class AnnotationHelper { - public static FormParam createFormParam(Parameter parameter){ - - return new FormParam(){ - - /* (non-Javadoc) - * @see javax.ws.rs.FormParam#value() - */ - @Override - public String value() - { - // TODO Auto-generated method stub - return parameter.getName(); - } - - @Override - public Class annotationType() - { - return FormParam.class; - } - - - }; + public static FormParam createFormParam(Parameter parameter) + { + + return new FormParam() + { + + @Override + public String value() + { + return parameter.getName(); + } + + @Override + public Class annotationType() + { + return FormParam.class; + } + + }; } - - public static ApiParam createApiParam(Parameter parameter){ - - return new ApiParam(){ + + public static QueryParam createQueryParam(Parameter parameter) + { + + return new QueryParam() + { + + @Override + public String value() + { + return parameter.getName(); + } + + @Override + public Class annotationType() + { + return QueryParam.class; + } + }; + } + + public static PathParam createPathParam(Parameter parameter) + { + + return new PathParam() + { + + @Override + public String value() + { + return parameter.getName(); + } + + @Override + public Class annotationType() + { + return PathParam.class; + } + }; + } + + public static ApiParam createApiParam(Parameter parameter) + { + + return new ApiParam() + { @Override public Class annotationType() @@ -54,7 +94,7 @@ public Class annotationType() @Override public String name() - { + { return parameter.getName(); } @@ -81,7 +121,7 @@ public String allowableValues() @Override public boolean required() - { + { return !parameter.getParameterizedType().getTypeName().contains("java.util.Optional"); } @@ -154,7 +194,7 @@ public String collectionFormat() // TODO Auto-generated method stub return null; } - + }; } } diff --git a/src/main/java/io/sinistral/proteus/server/swagger/Reader.java b/src/main/java/io/sinistral/proteus/server/swagger/Reader.java index f5ff4e9..6552dfb 100644 --- a/src/main/java/io/sinistral/proteus/server/swagger/Reader.java +++ b/src/main/java/io/sinistral/proteus/server/swagger/Reader.java @@ -22,6 +22,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.ws.rs.Consumes; import javax.ws.rs.HttpMethod; @@ -101,6 +103,8 @@ public class Reader { private static final Logger LOGGER = LoggerFactory.getLogger(Reader.class); private static final String SUCCESSFUL_OPERATION = "successful operation"; private static final String PATH_DELIMITER = "/"; + private static final Pattern PATH_PATTERN = Pattern.compile("\\{([^\\}]*?)\\}"); + private final ReaderConfig config; private Swagger swagger; @@ -308,17 +312,37 @@ private Swagger read(Class cls, String parentPath, String parentMethod, boole String operationPath = getPath(apiPath, methodPath, parentPath); Map regexMap = new LinkedHashMap<>(); operationPath = PathUtils.parsePath(operationPath, regexMap); + + + if (operationPath != null) { if (isIgnored(operationPath)) { continue; } + + List pathParamNames = new ArrayList<>(); + + Matcher m = PATH_PATTERN.matcher(operationPath); + while(m.find()) + { + String pathParamName = m.group(1); + int bracketIndex = pathParamName.indexOf('['); + + if(bracketIndex > -1) + { + pathParamName = pathParamName.substring(0, bracketIndex); + } + + pathParamNames.add(pathParamName); + } + final ApiOperation apiOperation = ReflectionUtils.getAnnotation(method, ApiOperation.class); String httpMethod = extractOperationMethod(apiOperation, method, SwaggerExtensions.chain()); Operation operation = null; if (apiOperation != null || config.isScanAllResources() || httpMethod != null || methodPath != null) { - operation = parseMethod(cls, method, annotatedMethod, globalParameters, classApiResponses); + operation = parseMethod(cls, method, annotatedMethod, globalParameters, classApiResponses,pathParamNames); } if (operation == null) { continue; @@ -799,12 +823,12 @@ public Operation parseMethod(Method method) { JavaType classType = TypeFactory.defaultInstance().constructType(method.getDeclaringClass()); BeanDescription bd = new ObjectMapper().getSerializationConfig().introspect(classType); return parseMethod(classType.getClass(), method, bd.findMethod(method.getName(), method.getParameterTypes()), - Collections. emptyList(), Collections. emptyList()); + Collections. emptyList(), Collections. emptyList(),Collections.emptyList()); } @SuppressWarnings("deprecation") private Operation parseMethod(Class cls, Method method, AnnotatedMethod annotatedMethod, - List globalParameters, List classApiResponses) { + List globalParameters, List classApiResponses, List pathParamNames) { Operation operation = new Operation(); if (annotatedMethod != null) { method = annotatedMethod.getAnnotated(); @@ -831,15 +855,12 @@ private Operation parseMethod(Class cls, Method method, AnnotatedMethod annot } else { operationId = this.getOperationId(method.getName()); } - - // LOGGER.debug("initial operationId: " + operationId + " nickname: " + apiOperation.nickname()); - + String responseContainer = null; Type responseType = null; Map defaultResponseHeaders = new LinkedHashMap(); - - + if (apiOperation != null) { if (apiOperation.hidden()) { @@ -847,10 +868,8 @@ private Operation parseMethod(Class cls, Method method, AnnotatedMethod annot } if (operationId == null) { operationId = apiOperation.nickname(); - } - - // LOGGER.debug("operationId after nickname: " + operationId); - + } + defaultResponseHeaders = parseResponseHeaders(apiOperation.responseHeaders()); operation.summary(apiOperation.value()).description(apiOperation.notes()); @@ -1030,7 +1049,7 @@ else if( responseCls.isAssignableFrom(CompletableFuture.class) ) } - List parameters = getParameters(type, Arrays.asList(paramAnnotations[i]), methodParameters[i]); + List parameters = getParameters(type, Arrays.asList(paramAnnotations[i]), methodParameters[i], pathParamNames); for (Parameter parameter : parameters) { operation.parameter(parameter); @@ -1050,7 +1069,7 @@ else if( responseCls.isAssignableFrom(CompletableFuture.class) ) - List parameters = getParameters(type, Arrays.asList(paramAnnotations[i]),methodParameters[i]); + List parameters = getParameters(type, Arrays.asList(paramAnnotations[i]),methodParameters[i],pathParamNames); for (Parameter parameter : parameters) { @@ -1102,7 +1121,7 @@ private void addResponse(Operation operation, ApiResponse apiResponse) { } } - private List getParameters(Type type, List annotations, java.lang.reflect.Parameter methodParameter) { + private List getParameters(Type type, List annotations, java.lang.reflect.Parameter methodParameter, List pathParamNames) { final Iterator chain = SwaggerExtensions.chain(); @@ -1126,11 +1145,11 @@ private List getParameters(Type type, List annotations, j annotations = new ArrayList<>(annotations); + if(! annotations.stream().filter( a -> a instanceof ApiParam ).findFirst().isPresent() ) { - annotations.add( AnnotationHelper.createApiParam( methodParameter ) ) ; - + annotations.add( AnnotationHelper.createApiParam( methodParameter ) ) ; } @@ -1144,6 +1163,24 @@ private List getParameters(Type type, List annotations, j annotations.add(AnnotationHelper.createFormParam(methodParameter)); } + + if(annotations.size() == 1) + { + if( annotations.get(0) instanceof ApiParam) + { + // If there is only one ApiParam and the parameter type is a member of the java.lang and the name of that parameter is in the path operation's path make the assumption that this is a path param + if(methodParameter.getType().getName().indexOf("java.lang") > -1 && pathParamNames.contains(methodParameter.getName())) + { + annotations.add(AnnotationHelper.createPathParam(methodParameter)); + + } + // If there is only one ApiParam and the parameter type is a member of the java.lang or java.util package we make the assumption that this is a query param + else if( methodParameter.getType().getName().indexOf("java.lang") > -1 || methodParameter.getType().getName().indexOf("java.util") > -1 ) + { + annotations.add(AnnotationHelper.createQueryParam(methodParameter)); + } + } + } final List parameters = extension.extractParameters(annotations, type, typesToSkip, chain); if (!parameters.isEmpty()) { diff --git a/src/test/java/io/sinistral/proteus/controllers/Tests.java b/src/test/java/io/sinistral/proteus/controllers/Tests.java index af11d35..616ed01 100644 --- a/src/test/java/io/sinistral/proteus/controllers/Tests.java +++ b/src/test/java/io/sinistral/proteus/controllers/Tests.java @@ -140,11 +140,18 @@ public CompletableFuture>> responseFu @Consumes("*/*") @ApiOperation(value = "Upload file path endpoint", httpMethod = "POST" ) public ServerResponse responseUploadFilePath(ServerRequest request, @FormParam("file") java.nio.file.Path file ) throws Exception - { - - return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); - - + { + return response(ByteBuffer.wrap(Files.toByteArray(file.toFile()))).applicationOctetStream(); + } + + @POST + @Path("/response/json/echo") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes("*/*") + @ApiOperation(value = "Echo json endpoint", httpMethod = "POST" ) + public ServerResponse responseEchoJson(ServerRequest request, @FormParam("user") User user ) throws Exception + { + return response(user).applicationJson(); } @POST diff --git a/src/test/java/io/sinistral/proteus/models/User.java b/src/test/java/io/sinistral/proteus/models/User.java index be35575..d9cbb78 100644 --- a/src/test/java/io/sinistral/proteus/models/User.java +++ b/src/test/java/io/sinistral/proteus/models/User.java @@ -79,5 +79,9 @@ public UserType getType() return type; } + public static User generateUser() + { + return new User((long)(Math.random()*1000)+1L, UserType.ADMIN); + } } diff --git a/src/test/java/io/sinistral/proteus/server/TestControllerEndpoints.java b/src/test/java/io/sinistral/proteus/server/TestControllerEndpoints.java index 9e556e9..d6361c4 100644 --- a/src/test/java/io/sinistral/proteus/server/TestControllerEndpoints.java +++ b/src/test/java/io/sinistral/proteus/server/TestControllerEndpoints.java @@ -26,6 +26,7 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.sinistral.proteus.models.User; +import io.sinistral.proteus.models.User.UserType; /* * import static io.restassured.RestAssured.*; import static io.restassured.matcher.RestAssuredMatchers.*; import static org.hamcrest.Matchers.*; @@ -101,6 +102,15 @@ public void responsePlaintext() { given().accept(ContentType.TEXT).log().uri().when().get("tests/response/plaintext").then().statusCode(200).and().body(containsString("Hello, World!")); } + + @Test + public void responseEchoUser() + { + User user = new User(101L,UserType.ADMIN); + + given().contentType(ContentType.JSON).accept(ContentType.JSON).body(user).log().uri().when().post("tests/response/json/echo").then().statusCode(200).and().body(containsString("101")); + + } @Test public void responseFutureUser()