Swagger with Play: All you need to know [Potentially outdated]

Saheb Motiani
8 min readMar 20, 2017

--

Swagger is a fancy tool (combination of libraries), which generates beautiful documentation for your REST APIs. All you need to do is add some annotations to your code, okay, more than some, in fact, many at times if you want the documentation to be perfect and self-explanatory!

I prefer to use Swagger over Postman collections because once you invest some time into adding annotations, you don’t need to worry about the inconsistency between code and documentation because documentation is generated from the current code. More importantly, you don’t need another tool installed (as in the case of Postman or Terminal/Console to use curl Command Line Utility), all you need is a browser and to go to ServiceURL/docs/. This helps when you wish to demo your end points and also helps the integrating team to communicate any issues clearly.

It was straight forward to use it with Jax RS (An annotation based Java Rest Web Service Framework), but there were some complications using it with Play and hence I decided to write this post answering some of the questions which I found difficult to resolve.

Which Swagger Library to use

“Swagger-play” if you want to integrate using annotations and generate a JSON file which acts as input to Swagger UI

Simply adding this dependency to your build.sbt should work. "io.swagger" %% "swagger-play2" % "1.5.3"

This version is compatible with Play 2.5.x, earlier versions will give errors. You might not find this version identified in the Readme of the library but it is available on Maven Central to use.

How to use Swagger UI and where to place it

Swagger-Play will generate the input JSON file for your Rest API Specification, but you need another tool to view the documentation and play with the requests. So let us download Swagger UI and add it to our codebase from here. Place it in the public folder of your play application.

I didn’t like this way of using Swagger UI — including third party code in my project’s code base. I tried to find a hack around it but was unsuccessful. It screwed up my project language attributes and made my Scala project appear as a JavaScript project, which I was able to work around by adding a .gitattributes file to the project.

Contents of the .gitattributes file:

public/swagger-ui/* linguist-vendored

I know this is a small thing, but a lot of developers (myself included) care about these details.

Edit: How to bake swagger-ui into the build process instead of copy pasting the dist folder.

Firstly, add the library dependencies to your build.sbt along with play webjar module.

"org.webjars" %% "webjars-play" % "2.5.0-4",
"org.webjars" % "swagger-ui" % "2.2.0"

Secondly, to redirect to your own swagger.json. Add this method do your Application Controller and route to make the docs accessible.

def redirectDocs = Action {
Redirect(url = "/assets/lib/swagger-ui/index.html", queryString = Map("url" -> Seq("/swagger.json")))
}
1GET /docs/ controllers.Application.redirectDocs

Configuration changes needed to make it work

You’ve got the Swagger libraries, but still, nothing will work, because we haven’t added any configuration to your Play application. So now is the time to do that and see if everything is working.

Enable the Swagger Module by adding the below line in your application.conf:

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

Add the following routes to your routes file:

# Swagger API
GET /swagger.json controllers.ApiHelpController.getResources
GET /docs/ controllers.Assets.at(path="/public/swagger-ui",file="index.html")
GET /docs/*file controllers.Assets.at(path="/public/swagger-ui",file)

The first route is to view the JSON generated by the Swagger-Play library and the other two routes are used to view the Swagger UI.

You should see output similar to the below snippet when you go to /swagger.json:

{
"swagger" : "2.0",
"info" : {
"version" : "beta",
"title" : "",
"contact" : {
"name" : ""
},
"license" : {
"name" : "",
"url" : "http://licenseUrl"
}
},
"host" : "localhost:9000",
"basePath" : "/",
"tags" : [ {
"name" : "Reservation"
}, {
"name" : "Resource"
} ],
"paths" : {.....}
}

This should work independently of Swagger UI. Now let us feed this JSON as input to the Swagger UI.

Copy the dist folder from Swagger UI project, paste it in your app’s public folder and rename it to swagger-ui. (Just a convention, you can let it be dist, just be sure to name your routes accordingly).

If you go to /docs/ you should see the Swagger UI loaded with petstore API JSON, which is selected by default. You can either paste the path to your swagger.json in the text box or modify index.html to use your route as the default.

(Note: You can use the local Swagger UI to view and hit any API if you have the link to its swagger.json)

You have to replace the URL which Swagger UI uses by default to fetch swagger JSON by editing the index.html in the dist folder. Change the URL to /swagger.json.

What is the bare minimum annotation required?

@Api at the class level which you want to expose using Swagger. It will use paths information from the routes file. By default everything goes under the default namespace.

object Resource {
implicit val resourceWrites: Writes[Resource] = Json.writes[Resource]
implicit val resourceReads: Reads[Resource] = Json.reads[Resource]
}
case class Resource(name: String, available: Boolean)
@Api
class ResourceController extends Controller {
def availableResources = Action {
Ok(Json.toJson(Resource("Printer", true)))
}
}

Creaing resource specific APIs in separate namespaces

By default every end point goes to default namespace, but REST is resource based and we would like to see namespaces separated by Resource names. To achieve that, you can do the following;

Pass the resource namespace arg value to the annotation @Api("Resource").

You can place the same Resource in multiple files if they grow large, swagger will ensure they are correctly classified according to the namespace arg.

How to avoid creating duplicates or subsets of your models to control the representation of them in the JSON schema

This is a more general problem when dealing with different creation, update and querying models. You can either figure out if some version of CQRS strategy works for you or just use the following simple hack/technique, which works for simple cases.

case class Reservation(id: Option[Int], resourceName: String, start: Timestamp, end: Timestamp)@Api
class ReservationController extends Controller {
def reserveResource = Action(BodyParsers.parse.json[Reservation]) { request =>
val reservation = request.body
// Persist and return Reservation with ID
val persistedReservation = reservation.copy(id = Some(1))
Ok(Json.toJson(persistedReservation))
}
}

Create Reservation Json (Request):

{
"resourceName": "Printer",
"start": 1489065609,
"end": 1489069209
}

Query Reservation Json (Response):

{
"id": 1,
"resourceName": "Printer",
"start": 1489065609,
"end": 1489069209
}

Making the Swagger UI body editable for POST requests

Most often than not we take the request body (JSON) and deserialze it to create a model instance. We can ensure this process is smooth by adding the annotations as shown below. This will help us with 2 things. Firstly, it will prevent you from writing model yourself and use the case class model definition directly. Secondly, it will create a form for the users which will enable them to only add values and not worry about key or structure of the body.

Use @ApiImplicitParams:

@Api("Reservation")
class ReservationController extends Controller {
@ApiImplicitParams(Array(
new ApiImplicitParam(
value = "Make Reservation for a resource",
required = true,
dataType = "controllers.Reservation", // complete path
paramType = "body"
)
))
def reserveResource = Action(BodyParsers.parse.json[Reservation]) { request =>
val reservation = request.body
//persist and return Reservation with ID
val persistedReservation = reservation.copy(id = Some(1))
Ok(Json.toJson(persistedReservation))
}
}

This would have just worked fine, if all the fields of Reservation were strings and all the fields were coming from the request JSON. But we have an id field which is tricky to deal with: ( We don’t want it to come from request, but it is part of the Reservation Model )

Customise the field type (change the datatype or default value) of Request JSON fields

Use @ApiModelProperty:

case class Reservation(id: Option[Int],
@ApiModelProperty(value = "Name of the resource") resourceName: String,
@ApiModelProperty(value = "Start in epoch seconds", dataType = "Long", example = "1488800328") start: Timestamp,
@ApiModelProperty(value = "End in epoch seconds", dataType = "Long", example = "1488800328") end: Timestamp)

This would have worked if you did want id to be present in in the request body, but if you don’t want the user to see that field, we will need to do something more to hide this field from the Swagger UI.

Hide/exclude fields from the Swagger Docs

This was tricky to figure out, but it is really important in case you want to hide some fields in the Swagger Schema. We don’t want to create a copy of the model just because we don’t want to display a couple or fields. More importantly, it doesn’t make sense to have redundant code just to support the documentation purposes.

Swagger has something called SpecFilter for this task. You just need to provide an implementation with appropriate logic. Here is an example I wrote which hides all id fields:

class SwaggerConfigurationFilter extends SwaggerSpecFilter {  override def isParamAllowed(
parameter: Parameter,
operation: Operation,
api: ApiDescription,
params: util.Map[String, util.List[String]],
cookies: util.Map[String, String],
headers: util.Map[String, util.List[String]]
): Boolean = true
override def isPropertyAllowed(
model: Model,
property: Property,
propertyName: String,
params: util.Map[String, util.List[String]],
cookies: util.Map[String, String],
headers: util.Map[String, util.List[String]]
): Boolean = if (property.getName == "id") false else true
override def isOperationAllowed(
operation: Operation,
api: ApiDescription,
params: util.Map[String, util.List[String]],
cookies: util.Map[String, String],
headers: util.Map[String, util.List[String]]
): Boolean = true
}

Where to place the CustomFilter File and how to configure it

You can place it anywhere but a good place would be to place alongside other Configuration Modules like GuiceConfigurationModule.

To wire-it up, add a line like this to your application.conf (the value should be the FQCN):

swagger.filter = "SwaggerConfigurationFilter"

Conclusion

  • We learned how to integrate any Play application with Swagger, which libraries to use to get it up and running quickly.
  • We now know the specific config changes required to make it work and how Swagger UI and the generated swagger json work together.
  • We have a clear idea about the annotations required and how we can use them to customize the docs for the end user experience.
  • We also figured out a way to prevent creation of duplicates and subsets of model instances in our codebase.
  • We were also able to extend SwaggerSpecFilter and provide custom filters to control the process of swagger json generation.

Additional Resources

Gist — https://gist.github.com/Saheb/dd11fa2ece946e743d95d126685d6f7c#file-swaggerwithplay-md

Code Repo — https://github.com/Saheb/SwaggerPlayDemo

Thanks to David, Jaakko and Qing for reviewing it.

— Saheb Motiani

--

--

Saheb Motiani
Saheb Motiani

Written by Saheb Motiani

A writer in progress | A programmer for a living | And an amateur poker player