Skip to content
This repository was archived by the owner on Sep 14, 2022. It is now read-only.

Commit 5d1402b

Browse files
committed
Merge pull request #57 from frantuma/issue-43
fix #43 - Parameter types handling
2 parents 20ee147 + 6012e47 commit 5d1402b

File tree

4 files changed

+116
-38
lines changed

4 files changed

+116
-38
lines changed

play-2.4/swagger-play2/app/play/modules/swagger/PlayReader.java

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package play.modules.swagger;
22

33
import com.fasterxml.jackson.databind.JavaType;
4-
import com.fasterxml.jackson.databind.type.TypeFactory;
54
import io.swagger.annotations.*;
65
import io.swagger.annotations.Info;
76
import io.swagger.converter.ModelConverters;
@@ -13,18 +12,22 @@
1312
import io.swagger.models.parameters.Parameter;
1413
import io.swagger.models.properties.*;
1514
import io.swagger.util.BaseReaderUtils;
15+
import io.swagger.util.Json;
1616
import io.swagger.util.ParameterProcessor;
1717
import io.swagger.util.PrimitiveType;
1818
import io.swagger.util.ReflectionUtils;
1919
import org.apache.commons.lang3.StringUtils;
2020
import play.Logger;
2121
import play.modules.swagger.util.CrossUtil;
2222
import play.routes.compiler.*;
23+
import scala.Option;
2324

2425
import java.lang.annotation.Annotation;
2526
import java.lang.reflect.Method;
2627
import java.lang.reflect.Type;
2728
import java.util.*;
29+
import java.util.regex.Matcher;
30+
import java.util.regex.Pattern;
2831

2932
public class PlayReader {
3033

@@ -192,7 +195,7 @@ private Swagger read(Class<?> cls, boolean readHidden) {
192195
}
193196
path.set(httpMethod, operation);
194197
try {
195-
readImplicitParameters(method, operation);
198+
readImplicitParameters(method, operation, cls);
196199
} catch (Exception e) {
197200
throw e;
198201
}
@@ -350,19 +353,19 @@ protected void readInfoConfig(SwaggerDefinition config) {
350353
info.getVendorExtensions().putAll(BaseReaderUtils.parseExtensions(infoConfig.extensions()));
351354
}
352355

353-
private void readImplicitParameters(Method method, Operation operation) {
356+
private void readImplicitParameters(Method method, Operation operation, Class<?> cls) {
354357
ApiImplicitParams implicitParams = method.getAnnotation(ApiImplicitParams.class);
355358
if (implicitParams != null && implicitParams.value().length > 0) {
356359
for (ApiImplicitParam param : implicitParams.value()) {
357-
Parameter p = readImplicitParam(param);
360+
Parameter p = readImplicitParam(param, cls);
358361
if (p != null) {
359362
operation.addParameter(p);
360363
}
361364
}
362365
}
363366
}
364367

365-
protected io.swagger.models.parameters.Parameter readImplicitParam(ApiImplicitParam param) {
368+
protected io.swagger.models.parameters.Parameter readImplicitParam(ApiImplicitParam param, Class<?> cls) {
366369
final Parameter p;
367370
if (param.paramType().equalsIgnoreCase("path")) {
368371
p = new PathParameter();
@@ -380,18 +383,31 @@ protected io.swagger.models.parameters.Parameter readImplicitParam(ApiImplicitPa
380383
}
381384
Type type = null;
382385
// Swagger ReflectionUtils can't handle file or array datatype
383-
if (!"file".equalsIgnoreCase(param.dataType()) && !"array".equalsIgnoreCase(param.dataType())){
384-
type = typeFromString(param.dataType());
386+
if (!"".equalsIgnoreCase(param.dataType()) && !"file".equalsIgnoreCase(param.dataType()) && !"array".equalsIgnoreCase(param.dataType())){
387+
type = typeFromString(param.dataType(), cls);
388+
385389
}
386-
return ParameterProcessor.applyAnnotations(getSwagger(), p, type == null ? String.class : type, Collections.singletonList(param));
390+
Parameter result = ParameterProcessor.applyAnnotations(getSwagger(), p, type == null ? String.class : type, Collections.singletonList(param));
391+
392+
if (result instanceof AbstractSerializableParameter && type != null) {
393+
Property schema = createProperty(type);
394+
((AbstractSerializableParameter)p).setProperty(schema);
395+
}
396+
397+
return result;
398+
387399
}
388400

389-
private static Type typeFromString(String type) {
401+
private static Type typeFromString(String type, Class<?> cls) {
390402
final PrimitiveType primitive = PrimitiveType.fromName(type);
391403
if (primitive != null) {
392404
return primitive.getKeyClass();
393405
}
394406
try {
407+
Type routeType = getOptionTypeFromString (type, cls);
408+
409+
if (routeType != null) return routeType;
410+
395411
return Thread.currentThread().getContextClassLoader().loadClass(type);
396412
} catch (Exception e) {
397413
Logger.error(String.format("Failed to resolve '%s' into class", type), e);
@@ -522,37 +538,69 @@ private Operation parseMethod(Class<?> cls, Method method, Route route) {
522538
return operation;
523539
}
524540

525-
private Type getParamType(Class<?> cls, Method method, String simpleTypeName) {
526-
Type[] genericParameterTypes = method.getGenericParameterTypes();
527-
for (Type genericParameterType : genericParameterTypes) {
528-
final Type type = TypeFactory.defaultInstance().constructType(genericParameterType, cls);
529-
final Class<?> paramClass = ((JavaType) type).getRawClass();
530-
if (simpleTypeName.equalsIgnoreCase(paramClass.getSimpleName())) {
531-
return type;
532-
}
533-
if (simpleTypeName.equalsIgnoreCase(paramClass.getName())) {
534-
return type;
541+
542+
final static class OptionTypeResolver {
543+
private Option<Integer> optionTypeInt;
544+
private Option<Long> optionTypeLong;
545+
private Option<Byte> optionTypeByte;
546+
private Option<Boolean> optionTypeBoolean;
547+
private Option<Character> optionTypeChar;
548+
private Option<Float> optionTypeFloat;
549+
private Option<Double> optionTypeDouble;
550+
private Option<Short> optionTypeShort;
551+
552+
static Type resolveOptionType (String innerType, Class<?> cls) {
553+
try {
554+
return Json.mapper().getTypeFactory().constructType(
555+
OptionTypeResolver.class.getDeclaredField("optionType" + innerType).getGenericType(), cls);
556+
} catch (NoSuchFieldException e) {
557+
return null;
535558
}
536559
}
537-
return null;
538560
}
539561

540-
private List<Annotation> getParamAnnotations(Class<?> cls, Type[] genericParameterTypes, Annotation[][] paramAnnotations, String simpleTypeName, int fieldPosition) {
541-
Type type = TypeFactory.defaultInstance().constructType(genericParameterTypes[fieldPosition], cls);
542-
Class<?> paramClass = ((JavaType)type).getRawClass();
543-
if (simpleTypeName.equalsIgnoreCase(paramClass.getSimpleName())) {
544-
return Arrays.asList(paramAnnotations[fieldPosition]);
562+
private static Type getOptionTypeFromString (String simpleTypeName, Class<?> cls) {
563+
564+
if (simpleTypeName == null) return null;
565+
String regex = "(Option|scala\\.Option)\\s*\\[\\s*(Int|Long|Float|Double|Byte|Short|Char|Boolean)\\s*\\]\\s*$";
566+
567+
Pattern pattern = Pattern.compile(regex);
568+
Matcher matcher = pattern.matcher(simpleTypeName);
569+
if (matcher.find())
570+
{
571+
String enhancedType = matcher.group(2);
572+
return OptionTypeResolver.resolveOptionType(enhancedType, cls);
573+
} else {
574+
return null;
575+
}
576+
}
577+
private Type getParamType(Class<?> cls, Method method, String simpleTypeName, int position) {
578+
579+
try {
580+
Type type = getOptionTypeFromString (simpleTypeName, cls);
581+
if (type != null) return type;
582+
583+
Type[] genericParameterTypes = method.getGenericParameterTypes();
584+
return Json.mapper().getTypeFactory().constructType(genericParameterTypes[position], cls);
585+
} catch (Exception e) {
586+
Logger.error(String.format("Exception getting parameter type for method %s, param %s at position %d"), e);
587+
return null;
545588
}
546-
if (simpleTypeName.equalsIgnoreCase(paramClass.getName())) {
589+
590+
}
591+
592+
private List<Annotation> getParamAnnotations(Class<?> cls, Type[] genericParameterTypes, Annotation[][] paramAnnotations, String simpleTypeName, int fieldPosition) {
593+
try {
547594
return Arrays.asList(paramAnnotations[fieldPosition]);
595+
} catch (Exception e) {
596+
Logger.error(String.format("Exception getting parameter type for method %s, param %s at position %d"), e);
597+
return null;
548598
}
549-
return null;
550599
}
551600

552601
private List<Annotation> getParamAnnotations(Class<?> cls, Method method, String simpleTypeName, int fieldPosition) {
553602
Type[] genericParameterTypes = method.getGenericParameterTypes();
554603
Annotation[][] paramAnnotations = method.getParameterAnnotations();
555-
556604
List<Annotation> annotations = getParamAnnotations(cls, genericParameterTypes, paramAnnotations, simpleTypeName, fieldPosition);
557605
if (annotations != null) {
558606
return annotations;
@@ -587,7 +635,7 @@ private List<Parameter> getParameters(Class<?> cls, Method method, Route route)
587635
if (def.startsWith("\"") && def.endsWith("\"")){
588636
def = def.substring(1,def.length()-1);
589637
}
590-
Type type = getParamType(cls, method, p.typeName());
638+
Type type = getParamType(cls, method, p.typeName(), fieldPosition);
591639
Property schema = createProperty(type);
592640
if (route.path().has(p.name())) {
593641
// it's a path param
@@ -602,9 +650,7 @@ private List<Parameter> getParameters(Class<?> cls, Method method, Route route)
602650
}
603651
parameter.setName(p.name());
604652
List<Annotation> annotations = getParamAnnotations(cls, method, p.typeName(), fieldPosition);
605-
606653
ParameterProcessor.applyAnnotations(getSwagger(), parameter, type, annotations);
607-
608654
parameters.add(parameter);
609655
fieldPosition++;
610656
}
@@ -623,15 +669,15 @@ private static Set<Scheme> parseSchemes(String schemes) {
623669
}
624670

625671
private static boolean isVoid(Type type) {
626-
final Class<?> cls = TypeFactory.defaultInstance().constructType(type).getRawClass();
672+
final Class<?> cls = Json.mapper().getTypeFactory().constructType(type).getRawClass();
627673
return Void.class.isAssignableFrom(cls) || Void.TYPE.isAssignableFrom(cls);
628674
}
629675

630676
private static boolean isValidResponse(Type type) {
631677
if (type == null) {
632678
return false;
633679
}
634-
final JavaType javaType = TypeFactory.defaultInstance().constructType(type);
680+
final JavaType javaType = Json.mapper().getTypeFactory().constructType(type);
635681
if (isVoid(javaType)) {
636682
return false;
637683
}

play-2.4/swagger-play2/build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ crossScalaVersions := Seq("2.11.6", "2.11.7")
88

99
libraryDependencies ++= Seq(
1010
"org.slf4j" % "slf4j-api" % "1.6.4",
11-
"io.swagger" % "swagger-core" % "1.5.7",
12-
"io.swagger" %% "swagger-scala-module" % "1.0.0",
11+
"io.swagger" % "swagger-core" % "1.5.8-SNAPSHOT",
12+
"io.swagger" %% "swagger-scala-module" % "1.0.2-SNAPSHOT",
1313
"com.typesafe.play" %% "routes-compiler" % "2.4.6",
1414
"com.typesafe.play" %% "play-ebean" % "2.0.0" % "test",
1515
"org.specs2" %% "specs2-core" % "3.6.6" % "test",

play-2.4/swagger-play2/test/PlayApiListingCacheSpec.scala

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import java.io.File
22

33
import io.swagger.config.ScannerFactory
44
import io.swagger.models.{ModelImpl, HttpMethod}
5-
import io.swagger.models.parameters.{BodyParameter, PathParameter}
5+
import io.swagger.models.parameters.{QueryParameter, BodyParameter, PathParameter}
66
import io.swagger.models.properties.{RefProperty, ArrayProperty}
77
import play.modules.swagger._
88
import org.specs2.mutable._
@@ -24,6 +24,7 @@ GET /api/pointsofinterest testdata.PointOfInterestController.list(eastingMin:Dou
2424
GET /api/dog testdata.DogController.list
2525
PUT /api/dog testdata.DogController.add1
2626
GET /api/cat @testdata.CatController.list
27+
GET /api/cat43 @testdata.CatController.testIssue43(test_issue_43_param: Option[Int])
2728
PUT /api/cat @testdata.CatController.add1
2829
GET /api/fly testdata.FlyController.list
2930
PUT /api/dog testdata.DogController.add1
@@ -72,11 +73,13 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
7273
val docRoot = ""
7374
val swagger = ApiListingCache.listing(docRoot, "127.0.0.1")
7475

75-
Logger.debug ("swagger: " + toJsonString(swagger.get))
76+
Logger.debug ("swagger: " + toJsonString(swagger))
77+
swagger must beSome
78+
7679
swagger must beSome
7780
swagger.get.getSwagger must beEqualTo("2.0")
7881
swagger.get.getBasePath must beEqualTo(basePath)
79-
swagger.get.getPaths.size must beEqualTo(6)
82+
swagger.get.getPaths.size must beEqualTo(7)
8083
swagger.get.getDefinitions.size must beEqualTo(5)
8184
swagger.get.getHost must beEqualTo(swaggerConfig.getHost)
8285
swagger.get.getInfo.getContact.getName must beEqualTo(swaggerConfig.getContact)
@@ -133,6 +136,23 @@ PUT /api/dog/:id testdata.DogController.add0(id:String)
133136
opCatPut.getResponses.get("200").getSchema.asInstanceOf[RefProperty].getSimpleRef must beEqualTo("ActionAnyContent")
134137
opCatPut.getProduces must beNull
135138

139+
val pathCat43 = swagger.get.getPaths.get("/cat43")
140+
pathCat43.getOperations.size must beEqualTo(1)
141+
142+
val opCatGet43 = pathCat43.getOperationMap.get(HttpMethod.GET)
143+
opCatGet43.getOperationId must beEqualTo("test issue #43_nick")
144+
opCatGet43.getResponses.get("200").getSchema.asInstanceOf[ArrayProperty].getItems.asInstanceOf[RefProperty].getSimpleRef must beEqualTo("Cat")
145+
146+
opCatGet43.getParameters.head.getName must beEqualTo("test_issue_43_param")
147+
opCatGet43.getParameters.head.getIn must beEqualTo("query")
148+
opCatGet43.getParameters.head.asInstanceOf[QueryParameter].getType must beEqualTo("integer")
149+
150+
opCatGet43.getParameters.get(1).getName must beEqualTo("test_issue_43_implicit_param")
151+
opCatGet43.getParameters.get(1).getIn must beEqualTo("query")
152+
opCatGet43.getParameters.get(1).asInstanceOf[QueryParameter].getType must beEqualTo("integer")
153+
154+
155+
136156
val pathDog = swagger.get.getPaths.get("/dog")
137157
pathDog.getOperations.size must beEqualTo(2)
138158

play-2.4/swagger-play2/test/testdata/CatController.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ class CatController extends Controller {
5555
def no_route = Action {
5656
request => Ok("test case")
5757
}
58+
59+
@ApiOperation(value = "test issue #43",
60+
nickname = "test issue #43_nick",
61+
notes = "test issue #43_notes",
62+
response = classOf[testdata.Cat],
63+
responseContainer = "List",
64+
httpMethod = "GET")
65+
@ApiImplicitParams(Array(
66+
new ApiImplicitParam(name = "test_issue_43_implicit_param", dataType = "Option[Int]", value = "test issue #43 implicit param", paramType = "query")))
67+
def testIssue43(test_issue_43_param: Option[Int]) = Action {
68+
request => Ok("test issue #43")
69+
}
5870
}
5971

6072
case class Cat(id: Long, name: String)

0 commit comments

Comments
 (0)