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

Play 2.5 Support #68

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -19,6 +19,7 @@ Scala Versions | Play Version | Swagger Version | swagger-play version
2.9.1, 2.10.4 | 2.2.x | 1.2 | 1.3.7
2.10.4, 2.11.1 | 2.3.x | 1.2 | 1.3.12
2.11.6, 2.11.7 | 2.4.x | 2.0 | 1.5.0
2.11.7 | 2.5.x | 2.0 | 1.6.0

Other Swagger-Play integrations
-------
Expand Down
21 changes: 21 additions & 0 deletions play-2.5/swagger-play2/.gitignore
@@ -0,0 +1,21 @@
ass
*.log

.idea/
.idea_modules/
*.iml

# sbt specific
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/

# Scala-IDE specific
.scala_dependencies
.project
.settings
.classpath
# Scala-IDE specific
129 changes: 129 additions & 0 deletions play-2.5/swagger-play2/README.md
@@ -0,0 +1,129 @@
[![Build Status](https://travis-ci.org/rayyildiz/swagger-play.svg?branch=master)](https://travis-ci.org/rayyildiz/swagger-play)

# Swagger Play2 Module

## Overview
This is a module to support the play2 framework from [playframework](http://www.playframework.org). It is written in scala but can be used with either java or scala-based play2 applications.

## Version History

* swagger-play2 1.5.1 supports play 2.4 and swagger 2.0. If you need swagger 1.2 support, use 1.3.13. If you need 2.2 support, use 1.3.7 or earlier.

* swagger-play2 1.3.13 supports play 2.4. If you need 2.2 support, use 1.3.7 or earlier.

* swagger-play2 1.3.12 supports play 2.3. If you need 2.2 support, use 1.3.7 or earlier.

* swagger-play2 1.3.7 supports play 2.2. If you need 2.1 support, please use 1.3.5 or earlier

* swagger-play2 1.3.6 requires play 2.2.x.

* swagger-play2 1.2.1 and greater support scala 2.10 and play 2.0 and 2.1.

* swagger-play2 1.2.0 support scala 2.9.x and play 2.0, please use 1.2.0.

Usage
-----

You can depend on pre-built libraries in maven central by adding the following dependency:

```
libraryDependencies ++= Seq(
"io.swagger" %% "swagger-play2" % "1.5.1"
)
```

Or you can build from source.

```
cd modules/swagger-play2

sbt publishLocal
```

### Adding Swagger to your Play2 app

There are just a couple steps to integrate your Play2 app with swagger.

1\. Add the Swagger module to your `application.conf`

```
play.modules.enabled += "play.modules.swagger.SwaggerModule"
```

2\. Add the resource listing to your routes file (you can read more about the resource listing [here](https://github.com/swagger-api/swagger-core/wiki/Resource-Listing))

```

GET /swagger.json controllers.ApiHelpController.getResources

```

3\. Annotate your REST endpoints with Swagger annotations. This allows the Swagger framework to create the [api-declaration](https://github.com/swagger-api/swagger-core/wiki/API-Declaration) automatically!

In your controller for, say your "pet" resource:

```scala
@Api(value = "/pet", description = "Operations about pets")
class PetApiController extends BaseApiController {

@ApiOperation(nickname = "getPetById", value = "Find pet by ID", notes = "Returns a pet", response = classOf[models.Pet], httpMethod = "GET")
@ApiResponses(Array(
new ApiResponse(code = 400, message = "Invalid ID supplied"),
new ApiResponse(code = 404, message = "Pet not found")))
def getPetById(
@ApiParam(value = "ID of the pet to fetch") @PathParam("id") id: String) = Action {

...

```

What this does is the following:

* Tells swagger that the methods in this controller should be described under the `/api-docs/pet` path

* The Routes file tells swagger that this API listens to `/{id}`

* Describes the operation as a `GET` with the documentation `Find pet by Id` with more detailed notes `Returns a pet ....`

* Takes the param `id`, which is a datatype `string` and a `path` param

* Returns error codes 400 and 404, with the messages provided

In the routes file, you then wire this api as follows:

```
GET /pet/:id controllers.PetApiController.getPetById(id)
```

This will "attach" the /api-docs/pet api to the swagger resource listing, and the method to the `getPetById` method above

Please note that the minimum configuration needed to have a route/controller be exposed in swagger declaration is to have an `Api` annotation at class level.

#### The ApiParam annotation

Swagger for play has two types of `ApiParam`s--they are `ApiParam` and `ApiImplicitParam`. The distinction is that some
paramaters (variables) are passed to the method implicitly by the framework. ALL body parameters need to be described
with `ApiImplicitParam` annotations. If they are `queryParam`s or `pathParam`s, you can use `ApiParam` annotations.


# application.conf - config options
```
api.version (String) - version of API | default: "beta"
swagger.api.basepath (String) - base url | default: "http://localhost:9000"
swagger.filter (String) - classname of swagger filter | default: empty
swagger.api.info = {
contact : (String) - Contact Information | default : empty,
description : (String) - Description | default : empty,
title : (String) - Title | default : empty,
termsOfService : (String) - Terms Of Service | default : empty,
license : (String) - Terms Of Service | default : empty,
licenseUrl : (String) - Terms Of Service | default : empty
}

## Note on Dependency Injection
This plugin works by default if your application uses Runtime dependency injection.

Nevertheless, a helper is provided `SwaggerApplicationLoader` to ease the use of this plugin with Compile Time Dependency Injection.

```

214 changes: 214 additions & 0 deletions play-2.5/swagger-play2/app/controllers/ApiHelpController.scala
@@ -0,0 +1,214 @@
/**
* 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 controllers

import play.api.http.HttpEntity
import play.api.mvc._
import play.api.Logger
import play.api.libs.iteratee.Enumerator
import play.api.libs.streams.Streams
import play.modules.swagger.ApiListingCache

import javax.xml.bind.annotation._

import java.io.StringWriter

import io.swagger.util.Json
import io.swagger.models.Swagger
import io.swagger.core.filter.SpecFilter
import io.swagger.config.FilterFactory

import scala.collection.JavaConversions._
import scala.collection.JavaConverters._

import akka.stream.scaladsl._
import akka.util.ByteString

object ErrorResponse {
val ERROR = 1
val WARNING = 2
val INFO = 3
val OK = 4
val TOO_BUSY = 5
}

class ErrorResponse(@XmlElement var code: Int, @XmlElement var message: String) {
def this() = this(0, null)

@XmlTransient
def getCode: Int = code

def setCode(code: Int) = this.code = code

def getType: String = code match {
case ErrorResponse.ERROR => "error"
case ErrorResponse.WARNING => "warning"
case ErrorResponse.INFO => "info"
case ErrorResponse.OK => "ok"
case ErrorResponse.TOO_BUSY => "too busy"
case _ => "unknown"
}

def setType(`type`: String) = {}

def getMessage: String = message

def setMessage(message: String) = this.message = message
}

class ApiHelpController extends SwaggerBaseApiController {

def getResources = Action {
request =>
implicit val requestHeader: RequestHeader = request
val host = requestHeader.host
val resourceListing = getResourceListing(host)
val responseStr = returnXml(request) match {
case true => toXmlString(resourceListing)
case false => toJsonString(resourceListing)
}
returnValue(request, responseStr)
}

def getResource(path: String) = Action {
request =>
implicit val requestHeader: RequestHeader = request
val host = requestHeader.host
val apiListing = getApiListing(path, host)
val responseStr = returnXml(request) match {
case true => toXmlString(apiListing)
case false => toJsonString(apiListing)
}
Option(responseStr) match {
case Some(help) => returnValue(request, help)
case None =>
val msg = new ErrorResponse(500, "api listing for path " + path + " not found")
Logger("swagger").error(msg.message)
if (returnXml(request)) {
InternalServerError.chunked(Source.single(toXmlString(msg).getBytes("UTF-8"))).as("application/xml")
} else {
InternalServerError.chunked(Source.single(toJsonString(msg).getBytes("UTF-8"))).as("application/json")
}
}
}
}

class SwaggerBaseApiController extends Controller {

protected def returnXml(request: Request[_]) = request.path.contains(".xml")

protected val AccessControlAllowOrigin = ("Access-Control-Allow-Origin", "*")

/**
* Get a list of all top level resources
*/
protected def getResourceListing(host: String)(implicit requestHeader: RequestHeader) = {
Logger("swagger").debug("ApiHelpInventory.getRootResources")
val docRoot = ""
val queryParams = (for((key, value) <- requestHeader.queryString) yield {
(key, value.toList.asJava)
}).toMap
val cookies = (for(cookie <- requestHeader.cookies) yield {
(cookie.name, cookie.value)
}).toMap
val headers = (for((key, value) <- requestHeader.headers.toMap) yield {
(key, value.toList.asJava)
}).toMap

val f = new SpecFilter
val l: Option[Swagger] = ApiListingCache.listing(docRoot, host)

val specs: Swagger = l match {
case Some(m) => m
case _ => new Swagger()
}

val hasFilter = Option(FilterFactory.getFilter)
hasFilter match {
case Some(filter) => f.filter(specs, FilterFactory.getFilter, queryParams.asJava, cookies, headers)
case None => specs
}


}

/**
* Get detailed API/models for a given resource
*/
protected def getApiListing(resourceName: String, host: String)(implicit requestHeader: RequestHeader) = {
Logger("swagger").debug("ApiHelpInventory.getResource(%s)".format(resourceName))
val docRoot = ""
val f = new SpecFilter
val queryParams = requestHeader.queryString.map {case (key, value) => key -> value.toList.asJava}
val cookies = requestHeader.cookies.map {cookie => cookie.name -> cookie.value}.toMap.asJava
val headers = requestHeader.headers.toMap.map {case (key, value) => key -> value.toList.asJava}
val pathPart = resourceName

val l: Option[Swagger] = ApiListingCache.listing(docRoot, host)
val specs: Swagger = l match {
case Some(m) => m
case _ => new Swagger()
}
val hasFilter = Option(FilterFactory.getFilter)

val clone = hasFilter match {
case Some(filter) => f.filter(specs, FilterFactory.getFilter, queryParams.asJava, cookies, headers)
case None => specs
}
clone.setPaths(clone.getPaths.filterKeys(_.startsWith(pathPart) ))
clone
}

def toXmlString(data: Any): String = {
if (data.getClass.equals(classOf[String])) {
data.asInstanceOf[String]
} else {
val stringWriter = new StringWriter()
stringWriter.toString
}
}

protected def XmlResponse(data: Any) = {
val xmlValue = toXmlString(data)
Ok.chunked(Source.single(xmlValue.getBytes("UTF-8"))).as("application/xml")
}

protected def returnValue(request: Request[_], obj: Any): Result = {
val response = returnXml(request) match {
case true => XmlResponse(obj)
case false => JsonResponse(obj)
}
response.withHeaders(AccessControlAllowOrigin)
}

def toJsonString(data: Any): String = {
if (data.getClass.equals(classOf[String])) {
data.asInstanceOf[String]
} else {
Json.pretty(data.asInstanceOf[AnyRef])
}
}

protected def JsonResponse(data: Any) = {
val jsonBytes = toJsonString(data).getBytes("UTF-8")
val source = Source.single(jsonBytes).map(ByteString.apply)
Result (
header = ResponseHeader(200, Map(CONTENT_LENGTH -> jsonBytes.length.toString)),
body = HttpEntity.Streamed(source, None, None)
).as ("application/json")
}
}