Skip to content

Instantly share code, notes, and snippets.

@Saheb

Saheb/Data type Change.png Secret

Last active Aug 13, 2017
Embed
What would you like to do?
Swagger with Play: All you need to know

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 https://github.com/swagger-api/swagger-play

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.

What configuration changes are 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.

Default Swagger UI

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)))
  }
}

How to separate resource specific APIs despite having them in different Controller files

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 same Resource in multiple files if they grow larger, 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
}

How to make the Swagger UI body editable when making POST requests

By default you can't edit the body, to change that, and to ensure the body (JSON) is what it is expected to be on the server side, add the below annotations and it will create a form to fill in the values of the fields.

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))
  }
}

Post Editable Swagger

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 )

How to 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.

Data Type Change Swagger

How to 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 without having to create a copy of the model without those fields.

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
}

Swagger Final

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"

Additional Resources

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment