Skip to content

Commit

Permalink
Merge pull request #167 from buildo/166-add_documentation_to_tapiro
Browse files Browse the repository at this point in the history
#166: Add documentation to tapiro (closes #166)
  • Loading branch information
calippo authored Mar 5, 2020
2 parents dfd074e + a22e84f commit f2f50e1
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 0 deletions.
5 changes: 5 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,17 @@ lazy val docs = project
moduleName := "retro-docs",
libraryDependencies ++= docsDependencies,
mdocVariables := Map(
"CIRCE_VERSION" -> V.circe,
"AKKA_HTTP_VERSION" -> V.akkaHttp,
"TAPIR_VERSION" -> V.tapir,
"TOCTOC_SNAPSHOT_VERSION" -> version.in(toctocCore).value,
"TOCTOC_STABLE_VERSION" -> version.in(toctocCore).value.replaceFirst("\\+.*", ""),
"ENUMERO_SNAPSHOT_VERSION" -> version.in(enumeroCore).value,
"ENUMERO_STABLE_VERSION" -> version.in(enumeroCore).value.replaceFirst("\\+.*", ""),
"SBT_BUILDO_SNAPSHOT_VERSION" -> version.in(`sbt-buildo`).value,
"SBT_BUILDO_STABLE_VERSION" -> version.in(`sbt-buildo`).value.replaceFirst("\\+.*", ""),
"SBT_TAPIRO_SNAPSHOT_VERSION" -> version.in(`sbt-tapiro`).value,
"SBT_TAPIRO_STABLE_VERSION" -> version.in(`sbt-tapiro`).value.replaceFirst("\\+.*", ""),
),
)
.dependsOn(toctocCore, enumeroCore, toctocSlickPostgreSql)
Expand Down
89 changes: 89 additions & 0 deletions docs/tapiro/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
id: installation
title: Installation
---

`tapiro` can be installed as an Sbt plugin.

`sbt-tapiro` is an Sbt plugin that uses `tapiro` to generate http/json routes parsing scala traits definitions.

## Installation

To start using `sbt-tapiro` simply add this line in `project/plugins.sbt`

```scala
addSbtPlugin("io.buildo" %% "sbt-tapiro" % "@SBT_TAPIRO_STABLE_VERSION@")
```

### Snapshot releases

We publish a snapshot version on every merge on master.

The latest snapshot version is `@SBT_TAPIRO_SNAPSHOT_VERSION@` and you can use
it to try the latest unreleased features. For example:

```scala
addSbtPlugin("io.buildo" %% "sbt-tapiro" % "@SBT_TAPIRO_SNAPSHOT_VERSION@")
resolvers += Resolver.sonatypeRepo("snapshots")
```

## Plugin

To use the code generator, you need to add this to your `build.sbt`.

```scala
import _root_.io.buildo.tapiro.Server

lazy val application = project
.settings(
libraryDependencies ++= applicationDependencies ++ tapiroDependencies,
tapiro / tapiroRoutesPaths := List("[path to routes]"),
tapiro / tapiroModelsPaths := List("[path to models]"),
tapiro / tapiroOutputPath := "[path to endpoints]",
tapiro / tapiroEndpointsPackages := List("[package]", "[subpackage]"),
tapiro / tapiroServer := Server.AkkaHttp, //or Server.Http4s
)
.enablePlugins(SbtTapiro)
```

You can now run it with `sbt application/tapiro`.

```scala
## Dependencies

The generated code comes with library dependencies.

In case akka-http version is used:
```scala
val V = new {
val circe = "@CIRCE_VERSION@"
val tapir = "@TAPIR_VERSION@"
val akkaHttp = "@AKKA_HTTP_VERSION@"
}

val tapiroDependencies = Seq(
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-core" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % V.tapir,
"com.typesafe.akka" %% "akka-http" % V.akkaHttp,
"io.circe" %% "circe-core" % V.circe,
)
```

In case http4s is used:

```scala
val V = new {
val circe = "@CIRCE_VERSION@"
val tapir = "@TAPIR_VERSION@"
}

val tapiroDependencies = Seq(
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-core" % V.tapir,
"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % V.tapir,
"io.circe" %% "circe-core" % V.circe,
)
```

These dependencies usually go under `project/Dependencies.scala`
124 changes: 124 additions & 0 deletions docs/tapiro/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
id: introduction
title: Introduction
---

Tapiro parses your Scala controllers to generate HTTP endpoints.

A Scala controller is a trait defined as follows:

```scala mdoc
import scala.annotation.StaticAnnotation

class query extends StaticAnnotation
class command extends StaticAnnotation

case class Cat(name: String)
case class Error(msg: String)

trait Cats[F[_], AuthToken] {
@query //translate this to a GET
def findCutestCat(): F[Either[Error, Cat]]

@command //translate this to a POST
def doSomethingWithTheCat(catId: Int, token: AuthToken): F[Either[Error, Unit]]
}
```

For each controller tapiro generates two files:
- `CatsEndpoints.scala` containing the HTTP api description using https://tapir-scala.readthedocs.io/
- `CatsHttp4sEndpoints.scala` or `CatsAkkaHttpEndpoints.scala` depeneding on the HTTP server the user is using.

## Complete Example

Here you have an example implementation of the `Cats` controller definied in the previous section:

```scala mdoc
import cats.effect._

object Cats {
def create[F[_]](implicit F: Sync[F]) = new Cats[F, String] {
override def findCutestCat(): F[Either[Error, Cat]] =
F.delay(Right(Cat("Cheshire")))
override def doSomethingWithTheCat(catId: Int, token: String): F[Either[Error, Unit]] =
F.delay(Right(()))
}
}


```

Here you have the autogenerated magic fromo tapiro (This is the content of `CatsHttp4sEndpoints.scala` it will be autogenerated).

```scala mdoc
import org.http4s.HttpRoutes

// ---- begins autogenerated code
object CatsHttp4sEndpoints {
def routes(controller: Cats[IO, String]): HttpRoutes[IO] = ???
}
// ---- ends autogenerated code
```

Here is how to run the server:

```scala mdoc
import org.http4s.server.blaze._
import org.http4s.implicits._
import cats.implicits._

object Main extends IOApp {
val catsImpl = Cats.create[IO]
val routes = CatsHttp4sEndpoints.routes(catsImpl)

override def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
.withHttpApp(routes.orNotFound)
.serve
.compile
.drain
.as(ExitCode.Success)
}
```

The resulting server can be queried as follows:
```
/GET /Cats/findCutestCat
/POST /Cats/doSomethingWithTheCat -d '{ "catId": 1 }'
```

## Authentication

An `AuthToken` type argument is expected in each controller and is added as authorization header.

`trait Cats[F[_], AuthToken]`

The actual implementation of the `AuthToken` is left to the user. All tapiro requires is a proper tapir `PlainCodec` such as:

```scala mdoc
import sttp.tapir._
import sttp.tapir.Codec._

case class CustomAuth(token: String)

def decodeAuth(s: String): DecodeResult[CustomAuth] = {
val TokenPattern = "Token token=(.+)".r
s match {
case TokenPattern(token) => DecodeResult.Value(CustomAuth(token))
case _ => DecodeResult.Error(s, new Exception("token not found"))
}
}

def encodeAuth(auth: CustomAuth): String = auth.token

implicit val authCodec: PlainCodec[CustomAuth] = Codec.stringPlainCodecUtf8
.mapDecode(decodeAuth)(encodeAuth)
```

The user will find the decoded token as the last argument of the method in the trait.

```scala
@command //translate this to a POST
def doSomethingWithTheCat(catId: Int, token: AuthToken): F[Either[Error, Unit]]
```
42 changes: 42 additions & 0 deletions docs/tapiro/migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
id: migrate
title: Migration from Wiro
---

Tapiro is meant to deprecate [wiro](https://github.com/buildo/wiro).

Tapiro is based on the same concepts of wiro, the migration is pretty straightforward.

Here is a checklist of what you need to do:
1. Install the plugin (as described in the [guide](installation.md))
2. Configure your `build.sbt` (as described in the [guide](installation.md))
3. Add `AuthToken` type parameter to controllers
`trait AccountController[F]` -> `trait AccountController[F[_], AuthToken]`
4. Modify controllers so that wiro `Auth` is replaced with AuthToken and move as last argument
`def read(token: Auth, arg: Int)` -> `def read(arg: Int, token: AuthToken)`
5. Add `**/*Endpoints.scala linguist-generated` to repository's `.gitattributes` to automatically collapse tapiro generated code in GitHub diffs
6. Add required codecs
This is a valid codec for wiro.Auth:

```scala mdoc
import sttp.tapir._
import sttp.tapir.Codec._

case class Auth(token: String) //should be imported as wiro.Auth instead

implicit val authCodec: PlainCodec[Auth] = Codec.stringPlainCodecUtf8
.mapDecode(decodeAuth)(encodeAuth)

def decodeAuth(s: String): DecodeResult[Auth] = {
val TokenPattern = "Token token=(.+)".r
s match {
case TokenPattern(token) => DecodeResult.Value(Auth(token))
case _ => DecodeResult.Error(s, new Exception("token not found"))
}
}

def encodeAuth(auth: Auth): String = auth.token
```
7. Run `sbt tapiro`

Using `Server.AkkaHttp` the resulting routes can be added to wiro as custom routes.
6 changes: 6 additions & 0 deletions docs/tapiro/rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: rpc
title: Why?
---

> 📖 **NOTE**: Long time ago we wrote a [blogpost](https://blog.buildo.io/http-routes-at-buildo-1424250c41d3) about this. The blogpost is about [another library](https://github.com/buildo/wiro) but the underlying concepts are the same.
9 changes: 9 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object Dependencies {
val plantuml = "8059"
val pprint = "0.5.9"
val sbtLogging = "1.3.3"
val tapir = "0.12.19"
}

val circeCore = "io.circe" %% "circe-core" % V.circe
Expand Down Expand Up @@ -80,6 +81,10 @@ object Dependencies {
val plantuml = "net.sourceforge.plantuml" % "plantuml" % V.plantuml
val pprint = "com.lihaoyi" %% "pprint" % V.pprint
val sbtLogging = "org.scala-sbt" %% "util-logging" % V.sbtLogging
val tapir = "com.softwaremill.sttp.tapir" %% "tapir-core" % V.tapir
val tapirJsonCirce = "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % V.tapir
val tapirCore = "com.softwaremill.sttp.tapir" %% "tapir-core" % V.tapir
val tapirHttp4s = "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % V.tapir

val enumeroDependencies = List(
scalatest,
Expand Down Expand Up @@ -191,6 +196,10 @@ object Dependencies {

val docsDependencies = List(
plantuml,
tapir,
tapirJsonCirce,
tapirCore,
tapirHttp4s
)

}
1 change: 1 addition & 0 deletions website/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"toctoc": "toctoc",
"enumero": "enumero",
"sbt-buildo": "sbt-buildo",
"tapiro": "tapiro",
"GitHub": "GitHub"
},
"categories": {
Expand Down
8 changes: 8 additions & 0 deletions website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
"enumero": {
"Getting started": ["enumero/introduction", "enumero/installation"]
},
"tapiro": {
"Setup": ["tapiro/installation"],
"Getting started": [
"tapiro/introduction",
"tapiro/rpc",
"tapiro/migrate"
]
},
"sbt-buildo": {
"Getting started": ["sbt-buildo/introduction"]
},
Expand Down
1 change: 1 addition & 0 deletions website/siteConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const siteConfig = {
{ doc: "toctoc/installation", label: "toctoc" },
{ doc: "enumero/introduction", label: "enumero" },
{ doc: "sbt-buildo/introduction", label: "sbt-buildo" },
{ doc: "tapiro/introduction", label: "tapiro" },
{ search: true },
{ href: "https://github.com/buildo/retro", label: "GitHub", external: true }
],
Expand Down

0 comments on commit f2f50e1

Please sign in to comment.