Skip to content

evolution-gaming/akka-http-documenteddsl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AKKA-HTTP-DOCUMENTEDDSL

CI Coverage Status version

The Problem

Want to provide Swaggerrish api documentation of your app? Or RAML?

Spray and Akka-Http both have perfect routing dsl and at the first sight it will be easy to extract the data to build something like swagger on top of it. But looking deeper you see that these was designed being not introspective. In short you are unable to distinguish one directive from another in runtime (or in compile time using macros) and therefore you can't build the picture of your route behaviour.

So not Spray nor Akka-Http have any mechanisms to extract documentation from their routing dsl.

Provided Solution

For our needs we selected number of directives which could form the api documentation and wrapped them into neatly documented directives. We then added a few of our own directives to make us able to describe specifics of our apis and now we are able to generate some internally invented format describing our api. It is not a Swagger or a Raml, it is something simpler but still rich enough to provide users with all the needed information about api. Generated documentation then could be available in json form, and/or presented via html5 frontend.

Introduction

To use documented directives you need to import couple things.

import akka.http.documenteddsl.DDirectives._

This is an entry point to documented dsl.

Next important thing - you need to have org.coursera.autoschema.AutoSchema which could be accessed implicitly in your scope.

There are number of documented directives:

  • Method directives
    • GET
    • POST
    • PUT
    • DELETE
    • HEAD
    • OPTIONS
  • Path directives
    • Path
  • Header directives
    • Header
    • OptHeader
  • Parameter directives
    • Param
    • OptParam
    • DefaultParam
  • Form directives
    • FormField
    • OptFormField
    • DefaultFormField
  • Marshalling directives
    • In
  • Unmarshalling directives
    • Out
  • Session directives
    • Session
  • Documentation directives
    • Category
    • Title
    • Description

Here are some primitive example of how your code may look like if using documented dsl.

implicit object autoSchema extends AutoSchema with DocumentedTypeMappings

private val Find    = Category("Api", "Resource") & Title("Find") & Description("Returns specified resource entrie") &
                      Path(Segment[String]) & GET &
                      Out[ExampleResource] & Out(StatusCodes.NotFound, "Resource not found")

private val FindAll = Category("Api", "Resource") & Title("Find All") & Description("Returns all resource entries") &
                      GET &
                      Out[Set[ExampleResource]]

private val Create  = Category("Api", "Resource") & Title("Create") & Description("Creates a new resource entry") &
                      POST &
                      In(CreateExample) & Out[ExampleResource]

private val Update  = Category("Api", "Resource") & Title("Update") & Description("Updates specified resource entry") &
                      Path(Segment[String]) & PUT &
                      In(UpdateExample) & Out[ExampleResource] & Out(StatusCodes.NotFound, "Resource not found")

private val Delete  = Category("Api", "Resource") & Title("Delete") & Description("Deletes specified resource entry") &
                      Path(Segment[String]) & DELETE &
                      Out[ExampleResource] & Out(StatusCodes.NotFound, "Resource not found")

lazy val route: DRoute = PathPrefix("resources") {
  Find    {find} |~|
  FindAll {complete(collection)} |~|
  Create  {create} |~|
  Update  {update} |~|
  Delete  {delete}
}

import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._

private def find(id: String): Route = ???
private def create(payload: CreateResource): Route = ???
private def update(id: String, payload: UpdateResource): Route = ???
private def delete(id: String): Route = ???

And here is the documentation generated from this example

GET http://localhost:8080/api.json

{
  "routes" : [ {
    "uid" : "14906C3B966588C5BAD6B46E",
    "method" : "GET",
    "path" : "resources",
    "out" : {
      "success" : [ {
        "status" : {
          "code" : 200,
          "detail" : "OK"
        },
        "contentType" : "application/json",
        "schema" : {
          "type" : "array",
          "items" : {
            "title" : "ExampleResource",
            "type" : "object",
            "required" : [ "name", "id" ],
            "properties" : {
              "description" : {
                "type" : "string"
              },
              "id" : {
                "type" : "string"
              },
              "name" : {
                "type" : "string"
              }
            }
          }
        }
      } ],
      "failure" : [ ]
    },
    "title" : "Find All",
    "description" : "Returns all resource entries",
    "category" : [ "Api", "Resource" ]
  }, {...} ]
}

Default DocumentationRoutes also provide the way to decouple routes requests. You use OPTIONS to request api toc. And then you can request route by route using uids.

OPTIONS http://localhost:8080/api.json

{
  "Api": {
    "Resource": {
      "uid": {
        "14906C3B8B40240130BFA42E": "Delete",
        "14906C3B95EB76C2E1EA5DEE": "Update",
        "14906C3B96287FC336629FAE": "Create",
        "14906C3B965646849338300E": "Find",
        "14906C3B966588C5BAD6B46E": "Find All"
      }
    }
  }
}

GET http://localhost:8080/api.json/14906C3B8B40240130BFA42E

{
  "uid" : "14906C3B966588C5BAD6B46E",
  "method" : "GET",
  "path" : "resources",
  "out" : {
    "success" : [ {
      "status" : {
        "code" : 200,
        "detail" : "OK"
      },
      "contentType" : "application/json",
      "schema" : {
        "type" : "array",
        "items" : {
          "title" : "ExampleResource",
          "type" : "object",
          "required" : [ "name", "id" ],
          "properties" : {
            "description" : {
              "type" : "string"
            },
            "id" : {
              "type" : "string"
            },
            "name" : {
              "type" : "string"
            }
          }
        }
      }
    } ],
    "failure" : [ ]
  },
  "title" : "Find All",
  "description" : "Returns all resource entries",
  "category" : [ "Api", "Resource" ]
}

Project contain small api example built on documented directives to show you how does it look like. Please take a look at it.

Known issues

  • Only Play Json supported now
  • Your payloads always treated as json payloads
  • Lack of ability to describe route hierarchically (it is possible only for Directive0, so you still can prefix your routes)

Setup

Sbt

resolvers += Resolver.bintrayRepo("evolutiongaming", "maven")
libraryDependencies += "com.evolutiongaming" %% "akka-http-documenteddsl" % "_latestVersion"