diff --git a/modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/ModelConverters.scala b/modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/ModelConverters.scala index 83b0e5eca8..202e2421fb 100644 --- a/modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/ModelConverters.scala +++ b/modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/ModelConverters.scala @@ -27,6 +27,10 @@ object ModelConverters { else converters += c } + def removeConverter(c: ModelConverter) = { + converters -= c + } + def read(cls: Class[_], t: Map[String, String] = Map.empty): Option[Model] = { val types = { if(t.isEmpty)typeMap diff --git a/modules/swagger-core/src/test/scala/converter/CustomConverterTest.scala b/modules/swagger-core/src/test/scala/converter/CustomConverterTest.scala index a958f0a3d2..b33b66e4f4 100644 --- a/modules/swagger-core/src/test/scala/converter/CustomConverterTest.scala +++ b/modules/swagger-core/src/test/scala/converter/CustomConverterTest.scala @@ -25,13 +25,15 @@ import org.scalatest.matchers.ShouldMatchers class CustomConverterTest extends FlatSpec with ShouldMatchers { it should "ignore properties with type Bar" in { // add the custom converter - ModelConverters.addConverter(new CustomConverter, true) + val customConverter = new CustomConverter + ModelConverters.addConverter(customConverter, true) // make sure the field bar: converter.Bar is not present ModelConverters.read(classOf[Foo]) match { case Some(model) => model.properties.get("bar") should be (None) case _ => fail("didn't read anything") } + ModelConverters.removeConverter(customConverter) } } diff --git a/modules/swagger-core/src/test/scala/converter/JaveDateTimeOverride.scala b/modules/swagger-core/src/test/scala/converter/JaveDateTimeOverride.scala index 5a877713b4..8cbe56949d 100644 --- a/modules/swagger-core/src/test/scala/converter/JaveDateTimeOverride.scala +++ b/modules/swagger-core/src/test/scala/converter/JaveDateTimeOverride.scala @@ -17,9 +17,12 @@ import scala.reflect.BeanProperty @RunWith(classOf[JUnitRunner]) class JaveDateTimeOverride extends FlatSpec with ShouldMatchers { - ModelConverters.addConverter(new JavaDateTimeConverter, true) + val javaDateTimeConverter = new JavaDateTimeConverter + ModelConverters.addConverter(javaDateTimeConverter, true) val models = ModelConverters.readAll(classOf[ModelWithDate]) JsonSerializer.asJson(models) should be ("""[{"id":"ModelWithDate","properties":{"dateValue":{"type":"integer","format":"int64"}}}]""") + // cleanup to avoid impacting other test cases with Date model members + ModelConverters.removeConverter(javaDateTimeConverter) } class JavaDateTimeConverter extends SwaggerSchemaConverter { diff --git a/modules/swagger-core/src/test/scala/converter/SnakeCaseConverterTest.scala b/modules/swagger-core/src/test/scala/converter/SnakeCaseConverterTest.scala index 5b4318ffad..fe548918ce 100644 --- a/modules/swagger-core/src/test/scala/converter/SnakeCaseConverterTest.scala +++ b/modules/swagger-core/src/test/scala/converter/SnakeCaseConverterTest.scala @@ -24,7 +24,8 @@ import javax.xml.bind.annotation._ class SnakeCaseConverterTest extends FlatSpec with ShouldMatchers { it should "ignore properties with type Bar" in { // add the custom converter - ModelConverters.addConverter(new SnakeCaseConverter, true) + val snakeCaseConverter = new SnakeCaseConverter + ModelConverters.addConverter(snakeCaseConverter, true) // make sure the field bar: converter.Bar is not present ModelConverters.read(classOf[SnakeCaseModel]) match { @@ -34,6 +35,9 @@ class SnakeCaseConverterTest extends FlatSpec with ShouldMatchers { } case _ => fail("didn't read anything") } + + // cleanup to avoid impacting other test cases with Date model members + ModelConverters.removeConverter(snakeCaseConverter) } } diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala index 88c25462e5..804784693c 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/JaxrsApiReader.scala @@ -24,7 +24,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { val GenericTypeMapper = "([a-zA-Z\\.]*)<([a-zA-Z0-9\\.\\,\\s]*)>".r // decorates a Parameter based on annotations, returns None if param should be ignored - def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): List[Parameter] // Finds the type of the subresource this method produces, in case it's a subresource locator // In case it's not a subresource locator the entity type is returned @@ -167,7 +167,7 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { param.name = TYPE_BODY param.paramType = TYPE_BODY - Some(param.asParameter) + List(param.asParameter) } }).flatten.toList @@ -264,6 +264,25 @@ trait JaxrsApiReader extends ClassReader with ClassReaderUtils { } return fields; } + + def getAllParamsFromFields(cls: Class[_]): List[Parameter] = { + (for(field <- getAllFields(cls)) yield { + // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam + if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || + field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || + field.getAnnotation(classOf[ApiParam]) != null) { + val param = new MutableParameter + param.dataType = processDataType(field.getType, field.getGenericType) + Option (field.getAnnotation(classOf[ApiParam])) match { + case Some(annotation) => toAllowableValues(annotation.allowableValues) + case _ => + } + val annotations = field.getAnnotations + processParamAnnotations(param, annotations) + } + else List.empty + }).flatten.toList + } def pathFromMethod(method: Method): String = { val path = method.getAnnotation(classOf[javax.ws.rs.Path]) diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/BasicJaxrsReader.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/BasicJaxrsReader.scala index 7889b9bda9..2100cca963 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/BasicJaxrsReader.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/BasicJaxrsReader.scala @@ -69,24 +69,7 @@ class BasicJaxrsReader extends JaxrsApiReader { else ((List(), List(), List(), None)) } // look for method-level annotated properties - val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) - yield { - // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam - if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || - field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || - field.getAnnotation(classOf[ApiParam]) != null) { - val param = new MutableParameter - param.dataType = field.getType.getName - Option (field.getAnnotation(classOf[ApiParam])) match { - case Some(annotation) => toAllowableValues(annotation.allowableValues) - case _ => - } - val annotations = field.getAnnotations - processParamAnnotations(param, annotations) - } - else None - } - ).flatten.toList + val parentParams: List[Parameter] = getAllParamsFromFields(cls) for(method <- cls.getMethods) { val returnType = findSubresourceType(method) @@ -146,7 +129,7 @@ class BasicJaxrsReader extends JaxrsApiReader { } // decorates a Parameter based on annotations, returns None if param should be ignored - def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): List[Parameter] = { var shouldIgnore = false for (pa <- paramAnnotations) { pa match { @@ -188,9 +171,9 @@ class BasicJaxrsReader extends JaxrsApiReader { mutable.paramType = TYPE_BODY mutable.name = TYPE_BODY } - Some(mutable.asParameter) + List(mutable.asParameter) } - else None + else List.empty } def findSubresourceType(method: Method): Class[_] = { diff --git a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala index 8077116912..7bf91e17ee 100644 --- a/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala +++ b/modules/swagger-jaxrs/src/main/scala/com/wordnik/swagger/jaxrs/reader/DefaultJaxrsReader.scala @@ -49,24 +49,7 @@ class DefaultJaxrsApiReader extends JaxrsApiReader { case _ => None } // look for method-level annotated properties - val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) - yield { - // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam - if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || - field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || - field.getAnnotation(classOf[ApiParam]) != null) { - val param = new MutableParameter - param.dataType = field.getType.getName - Option (field.getAnnotation(classOf[ApiParam])) match { - case Some(annotation) => toAllowableValues(annotation.allowableValues) - case _ => - } - val annotations = field.getAnnotations - processParamAnnotations(param, annotations) - } - else None - } - ).flatten.toList + val parentParams: List[Parameter] = getAllParamsFromFields(cls) for(method <- cls.getMethods) { val returnType = findSubresourceType(method) @@ -128,7 +111,7 @@ class DefaultJaxrsApiReader extends JaxrsApiReader { } // decorates a Parameter based on annotations, returns None if param should be ignored - def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): List[Parameter] = { var shouldIgnore = false for (pa <- paramAnnotations) { pa match { @@ -170,9 +153,9 @@ class DefaultJaxrsApiReader extends JaxrsApiReader { mutable.paramType = TYPE_BODY mutable.name = TYPE_BODY } - Some(mutable.asParameter) + List(mutable.asParameter) } - else None + else List.empty } def findSubresourceType(method: Method): Class[_] = { diff --git a/modules/swagger-jaxrs/src/test/scala/PathParamTargetTest.scala b/modules/swagger-jaxrs/src/test/scala/PathParamTargetTest.scala index 957d64ce7f..467368d791 100644 --- a/modules/swagger-jaxrs/src/test/scala/PathParamTargetTest.scala +++ b/modules/swagger-jaxrs/src/test/scala/PathParamTargetTest.scala @@ -89,7 +89,7 @@ class JavaPathParamTargetTest extends FlatSpec with ShouldMatchers { // verify the 2nd api val detailsOps = apis.filter(_.path == "/javaPathParamTest/{id}/details").head.operations - val detailOp = detailsOps.head + val detailOp = detailsOps.filter(_.method == "POST").head detailOp.parameters.size should be (3) diff --git a/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala b/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala index 750d133744..97833c9979 100644 --- a/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala +++ b/modules/swagger-jersey-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala @@ -75,24 +75,7 @@ class JerseyApiReader extends JaxrsApiReader { case _ => None } // look for method-level annotated properties - val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) - yield { - // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam - if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || - field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || - field.getAnnotation(classOf[ApiParam]) != null) { - val param = new MutableParameter - param.dataType = field.getType.getName - Option (field.getAnnotation(classOf[ApiParam])) match { - case Some(annotation) => toAllowableValues(annotation.allowableValues) - case _ => - } - val annotations = field.getAnnotations - processParamAnnotations(param, annotations) - } - else None - } - ).flatten.toList + val parentParams: List[Parameter] = getAllParamsFromFields(cls) for(method <- cls.getMethods) { val returnType = findSubresourceType(method) @@ -153,7 +136,7 @@ class JerseyApiReader extends JaxrsApiReader { else None } - def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): List[Parameter] = { var shouldIgnore = false for (pa <- paramAnnotations) { pa match { @@ -211,9 +194,9 @@ class JerseyApiReader extends JaxrsApiReader { mutable.paramType = TYPE_BODY mutable.name = TYPE_BODY } - Some(mutable.asParameter) + List(mutable.asParameter) } - else None + else List() } def findSubresourceType(method: Method): Class[_] = { diff --git a/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala b/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala index c094253732..53f7b4769b 100644 --- a/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala +++ b/modules/swagger-jersey2-jaxrs/src/main/scala/com/wordnik/swagger/jersey/JerseyApiReader.scala @@ -75,24 +75,7 @@ class JerseyApiReader extends JaxrsApiReader { case _ => None } // look for method-level annotated properties - val parentParams: List[Parameter] = (for(field <- getAllFields(cls)) - yield { - // only process fields with @ApiParam, @QueryParam, @HeaderParam, @PathParam - if(field.getAnnotation(classOf[QueryParam]) != null || field.getAnnotation(classOf[HeaderParam]) != null || - field.getAnnotation(classOf[HeaderParam]) != null || field.getAnnotation(classOf[PathParam]) != null || - field.getAnnotation(classOf[ApiParam]) != null) { - val param = new MutableParameter - param.dataType = field.getType.getName - Option (field.getAnnotation(classOf[ApiParam])) match { - case Some(annotation) => toAllowableValues(annotation.allowableValues) - case _ => - } - val annotations = field.getAnnotations - processParamAnnotations(param, annotations) - } - else None - } - ).flatten.toList + val parentParams: List[Parameter] = getAllParamsFromFields(cls) for(method <- cls.getMethods) { val returnType = findSubresourceType(method) @@ -153,7 +136,7 @@ class JerseyApiReader extends JaxrsApiReader { else None } - def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): Option[Parameter] = { + def processParamAnnotations(mutable: MutableParameter, paramAnnotations: Array[Annotation]): List[Parameter] = { var shouldIgnore = false for (pa <- paramAnnotations) { pa match { @@ -198,6 +181,10 @@ class JerseyApiReader extends JaxrsApiReader { } } } + case e: BeanParam => { + val cls = SwaggerContext.loadClass(mutable.dataType) + return getAllParamsFromFields(cls) + } case e: Context => shouldIgnore = true case _ => } @@ -207,9 +194,9 @@ class JerseyApiReader extends JaxrsApiReader { mutable.paramType = TYPE_BODY mutable.name = TYPE_BODY } - Some(mutable.asParameter) + List(mutable.asParameter) } - else None + else List.empty } def findSubresourceType(method: Method): Class[_] = { method.getGenericReturnType match { diff --git a/modules/swagger-jersey2-jaxrs/src/test/scala/BeanParamTest.scala b/modules/swagger-jersey2-jaxrs/src/test/scala/BeanParamTest.scala new file mode 100644 index 0000000000..ec5b06fdcd --- /dev/null +++ b/modules/swagger-jersey2-jaxrs/src/test/scala/BeanParamTest.scala @@ -0,0 +1,44 @@ +import com.wordnik.swagger.jersey.JerseyApiReader +import testresources._ + +import com.wordnik.swagger.jaxrs.reader._ +import com.wordnik.swagger.model._ +import com.wordnik.swagger.config._ + +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.FlatSpec +import org.scalatest.matchers.ShouldMatchers + +@RunWith(classOf[JUnitRunner]) +class BeanParamTest extends FlatSpec with ShouldMatchers { + it should "read beanparam parameters" in { + val reader = new JerseyApiReader + val config = new SwaggerConfig() + val apiResource = reader.read("/api-docs", classOf[BeanParamResource], config).getOrElse(fail("should not be None")) + + val apis = apiResource.apis + apis.size should be (1) + + val ops = apis.head.operations + ops.size should be (1) + + val op = ops.head + op.parameters.size should be (3) + + val param0 = op.parameters(0) + param0.name should be ("ids") + param0.dataType should be ("Set[string]") + param0.paramType should be ("query") + + val param1 = op.parameters(1) + param1.name should be ("startDate") + param1.dataType should be ("Date") + param1.paramType should be ("query") + + val param2 = op.parameters(2) + param2.name should be ("name") + param2.dataType should be ("string") + param2.paramType should be ("query") + } +} diff --git a/modules/swagger-jersey2-jaxrs/src/test/scala/testmodels/BeanParamModel.scala b/modules/swagger-jersey2-jaxrs/src/test/scala/testmodels/BeanParamModel.scala new file mode 100644 index 0000000000..5e7d977260 --- /dev/null +++ b/modules/swagger-jersey2-jaxrs/src/test/scala/testmodels/BeanParamModel.scala @@ -0,0 +1,19 @@ +package testmodels + +import com.wordnik.swagger.annotations.ApiParam +import javax.ws.rs.QueryParam +import java.util.Date + +class BeanParamModel { + @ApiParam(value = "sample set param") + @QueryParam("ids") + var ids: Set[String] = _ + + @ApiParam(value = "sample date param") + @QueryParam("startDate") + var startDate: Date = _ + + @ApiParam(value = "sample string param") + @QueryParam("name") + var name: String = _ +} diff --git a/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/BeanParamResource.scala b/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/BeanParamResource.scala new file mode 100644 index 0000000000..9f476c379d --- /dev/null +++ b/modules/swagger-jersey2-jaxrs/src/test/scala/testresources/BeanParamResource.scala @@ -0,0 +1,16 @@ +package testresources + +import javax.ws.rs._ +import com.wordnik.swagger.annotations._ +import testmodels._ +import javax.ws.rs.core.Response + +@Path("/beanParam") +@Api(value = "/beanParam", description = "Bean Param Resource") +class BeanParamResource { + @GET + @ApiOperation(value = "Search Object", notes = "No details provided", position = 0) + def searchObject(@BeanParam params: BeanParamModel) { + Response.ok.entity("ok").build + } +}