Skip to content

Commit 2c29336

Browse files
committed
merged from 2.10 branch
1 parent 3fab575 commit 2c29336

File tree

25 files changed

+470
-53
lines changed

25 files changed

+470
-53
lines changed

modules/swagger-annotations/src/main/java/com/wordnik/swagger/annotations/ApiModel.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@
4343
Class<?> parent() default Void.class;
4444
/** for models with a base class, a discriminator can be provided for polymorphic use cases */
4545
String discriminator() default "";
46+
Class<?>[] subTypes() default {};
4647
}

modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/ModelConverters.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ object ModelConverters {
3838
val output = new HashMap[String, Model]
3939
var model = read(cls)
4040
val propertyNames = new HashSet[String]
41+
42+
// add subTypes
43+
model.map(_.subTypes.map(typeRef => {
44+
try{
45+
LOGGER.debug("loading subtype " + typeRef)
46+
val cls = SwaggerContext.loadClass(typeRef)
47+
read(cls) match {
48+
case Some(model) => output += cls.getName -> model
49+
case _ =>
50+
}
51+
}
52+
catch {
53+
case e: Exception => LOGGER.error("can't load class " + typeRef)
54+
}
55+
}))
56+
57+
// add properties
4158
model.map(m => {
4259
output += cls.getName -> m
4360
val checkedNames = new HashSet[String]

modules/swagger-core/src/main/scala/com/wordnik/swagger/converter/SwaggerSchemaConverter.scala

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.wordnik.swagger.converter
33
import com.wordnik.swagger.model._
44
import com.wordnik.swagger.annotations.ApiModel
55

6+
import com.fasterxml.jackson.annotation.{ JsonTypeInfo, JsonSubTypes }
7+
68
import org.slf4j.LoggerFactory
79

810
import scala.collection.mutable.LinkedHashMap
@@ -24,15 +26,27 @@ class SwaggerSchemaConverter
2426
val sortedProperties = new LinkedHashMap[String, ModelProperty]()
2527
p.sortWith(_._1 < _._1).foreach(e => sortedProperties += e._2 -> e._3)
2628

27-
val baseModel = Option(cls.getAnnotation(classOf[ApiModel])) match {
28-
case Some(e) => Option(e.parent.getName)
29+
val parent = Option(cls.getAnnotation(classOf[ApiModel])) match {
30+
case Some(e) => Some(e.parent.getName)
2931
case _ => None
3032
}
31-
val discriminator = Option(cls.getAnnotation(classOf[ApiModel])) match {
32-
case Some(e) => Option(e.discriminator)
33-
case _ => None
33+
val discriminator = {
34+
val v =
35+
if(cls.getAnnotation(classOf[ApiModel]) != null)
36+
cls.getAnnotation(classOf[ApiModel]).discriminator
37+
else if(cls.getAnnotation(classOf[JsonTypeInfo]) != null)
38+
cls.getAnnotation(classOf[JsonTypeInfo]).property
39+
else ""
40+
if(v != null && v != "") Some(v)
41+
else None
42+
}
43+
val subTypes = {
44+
if(cls.getAnnotation(classOf[ApiModel]) != null)
45+
cls.getAnnotation(classOf[ApiModel]).subTypes.map(_.getName).toList
46+
else if(cls.getAnnotation(classOf[JsonSubTypes]) != null)
47+
(for(subType <- cls.getAnnotation(classOf[JsonSubTypes]).value) yield (subType.value.getName)).toList
48+
else List()
3449
}
35-
3650
sortedProperties.size match {
3751
case 0 => None
3852
case _ => Some(Model(
@@ -41,8 +55,9 @@ class SwaggerSchemaConverter
4155
cls.getName,
4256
sortedProperties,
4357
toDescriptionOpt(cls),
44-
baseModel,
45-
discriminator
58+
parent,
59+
discriminator,
60+
subTypes
4661
))
4762
}
4863
}

modules/swagger-core/src/main/scala/com/wordnik/swagger/core/filter/SpecFilter.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.wordnik.swagger.core.filter
1818

1919
import com.wordnik.swagger.model._
20+
import com.wordnik.swagger.core.SwaggerContext
21+
import com.wordnik.swagger.converter.ModelConverters
2022
import com.wordnik.swagger.converter.ModelInheritenceUtil
2123

2224
import org.slf4j.LoggerFactory
@@ -48,7 +50,7 @@ class SpecFilter {
4850
Some(op.copy(parameters = filteredParams))
4951
}
5052
else None
51-
}).flatten.toList
53+
}).flatten.toList.sortWith(_.position < _.position)
5254
filteredOps.size match {
5355
case 0 => None
5456
case _ => Some(api.copy(operations = filteredOps))
@@ -63,11 +65,13 @@ class SpecFilter {
6365
def filterModels(allModels: Option[Map[String, Model]], apis: List[ApiDescription]) = {
6466
val modelNames = requiredModels(allModels, apis)
6567
val existingModels = allModels.getOrElse(Map[String, Model]())
68+
LOGGER.debug("existingModels: " + modelNames)
6669
val output = (for(model <- modelNames) yield {
6770
if(existingModels.contains(model))
6871
Some(model, existingModels(model))
6972
else None
7073
}).flatten.toMap
74+
7175
val filtered = ModelInheritenceUtil.expand(output)
7276
if(output.size > 0) Some(filtered)
7377
else None
@@ -82,6 +86,7 @@ class SpecFilter {
8286
op.responseMessages.foreach(_.responseModel.map{modelNames += _})
8387
}
8488
})
89+
LOGGER.debug("requiredModels: " + modelNames)
8590
val topLevelModels = (for(model <- modelNames) yield {
8691
model match {
8792
case ComplexTypeMatcher(basePart) => {
@@ -92,8 +97,22 @@ class SpecFilter {
9297
case _ => model
9398
}
9499
}).toList
100+
val subTypes = (for(model <- topLevelModels) yield {
101+
allModels match {
102+
case Some(models) if(models.contains(model)) => {
103+
val m = models(model)
104+
(for(subType <- m.subTypes) yield {
105+
val cls = SwaggerContext.loadClass(subType)
106+
for(model <- ModelConverters.readAll(cls)) yield {
107+
model.name
108+
}
109+
}).flatten.toList
110+
}
111+
case _ => List()
112+
}
113+
}).flatten.toList
95114
val properties = requiredProperties(topLevelModels, allModels.getOrElse(Map()), new HashSet[String]())
96-
topLevelModels ++ properties
115+
topLevelModels ++ subTypes ++ properties
97116
}
98117

99118
def requiredProperties(models: List[String], allModels: Map[String, Model], inspectedTypes: HashSet[String]): List[String] = {

modules/swagger-core/src/main/scala/com/wordnik/swagger/model/SwaggerModels.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ case class Model(
5151
properties: LinkedHashMap[String, ModelProperty],
5252
description: Option[String] = None,
5353
baseModel: Option[String] = None,
54-
discriminator: Option[String] = None)
54+
discriminator: Option[String] = None,
55+
subTypes: List[String] = List.empty)
5556

5657
case class ModelProperty(
5758
`type`: String,

modules/swagger-core/src/main/scala/com/wordnik/swagger/model/SwaggerSerializers.scala

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.wordnik.swagger.model
22

33
import com.wordnik.swagger.core.SwaggerSpec
4+
import com.wordnik.swagger.core.util.ModelUtil
45

56
import org.json4s._
67
import org.json4s.JsonDSL._
@@ -19,7 +20,7 @@ object SwaggerSerializers extends Serializers {
1920
new JsonSchemaModelRefSerializer +
2021
new AllowableValuesSerializer +
2122
new JsonSchemaParameterSerializer +
22-
new OperationSerializer +
23+
new JsonSchemaOperationSerializer +
2324
new ResponseMessageSerializer +
2425
new ApiDescriptionSerializer +
2526
new ApiListingReferenceSerializer +
@@ -68,7 +69,7 @@ object SwaggerSerializers extends Serializers {
6869
})) ~
6970
("extends" -> {
7071
x.baseModel match {
71-
case Some(e) if(e != "void") => Extraction.decompose(e)
72+
case Some(e) if(e != "void" && e != "java.lang.Void") => Extraction.decompose(e)
7273
case _ =>JNothing
7374
}
7475
}) ~
@@ -85,6 +86,14 @@ object SwaggerSerializers extends Serializers {
8586
}
8687
case _ => List.empty
8788
}
89+
}) ~
90+
("subTypes" -> {
91+
x.subTypes match {
92+
case e: List[String] if (e.size > 0) => {
93+
Extraction.decompose(for(m <- e) yield ModelUtil.cleanDataType(m))
94+
}
95+
case _ => JNothing
96+
}
8897
})
8998
}
9099
))
@@ -107,7 +116,7 @@ object SwaggerSerializers extends Serializers {
107116
case ComplexTypeMatcher(container, value) =>
108117
toJsonSchemaContainer(container) ~ {
109118
("items" -> {if(isSimpleType(value))
110-
toJsonSchema("items", value)
119+
toJsonSchema("type", value)
111120
else
112121
toJsonSchema("$ref", value)})
113122
}
@@ -160,6 +169,79 @@ object SwaggerSerializers extends Serializers {
160169
}
161170
}
162171

172+
class JsonSchemaOperationSerializer extends CustomSerializer[Operation](formats => ({
173+
case json =>
174+
implicit val fmts: Formats = formats
175+
Operation(
176+
(json \ "method").extractOrElse({
177+
!!(json, OPERATION, "method", "missing required field", ERROR)
178+
""
179+
}),
180+
(json \ "summary").extract[String],
181+
(json \ "notes").extractOrElse(""),
182+
(json \ "responseClass").extractOrElse({
183+
!!(json, OPERATION, "responseClass", "missing required field", ERROR)
184+
""
185+
}),
186+
(json \ "nickname").extractOrElse({
187+
!!(json, OPERATION, "nickname", "missing required field", ERROR)
188+
""
189+
}),
190+
(json \ "position").extractOrElse(0),
191+
(json \ "produces").extractOrElse(List()),
192+
(json \ "consumes").extractOrElse(List()),
193+
(json \ "protocols").extractOrElse(List()),
194+
(json \ "authorizations").extractOrElse(List()),
195+
(json \ "parameters").extract[List[Parameter]],
196+
(json \ "responseMessages").extract[List[ResponseMessage]],
197+
(json \ "deprecated").extractOpt[String]
198+
)
199+
}, {
200+
case x: Operation =>
201+
implicit val fmts = formats
202+
203+
val output = toJsonSchema("type", x.responseClass)
204+
205+
("method" -> x.method) ~
206+
("summary" -> x.summary) ~
207+
("notes" -> x.notes) ~
208+
output ~
209+
("nickname" -> x.nickname) ~
210+
("produces" -> {
211+
x.produces match {
212+
case e: List[String] if(e.size > 0) => Extraction.decompose(e)
213+
case _ => JNothing
214+
}
215+
}) ~
216+
("consumes" -> {
217+
x.consumes match {
218+
case e: List[String] if(e.size > 0) => Extraction.decompose(e)
219+
case _ => JNothing
220+
}
221+
}) ~
222+
("protocols" -> {
223+
x.protocols match {
224+
case e: List[String] if(e.size > 0) => Extraction.decompose(e)
225+
case _ => JNothing
226+
}
227+
}) ~
228+
("authorizations" -> {
229+
x.authorizations match {
230+
case e: List[String] if(e.size > 0) => Extraction.decompose(e)
231+
case _ => JNothing
232+
}
233+
}) ~
234+
("parameters" -> Extraction.decompose(x.parameters)) ~
235+
("responseMessages" -> {
236+
x.responseMessages match {
237+
case e: List[ResponseMessage] if(e.size > 0) => Extraction.decompose(e)
238+
case _ => JNothing
239+
}
240+
}) ~
241+
("deprecated" -> x.`deprecated`)
242+
}
243+
))
244+
163245
class JsonSchemaModelPropertySerializer extends CustomSerializer[ModelProperty] (formats => ({
164246
case json =>
165247
implicit val fmts: Formats = formats
@@ -272,8 +354,8 @@ object SwaggerSerializers extends Serializers {
272354
""
273355
}),
274356
(json \ "allowableValues").extract[AllowableValues],
275-
(json \ "paramType").extractOrElse({
276-
!!(json, OPERATION_PARAM, "paramType", "missing required field", ERROR)
357+
(json \ "type").extractOrElse({
358+
!!(json, OPERATION_PARAM, "type", "missing required field", ERROR)
277359
""
278360
}),
279361
(json \ "paramAccess").extractOpt[String]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package converter
2+
3+
import converter.models._
4+
5+
import com.wordnik.swagger.model._
6+
import com.wordnik.swagger.converter._
7+
8+
import com.wordnik.swagger.core.util._
9+
import com.wordnik.swagger.annotations.ApiModelProperty
10+
11+
12+
import org.json4s._
13+
import org.json4s.JsonDSL._
14+
import org.json4s.jackson.JsonMethods._
15+
import org.json4s.jackson.Serialization.{read, write}
16+
17+
import java.util.Date
18+
19+
import org.junit.runner.RunWith
20+
import org.scalatest.junit.JUnitRunner
21+
import org.scalatest.FlatSpec
22+
import org.scalatest.matchers.ShouldMatchers
23+
24+
import scala.reflect.BeanProperty
25+
26+
@RunWith(classOf[JUnitRunner])
27+
class JacksonSubTypeModelTest extends FlatSpec with ShouldMatchers {
28+
implicit val formats = SwaggerSerializers.formats
29+
30+
it should "read a model with subTypes" in {
31+
val model = ModelConverters.read(classOf[JAnimal]).getOrElse(fail("no model found"))
32+
model.subTypes.size should be (2)
33+
34+
write(model) should be ("""{"id":"JAnimal","discriminator":"type","properties":{"type":{"type":"string","description":"type of animal"},"date":{"type":"string","format":"date-time","description":"Date added to the zoo"}},"subTypes":["JWildAnimal","JDomesticAnimal"]}""")
35+
36+
/*
37+
{
38+
"id": "JAnimal",
39+
"discriminator": "type",
40+
"properties": {
41+
"type": {
42+
"type": "string",
43+
"description": "type of animal"
44+
},
45+
"date": {
46+
"type": "string",
47+
"format": "date-time",
48+
"description": "Date added to the zoo"
49+
}
50+
},
51+
"subTypes": [
52+
"JWildAnimal",
53+
"JDomesticAnimal"
54+
]
55+
}
56+
*/
57+
}
58+
}

modules/swagger-core/src/test/scala/converter/ModelPropertyParserTest.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ class ModelPropertyParserTest extends FlatSpec with ShouldMatchers {
2525
implicit val properties = new scala.collection.mutable.LinkedHashMap[String, ModelProperty]
2626
val parser = new ModelPropertyParser(cls)
2727
parser.parse
28-
println(properties)
2928
}
3029
}

0 commit comments

Comments
 (0)