Getting started
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.{ActorMaterializer, Materializer}
import io.circe.Printer
import ru.tinkoff.tschema.akkaHttp.MkRoute
import io.circe.syntax._
import ru.tinkoff.tschema.swagger._
object ExampleDefinition {
import ru.tinkoff.tschema.syntax._
def api = get |> operation('hello) |> capture[String]('name) |> $$[String]
}
object ExampleSwagger {}
object Example extends App {
import ExampleDefinition.api
import akka.http.scaladsl.server.Directives._
// building service
object handler {
def hello(name: String): String = s"Hello, $name"
}
val apiRoute = MkRoute(api)(handler)
//building swagger
val apiSwagger = MkSwagger(api)(()).make(OpenApiInfo("example"))
val printer = Printer.spaces2.copy(dropNullValues = true)
val swaggerRoute = path("swagger")(complete(apiSwagger.asJson.pretty(printer)))
//typical akka http boilerplate
implicit val system: ActorSystem = ActorSystem("tschema-example")
implicit val materializer: Materializer = ActorMaterializer()
//run the server
Http().bindAndHandle(apiRoute ~ swaggerRoute, "localhost", 8080)
}
More examples see in subproject examples
How it works
Definition
Your schemes definitions consist of elements from ru.tinkoff.tschema.syntax._ and maybe custom directives
def api = get |> operation('hello) |> capture[String]('name) |> $$[String]
This may be read as a sequence:
- check HTTP method is GET
- check path prefix is “hello” and mark the following definition as part of
hellooperation - capture segment of uri path as
nameparameter - return String
Your definition could have some branching, a common prefix will be applied as the prefix of all branches.
Branching is done with the <> operator:
def api =
get |> prefix('greeting) |> capture[String]('name) |> ((
operation('hello) |> $$[String]
) <> (
operation('aloha) |> queryParam[Int]('age) |> $$[String]
))
Note that now you must implement aloha method in your handler
or compile error will be raised in the MkRoute application
DSL
All definition elements are functions with almost no implementation, returning types from the
ru.tinkoff.tschema.typeDSL._ package, or created by yourself.
Those types are the definition.
All interpreters just traversing the tree type and building construction using information from types only
Type tree will consist of type constructors, subtypes of DSlAtom at it branches, and DSLRes at leafs.
Each DSLAtom describes single step like parameter matching, filter or anything you’d like to do inside
Akka http route.
When you are ready to build your source, you now can execute route building.
MkRoute(api)(handler) will take the type of api parameter and create corresponding tree.
using directive definitions given by implicit instances of:
trait Serve.Aux[T, In]{type Out}where:T- your DSLAtom or DSLResIn- input parameters collected by precedingServeinstances and tagged by namesOut- parameters, that will be provided for subtree
trait RoutableIn[In, Res, Out]where:In- your DSLDefRes- result type, returned by corresponding method in handlerOut- result type, defined in the API definition
You generally will need following instances:
ToResponseMarshallerfor returning type of your methodFromEntityUnmarshallerfor type, used in yourbodydirectiveFromParamfor any type in parameter directives likequeryParam
Swagger
When you need to create OpenApi 3.0 object, you can just apply MkSwagger(api)(())
This will create SwaggerBuilder object.
These builders could be concatenated with ++ to collect definitions from several modules
To successfully run you’ll need following implicits:
SwaggerTypeablefor any mentioned typeSwaggerMapperfor any custom directive
When you final SwaggerBuilder is ready, apply it’s make method supplying some top-level info,
this will create OpenApi object that has the circe Encoder instance, which you can serve any way you want.