Skip to content

Commit

Permalink
Merge pull request #21 from kailuowang/master
Browse files Browse the repository at this point in the history
Fix issue #20
  • Loading branch information
kailuowang committed Nov 10, 2015
2 parents 1d47232 + 28c722e commit cb39ba7
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 64 deletions.
10 changes: 2 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,16 @@ organization in ThisBuild := "com.iheart"

name := "play-swagger"

version in ThisBuild := "0.1.6-SNAPSHOT"

resolvers += Resolver.bintrayRepo("scalaz", "releases")

scalaVersion in ThisBuild := "2.11.7"

libraryDependencies ++= Dependencies.playJson ++ Dependencies.test ++ Dependencies.yaml

licenses += ("Apache-2.0", url("http://www.apache.org/licenses/"))

bintrayOrganization := Some("iheartradio")

bintrayPackageLabels := Seq("play-framework", "swagger", "play")
Publish.settings

lazy val playSwagger = project in file(".")

coverallsToken := Some("tVYHvi1dwcXx3XzTnEOCLjCneOei9wraz")

scalariformSettings
Format.settings
19 changes: 19 additions & 0 deletions project/Format.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sbt._
import com.typesafe.sbt.SbtScalariform
import com.typesafe.sbt.SbtScalariform.ScalariformKeys

object Format {
lazy val settings = SbtScalariform.scalariformSettings ++ Seq(
ScalariformKeys.preferences in Compile := formattingPreferences,
ScalariformKeys.preferences in Test := formattingPreferences
)

def formattingPreferences = {
import scalariform.formatter.preferences._
FormattingPreferences()
.setPreference(RewriteArrowSymbols, true)
.setPreference(AlignParameters, true)
.setPreference(AlignSingleLineCaseStatements, true)
}

}
23 changes: 23 additions & 0 deletions project/Publish.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sbt._, Keys._
import bintray.BintrayKeys._


object Publish {

val bintraySettings = Seq(
bintrayOrganization := Some("iheartradio"),
bintrayPackageLabels := Seq("play-framework", "swagger", "rest-api", "API", "documentation")
)

val publishingSettings = Seq(
publishMavenStyle := true,
licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0.html")),
homepage := Some(url("http://iheartradio.github.io/play-swagger")),
scmInfo := Some(ScmInfo(url("https://github.com/iheartradio/play-swagger"),
"git@github.com:iheartradio/play-swagger.git")),
pomIncludeRepository := { _ => false },
publishArtifact in Test := false
)

val settings = bintraySettings ++ publishingSettings
}
3 changes: 3 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0")

addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.1")

addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.0.4")

addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.4.0")

addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.0.0")

22 changes: 12 additions & 10 deletions src/main/scala/com/iheart/playSwagger/Domain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ object Domain {
type Method = String

case class Definition(
name: String,
properties: Seq[SwaggerParameter],
description: Option[String] = None
name: String,
properties: Seq[SwaggerParameter],
description: Option[String] = None
)

case class SwaggerParameter(
name: String,
`type`: Option[String] = None,
format: Option[String] = None,
required: Boolean = true,
example: Option[JsValue] = None,
referenceType: Option[String] = None,
items: Option[String] = None
name: String,
`type`: Option[String] = None,
format: Option[String] = None,
required: Boolean = true,
default: Option[JsValue] = None,
example: Option[JsValue] = None,
referenceType: Option[String] = None,
items: Option[String] = None

)
}

74 changes: 45 additions & 29 deletions src/main/scala/com/iheart/playSwagger/SwaggerParameterMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,62 @@ package com.iheart.playSwagger

import com.iheart.playSwagger.Domain.SwaggerParameter
import org.joda.time.DateTime
import play.api.libs.json.JsString
import play.api.libs.json.{ JsBoolean, JsNumber, JsValue, JsString }

object SwaggerParameterMapper {
def mapParam(name: String, scalaTypeName: String, domainNameSpace: Option[String] = None): SwaggerParameter = {
def mapParam(name: String, typeAndOrDefaultValue: String, domainNameSpace: Option[String] = None): SwaggerParameter = {

def higherOrderType(higherOrder: String, typeName: String): Option[String] = s"$higherOrder\\[(\\S+)\\]".r.findFirstMatchIn(typeName).map(_.group(1))

def collectionItemType(typeName: String): Option[String] =
List("Seq", "List", "Set", "Vector").map(higherOrderType(_, typeName)).reduce(_ orElse _)

def prop(tp: String, format: Option[String] = None, required: Boolean = true) =
def swaggerParam(tp: String, format: Option[String] = None, required: Boolean = true) =
SwaggerParameter(name, `type` = Some(tp), format = format, required = required)

val typeName = scalaTypeName.replace("scala.", "").replace("java.lang.", "")

def isReference(tpeName: String): Boolean = domainNameSpace.fold(false)(tpeName.startsWith(_))

if (isReference(typeName))
SwaggerParameter(name, referenceType = Some(typeName))
else {
val optionalType = higherOrderType("Option", typeName)
val itemType = collectionItemType(typeName)
if (itemType.isDefined)
SwaggerParameter(name, items = itemType)
else if (optionalType.isDefined)
(if (isReference(optionalType.get))
SwaggerParameter(name, referenceType = optionalType)
else
mapParam(name, optionalType.get)).copy(required = false)
else
typeName match {
case "Int" prop("integer", Some("int32"))
case "Long" prop("integer", Some("int64"))
case "Double" prop("number", Some("double"))
case "Float" prop("number", Some("float"))
case "org.jodaTime.DateTime" prop("integer", Some("epoch"))
case "Any" prop("any").copy(example = Some(JsString("any JSON value")))
case unknown prop(unknown.toLowerCase())
}
val parts = typeAndOrDefaultValue.split("\\?=")

val typePart = parts.head.stripMargin
val typeName = typePart.replace("scala.", "").replace("java.lang.", "")

val defaultValueO: Option[JsValue] = {
if (parts.length == 2) {
val stringVal = parts.last.stripMargin
Some(typeName match {
case "Int" | "Long" JsNumber(stringVal.toLong)
case "Double" | "Float" JsNumber(stringVal.toDouble)
case "Boolean" JsBoolean(stringVal.toBoolean)
case _ JsString(stringVal)
})
} else None
}

def isReference(tpeName: String = typeName): Boolean = domainNameSpace.fold(false)(tpeName.startsWith(_))
lazy val optionalTypeO = higherOrderType("Option", typeName)
lazy val itemTypeO = collectionItemType(typeName)

def referenceParam(referenceType: String) =
SwaggerParameter(name, referenceType = Some(referenceType))

def optionalParam(optionalTpe: String) = {
val param = (if (isReference(optionalTpe)) referenceParam(optionalTpe) else mapParam(name, optionalTpe))
param.copy(required = false, default = defaultValueO)
}

def generalParam =
(typeName match {
case "Int" swaggerParam("integer", Some("int32"))
case "Long" swaggerParam("integer", Some("int64"))
case "Double" swaggerParam("number", Some("double"))
case "Float" swaggerParam("number", Some("float"))
case "org.jodaTime.DateTime" swaggerParam("integer", Some("epoch"))
case "Any" swaggerParam("any").copy(example = Some(JsString("any JSON value")))
case unknown swaggerParam(unknown.toLowerCase())
}).copy(default = defaultValueO, required = defaultValueO.isEmpty)

if (isReference()) referenceParam(typeName)
else if (itemTypeO.isDefined) SwaggerParameter(name, items = itemTypeO)
else if (optionalTypeO.isDefined) optionalParam(optionalTypeO.get)
else generalParam
}
}
21 changes: 9 additions & 12 deletions src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP

import SwaggerSpecGenerator.marker

private implicit def toOWrites[A](writes: Writes[A]): OWrites[A] = OWrites {
writes.writes(_).asInstanceOf[JsObject]
}

private val referencePrefix = "#/definitions/"

private val refWrite = OWrites((refType: String) Json.obj("$ref" JsString(referencePrefix + refType)))
Expand All @@ -43,6 +39,7 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP
(__ \ 'type).writeNullable[String] ~
(__ \ 'format).writeNullable[String] ~
(__ \ 'required).write[Boolean] ~
(__ \ 'default).writeNullable[JsValue] ~
(__ \ 'example).writeNullable[JsValue] ~
(__ \ "schema").writeNullable[String](refWrite) ~
itemsWrite
Expand All @@ -67,8 +64,8 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP

private[playSwagger] def generateWithBase(
routesDocumentation: RoutesDocumentation,
routesLines: Map[Tag, List[Line]],
baseJson: JsObject = Json.obj()
routesLines: Map[Tag, List[Line]],
baseJson: JsObject = Json.obj()
): JsObject = {

val pathsJson = routesLines.map {
Expand Down Expand Up @@ -108,8 +105,8 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP
try {
val ext = url.getFile.split("\\.").last
ext match {
case "json" Json.parse(st).asInstanceOf[JsObject]
case "yml" parseYaml(read(st).mkString("\n"))
case "json" Json.parse(st).asInstanceOf[JsObject]
case "yml" parseYaml(read(st).mkString("\n"))
case unknown throw new IllegalArgumentException(s"$name has an unsupported extension. Use either json or yaml. ")
}
} finally {
Expand Down Expand Up @@ -152,7 +149,7 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP

val commentDocLines = commentLines match {
case `marker` +: docs :+ `marker` docs
case _ Nil
case _ Nil
}

val paramsFromController = {
Expand All @@ -161,7 +158,7 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP
val paramsPattern = "\\((.+)\\)$".r

JsArray(paramsPattern.findFirstMatchIn(controllerDesc).map(_.group(1)).fold(Array[SwaggerParameter]()) { paramsString
paramsString.split(",").map { param =>
paramsString.split(",").map { param
val Array(name, pType) = param.split(":")
mapParam(name, pType, domainNameSpace)
}
Expand Down Expand Up @@ -203,7 +200,7 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP
val controllerDesc = condense(cleanUp(controllerRaw))
val controllerMethodPath = methodPath(controllerDesc).get

val beforeRouteEntry = allRoutes.takeWhile { l =>
val beforeRouteEntry = allRoutes.takeWhile { l
methodPath(cleanUp(l)).map(condense).fold(true)(_ != controllerMethodPath)
}

Expand All @@ -215,7 +212,7 @@ case class SwaggerSpecGenerator(domainNameSpace: Option[String] = None, defaultP
None
else {
val path = rawPath.replaceAll("""\$(\w+)<[^>]+>""", "{$1}")
Some(path Json.obj(method.toLowerCase -> endPointSpec(controllerDesc, commentLines, path)))
Some(path Json.obj(method.toLowerCase endPointSpec(controllerDesc, commentLines, path)))
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/test/resources/api.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
###
# summary: get player
###
GET /player/:pid/context/:bid controllers.Player.getPlayer(pid, bid)

GET /player/:pid/tracks/search controllers.Player.searchTrack(pid, keyword)

###
# parameters:
# - name: body
# description: track information
# schema:
# $ref: '#/definitions/com.iheart.playSwagger.Track'
###
POST /player/:pid/playedTracks controllers.Player.addPlayedTracks(pid)
4 changes: 4 additions & 0 deletions src/test/resources/doc.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
GET /api/resource/ controllers.Resource.get()
PUT /api/resource/ controllers.Resource.put()
POST /api/resource/ controllers.Resource.post()
DELETE /api/resource/ controllers.Resource.delete()
2 changes: 2 additions & 0 deletions src/test/resources/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-> /api api.Routes
-> /doc doc.Routes
17 changes: 17 additions & 0 deletions src/test/scala/com/iheart/playSwagger/RoutesFileReaderSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.iheart.playSwagger

import org.specs2.mutable.Specification

class RoutesFileReaderSpec extends Specification {

"Reads" should {
"read all files" in {
implicit val cl = getClass.getClassLoader
val results = RoutesFileReader().readAll()
results.keys must contain(allOf("api", "doc"))
results("api").size must be_>(2)
results("doc") must contain("POST /api/resource/ controllers.Resource.post()")
}
}

}

0 comments on commit cb39ba7

Please sign in to comment.