Generate Swagger / OpenAPI specification from scala source code (http4s)

Viewed 3543

So I'm no swagger expert, but all systems using swagger require you to have the swagger specification in JSON or YAML defining all endpoints (and such) of your API.

My question is: Are there know ways to generate these specification files based on the actual source code? I'm asking, because it seems very hard to keep endpoint code & documentation in sync when you start adding properties or returning slightly different results.

So when I have this code (using http4s & RhoService):

object HelloWorld {
  val service = new RhoService {
    GET / "hello" / 'name |>> { name: String =>
      Ok(Json.obj("message" -> Json.fromString(s"Hello, ${name}")))
    }
  }
}

It would be great if it could produce (in some way:)

/hello/{name}:
    get:
      tags:
      - "HelloWorld"
      summary: ""
      description: ""
      operationId: "getHellobyName"
      produces:         
      - "application/json"
      parameters:
      - name: "name"
        in: "path"
        description: ""
        required: true
        type: "string"
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/Hello"           
      security:
      - api_key: []
3 Answers

Disclaimer: I'm the author of tapir.

rho is one possibility. Another approach is to completely separate the description of the API endpoint from the business logic.

Having a description of an endpoint, which is a regular Scala value, it can be then interpreted as either a server (given the "business logic" function), or documentation.

Two Scala libraries that can provide both http4s and OpenAPI interpreters are tapir and typeapi.

Disclaimer: I’m the author of endpoints4s.

Similar to tapir (mentioned in another answer) endpoints4s is a library that can produce http4s servers and OpenAPI documentation for HTTP endpoints.

You would write your example like the following:

// --- This is the description of your HTTP service

import endpoints4s.algebra

case class Hello(message: String)

trait HelloWorldEndpoints
  extends algebra.Endpoints
    with algebra.JsonEntitiesFromSchemas {

  implicit val helloJsonSchema: JsonSchema[Hello] =
    field[String]("message")
     .xmap(message => Hello(message))(hello => hello.message)

  val hello: Endpoint[String, Hello] =
    endpoint(
      get(path / "hello" / segment[String]("name")),
      ok(jsonResponse[Hello])
    )

}

// --- This is an OpenApi documentation of the endpoints defined
// --- in HelloWorldEndpoints

import endpoints4s.openapi
import endpoints4s.openapi.model.{ Info, OpenApi }

object HelloWorldDocumentation
  extends HelloWorldEndpoints
    with openapi.Endpoints
    with openapi.JsonEntitiesFromSchemas {

  val api: OpenApi =
    openApi(Info(title = "Hello API", version = "1.0"))(
      hello
    )

}

// --- This is an http4s server that implements the endpoints defined
// --- in HelloWorldEndpoints

import endpoints4s.http4s
import cats.effect.IO
import org.http4s.HttpRoutes

object HelloWorld
  extends http4s.server.Endpoints[IO]
    with http4s.server.JsonEntitiesFromSchemas
    with HelloWorldEndpoints {

  val service: HttpRoutes[IO] = HttpRoutes.of(
    routesFromEndpoints(
      hello.implementedBy(name => Hello(s"Hello, ${name}"))
    )
  )

}

// --- And this is an http4s service that publishes the OpenApi documentation

object OpenApiServer
  extends http4s.server.Endpoints[IO]
    with http4s.server.JsonEntitiesFromEncodersAndDecoders {

  val openApiService: HttpRoutes[IO] = HttpRoutes.of(
    routesFromEndpoints(
      endpoint(
        get(path / "open-api.json"),
        ok(jsonResponse[OpenApi])
      ).implementedBy(_ => HelloWorldDocumentation.api)
    )
  )

}
Related