Skip to content

Commit

Permalink
Merge branch 'master' into release2_5
Browse files Browse the repository at this point in the history
  • Loading branch information
kailuowang committed May 19, 2016
2 parents 9d2d074 + f349e15 commit 739beee
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 21 deletions.
41 changes: 22 additions & 19 deletions src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.iheart.playSwagger.Domain.{Definition, SwaggerParameter}
import play.api.libs.json._
import ResourceReader.read
import play.api.libs.functional.syntax._
import org.yaml.snakeyaml.Yaml
import SwaggerParameterMapper.mapParam
import scala.collection.immutable.ListMap
Expand Down Expand Up @@ -149,18 +148,28 @@ final case class SwaggerSpecGenerator(

import play.api.libs.functional.syntax._

private lazy val propFormat: Writes[SwaggerParameter] = (
private lazy val paramFormat: Writes[SwaggerParameter] = (
(__ \ 'name).write[String] ~
(__ \ 'type).writeNullable[String] ~
(__ \ 'format).writeNullable[String] ~
(__ \ 'required).write[Boolean] ~
(__ \ 'default).writeNullable[JsValue] ~
(__ \ 'example).writeNullable[JsValue] ~
(__ \ "schema").writeNullable[String](refWrite) ~
(__ \ "items").lazyWriteNullable[SwaggerParameter](propFormat.transform((js: JsValue) transformItems(js))) ~
(__ \ "items").writeNullable[SwaggerParameter](defPropFormat) ~
(__ \ "enum").writeNullable[Seq[String]]
)(unlift(SwaggerParameter.unapply))

private lazy val defPropFormat: Writes[SwaggerParameter] = (
(__ \ 'type).writeNullable[String] ~
(__ \ 'format).writeNullable[String] ~
(__ \ 'default).writeNullable[JsValue] ~
(__ \ 'example).writeNullable[JsValue] ~
(__ \ "$ref").writeNullable[String] ~
(__ \ "items").lazyWriteNullable[SwaggerParameter](defPropFormat) ~
(__ \ "enum").writeNullable[Seq[String]]
)(p (p.`type`, p.format, p.default, p.example, p.referenceType.map(referencePrefix + _), p.items, p.enum))

implicit class PathAdditions(path: JsPath) {
def writeNullableIterable[A <: Iterable[_]](implicit writes: Writes[A]): OWrites[A] =
OWrites[A] { (a: A)
Expand All @@ -169,26 +178,20 @@ final case class SwaggerSpecGenerator(
}
}

private def transformItems(js: JsValue): JsValue = {
val filtered = js.as[JsObject] - "name"
val movedRef = (filtered \ "schema" \ "$ref").asOpt[JsValue].fold(filtered) {
(filtered - "schema") + "$ref" _
}
(movedRef \ "items").asOpt[JsValue].fold(movedRef) {
movedRef + "items" _
}
}

private implicit val propFormatInDef = propFormat.transform((__ \ 'name).prune(_).get)

private implicit val swesWriter: Writes[Seq[SwaggerParameter]] = Writes[Seq[SwaggerParameter]] { ps
JsObject(ps.map(p p.name Json.toJson(p)))
JsObject(ps.map(p p.name Json.toJson(p)(defPropFormat)))
}

private implicit val defFormat: Writes[Definition] = (
(__ \ 'description).writeNullable[String] ~
(__ \ 'properties).write[Seq[SwaggerParameter]] ~
(__ \ 'required).write[Seq[String]]
)((d: Definition) (d.description, d.properties, d.properties.filter(_.required).map(_.name)))
(__ \ 'required).writeNullable[Seq[String]]
)((d: Definition) (d.description, d.properties, requiredProperties(d.properties)))

private def requiredProperties(properties: Seq[SwaggerParameter]): Option[Seq[String]] = {
val required = properties.filter(_.required).map(_.name)
if (required.isEmpty) None else Some(required)
}

private def defaultBase = readBaseCfg("swagger.json") orElse readBaseCfg("swagger.yml") getOrElse Json.obj()

Expand Down Expand Up @@ -280,7 +283,7 @@ final case class SwaggerSpecGenerator(
.fold(Seq.empty[SwaggerParameter])(_.map(mapParam(_, modelQualifier)))

JsArray(params.map { p
val jo = Json.toJson(p)(propFormat).as[JsObject]
val jo = Json.toJson(p)(paramFormat).as[JsObject]
val in = if (pathParams.contains(p.name)) "path" else "query"
jo + ("in" JsString(in))
})
Expand Down
14 changes: 14 additions & 0 deletions src/test/resources/test.routes
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,17 @@ POST /somethingPolymorphic controllers.Player.somethingPolymorph
# $ref: '#/definitions/com.iheart.playSwagger.JavaEnumContainer'
###
POST /somethingWithEnum controllers.Player.somethingWithEnum()

###
# parameters:
# - name: body
# description: option example
# schema:
# $ref: '#/definitions/com.iheart.playSwagger.AllOptional'
# responses:
# default:
# description: Something optional
# schema:
# $ref: '#/definitions/com.iheart.playSwagger.AllOptional'
###
POST /somethingOptional controllers.Player.somethingOptional()
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ trait PolymorphicItem

case class JavaEnumContainer(status: SampleJavaEnum)

case class AllOptional(a: Option[String], b: Option[String])

class SwaggerSpecGeneratorSpec extends Specification {
implicit val cl = getClass.getClassLoader

Expand All @@ -29,12 +31,14 @@ class SwaggerSpecGeneratorSpec extends Specification {
lazy val json = SwaggerSpecGenerator("com.iheart").generate("test.routes").get
lazy val pathJson = json \ "paths"
lazy val definitionsJson = json \ "definitions"
lazy val postBodyJson = (pathJson \ "/post-body" \ "post").as[JsObject]
lazy val artistJson = (pathJson \ "/api/artist/{aid}/playedTracks/recent" \ "get").as[JsObject]
lazy val stationJson = (pathJson \ "/api/station/{sid}/playedTracks/last" \ "get").as[JsObject]
lazy val addTrackJson = (pathJson \ "/api/station/playedTracks" \ "post").as[JsObject]
lazy val playerJson = (pathJson \ "/api/player/{pid}/context/{bid}" \ "get").as[JsObject]
lazy val playerAddTrackJson = (pathJson \ "/api/player/{pid}/playedTracks" \ "post").as[JsObject]
lazy val resourceJson = (pathJson \ "/api/resource").as[JsObject]
lazy val allOptionalDefJson = (definitionsJson \ "com.iheart.playSwagger.AllOptional").as[JsObject]
lazy val artistDefJson = (definitionsJson \ "com.iheart.playSwagger.Artist").as[JsObject]
lazy val trackJson = (definitionsJson \ "com.iheart.playSwagger.Track").as[JsObject]
lazy val studentJson = (definitionsJson \ "com.iheart.playSwagger.Student").asOpt[JsObject]
Expand Down Expand Up @@ -80,7 +84,7 @@ class SwaggerSpecGeneratorSpec extends Specification {
}

"read schema of referenced type" >> {
(trackJson \ "properties" \ "artist" \ "schema" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.Artist")
(trackJson \ "properties" \ "artist" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.Artist")
}

"read seq of referenced type" >> {
Expand All @@ -102,7 +106,7 @@ class SwaggerSpecGeneratorSpec extends Specification {

"read trait with container" >> {
polymorphicContainerJson must beSome[JsObject]
(polymorphicContainerJson.get \ "properties" \ "item" \ "schema" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.PolymorphicItem")
(polymorphicContainerJson.get \ "properties" \ "item" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.PolymorphicItem")
polymorphicItemJson must beSome[JsObject]
}

Expand All @@ -123,6 +127,7 @@ class SwaggerSpecGeneratorSpec extends Specification {
val params = parametersOf(addTrackJson)
params.length === 1
(params.head \ "in").asOpt[String] === Some("body")
(params.head \ "schema" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.Track")
}

"does not generate for end points marked as hidden" >> {
Expand Down Expand Up @@ -212,6 +217,32 @@ class SwaggerSpecGeneratorSpec extends Specification {
}
}

"should contain schemas in responses" >> {
(postBodyJson \ "responses" \ "200" \ "schema" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.FooWithSeq2")
}

"should contain schemas in requests" >> {
val paramJson = parametersOf(addTrackJson).head
(paramJson \ "schema" \ "$ref").asOpt[String] === Some("#/definitions/com.iheart.playSwagger.Track")
}

"definition properties does not contain 'required' boolean field" >> {
definitionsJson.as[JsObject].values.forall { definition
(definition \ "properties").as[JsObject].values.forall { property
(property \ "required").toOption === None
}
}
}

"definitions exposes 'required' array if there are required properties" >> {
val requiredFields = Seq("name", "artist", "related", "numbers")
(trackJson \ "required").as[Seq[String]] must contain(allOf(requiredFields: _*).exactly)
}

"definitions does not expose 'required' array if there are no required properties" >> {
(allOptionalDefJson \ "required").asOpt[Seq[String]] === None
}

"handle multiple levels of includes" >> {
val tags = (pathJson \ "/level1/level2/level3" \ "get" \ "tags").asOpt[Seq[String]]
tags must beSome.which(_ == Seq("level2"))
Expand Down

0 comments on commit 739beee

Please sign in to comment.