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

Commit

Permalink
Allow sub-routes
Browse files Browse the repository at this point in the history
  • Loading branch information
François LAROCHE committed Feb 14, 2017
1 parent 5111816 commit 0787941
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 54 deletions.
121 changes: 67 additions & 54 deletions play-2.5/swagger-play2/app/play/modules/swagger/SwaggerPlugin.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
/**
* Copyright 2014 Reverb Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright 2014 Reverb Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package play.modules.swagger

import java.io.File
import javax.inject.Inject

import io.swagger.config.{FilterFactory, ScannerFactory}
import play.modules.swagger.util.SwaggerContext
import io.swagger.core.filter.SwaggerSpecFilter
import play.api.inject.ApplicationLifecycle
import play.api.{Logger, Application}
import play.api.routing.Router
import scala.concurrent.Future
import scala.collection.JavaConversions._
import play.routes.compiler.{Route => PlayRoute, Include => PlayInclude, RoutesFileParser, StaticPart}
import play.api.{Application, Logger}
import play.modules.swagger.util.SwaggerContext
import play.routes.compiler.{RoutesFileParser, StaticPart, Include => PlayInclude, Route => PlayRoute}

import scala.collection.JavaConversions._
import scala.concurrent.Future
import scala.io.Source

trait SwaggerPlugin
Expand All @@ -54,33 +55,33 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route

val title = config.getString("swagger.api.info.title") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val description = config.getString("swagger.api.info.description") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val termsOfServiceUrl = config.getString("swagger.api.info.termsOfServiceUrl") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val contact = config.getString("swagger.api.info.contact") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val license = config.getString("swagger.api.info.license") match {
case None => ""
case Some(value)=> value
case Some(value) => value
}

val licenseUrl = config.getString("swagger.api.info.licenseUrl") match {
// licenceUrl needs to be a valid URL to validate against schema
case None => "http://licenseUrl"
case Some(value)=> value
case Some(value) => value
}

SwaggerContext.registerClassLoader(app.classloader)
Expand All @@ -106,44 +107,23 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route
val routes = parseRoutes

def parseRoutes: List[PlayRoute] = {
def playRoutesClassNameToFileName(className: String) = className.replace(".Routes", ".routes")

val routesFile = config.underlying.hasPath("play.http.router") match {
case false => "routes"
case true => config.getString("play.http.router") match {
case None => "routes"
case Some(value)=> playRoutesClassNameToFileName(value)
case Some(value) => SwaggerPluginHelper.playRoutesClassNameToFileName(value)
}
}
//Parses multiple route files recursively
def parseRoutesHelper(routesFile: String, prefix: String): List[PlayRoute] = {
logger.debug(s"Processing route file '$routesFile' with prefix '$prefix'")

val routesContent = Source.fromInputStream(app.classloader.getResourceAsStream(routesFile)).mkString
val parsedRoutes = RoutesFileParser.parseContent(routesContent,new File(routesFile))
val routes = parsedRoutes.right.get.collect {
case (route: PlayRoute) => {
logger.debug(s"Adding route '$route'")
Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
}
case (include: PlayInclude) => {
logger.debug(s"Processing route include $include")
parseRoutesHelper(playRoutesClassNameToFileName(include.router), include.prefix)
}
}.flatten
logger.debug(s"Finished processing route file '$routesFile'")
routes
}
parseRoutesHelper(routesFile, "")

SwaggerPluginHelper.parseRoutes(routesFile, "", logger.debug(_), app.classloader)
}

val routesRules = Map(routes map
{ route =>
{
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
(routeName -> route)
}
} : _*)
val routesRules = Map(routes map { route => {
val routeName = s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}"
(routeName -> route)
}
}: _*)

val route = new RouteWrapper(routesRules)
RouteFactory.setRoute(route)
Expand Down Expand Up @@ -174,3 +154,36 @@ class SwaggerPluginImpl @Inject()(lifecycle: ApplicationLifecycle, router: Route
}

}

object SwaggerPluginHelper {
def playRoutesClassNameToFileName(className: String): String = className.replace(".Routes", ".routes")

//Parses multiple route files recursively
def parseRoutes(routesFile: String, prefix: String, debug: String => Unit, classLoader: ClassLoader): List[PlayRoute] = {
debug(s"Processing route file '$routesFile' with prefix '$prefix'")

val routesContent = Source.fromInputStream(classLoader.getResourceAsStream(routesFile)).mkString
val parsedRoutes = RoutesFileParser.parseContent(routesContent, new File(routesFile))
val routes = parsedRoutes.right.get.collect {
case (route: PlayRoute) =>
debug(s"Adding route '$route'")
(prefix, route.path.parts) match {
case ("", _) => Seq(route)
case (_, Seq()) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, Seq(StaticPart(""))) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, Seq(StaticPart("/"))) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: route.path.parts)))
case (_, _) => Seq(route.copy(path = route.path.copy(parts = StaticPart(prefix) +: StaticPart("/") +: route.path.parts)))
}
case (include: PlayInclude) =>
debug(s"Processing route include $include")
val newPrefix = if(prefix == "") {
include.prefix
} else {
s"$prefix/${include.prefix}"
}
parseRoutes(playRoutesClassNameToFileName(include.router), newPrefix, debug, classLoader)
}.flatten
debug(s"Finished processing route file '$routesFile'")
routes
}
}
48 changes: 48 additions & 0 deletions play-2.5/swagger-play2/test/PlayDelegatedApiScannerSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import io.swagger.config.ScannerFactory
import org.specs2.mock.Mockito
import org.specs2.mutable._
import play.modules.swagger._
import play.routes.compiler.Route

import scala.collection.JavaConverters._

class PlayDelegatedApiScannerSpec extends Specification with Mockito {

val routes: List[Route] = play.modules.swagger.SwaggerPluginHelper.parseRoutes(
"delegation",
"",
_ => {},
Thread.currentThread().getContextClassLoader)


val routesRules: Map[String, Route] = Map(routes.map { route: Route =>
s"${route.call.packageName}.${route.call.controller}$$.${route.call.method}" -> route
}: _*)


val swaggerConfig = new PlaySwaggerConfig()
swaggerConfig.setBasePath("")
swaggerConfig.setHost("127.0.0.1")

PlayConfigFactory.setConfig(swaggerConfig)

var scanner = new PlayApiScanner()
ScannerFactory.setScanner(scanner)
val route = new RouteWrapper(routesRules.asJava)
RouteFactory.setRoute(route)


"route parsing" should {
"separate delegated paths correctly" in {

val urls = ApiListingCache.listing("", "127.0.0.1").get.getPaths.keySet().asScala

urls must contain("/api/all")
urls must contain("/api/my/action")
urls must contain("/api/subdelegated/all")
urls must contain("/api/subdelegated/my/action")
urls must contain("/api/subdelegated")
}
}

}
4 changes: 4 additions & 0 deletions play-2.5/swagger-play2/test/delegated.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-> /subdelegated subdelegated.Routes

GET /my/action testdata.DelegatedController.list
GET /all testdata.DelegatedController.list2
1 change: 1 addition & 0 deletions play-2.5/swagger-play2/test/delegation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-> /api delegated.Routes
3 changes: 3 additions & 0 deletions play-2.5/swagger-play2/test/subdelegated.routes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GET /my/action testdata.DelegatedController.list3
GET /all testdata.DelegatedController.list4
GET / testdata.DelegatedController.list5
24 changes: 24 additions & 0 deletions play-2.5/swagger-play2/test/testdata/DelegatedController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package testdata

import io.swagger.annotations.{Api, ApiOperation}
import play.api.mvc.{Action, Controller}

@Api
object DelegatedController extends Controller {

@ApiOperation(value = "list")
def list = Action { _ => Ok("test case")}

@ApiOperation(value = "list2")
def list2 = Action { _ => Ok("test case")}

@ApiOperation(value = "list3")
def list3 = Action { _ => Ok("test case")}

@ApiOperation(value = "list4")
def list4 = Action { _ => Ok("test case")}

@ApiOperation(value = "list5")
def list5 = Action { _ => Ok("test case")}

}

0 comments on commit 0787941

Please sign in to comment.