Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#166: Add documentation to tapiro (closes #166) #167

Merged
merged 20 commits into from
Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
calippo marked this conversation as resolved.
Show resolved Hide resolved

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)
calippo marked this conversation as resolved.
Show resolved Hide resolved
```

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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Here you have an example implementation of the `Cats` controller definied in the previous section:
Here you have an example implementation of the `Cats` controller defined 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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Here you have the autogenerated magic fromo tapiro (This is the content of `CatsHttp4sEndpoints.scala` it will be autogenerated).
Here you have the http4s endpoints auto-generated by tapiro (this would be the content of `CatsHttp4sEndpoints.scala`)


```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._
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serve questo import?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magari riprovo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confermo che serve per .orNotFound

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂ ok

import cats.implicits._

object Main extends IOApp {
val catsImpl = CatsImpl.create[IO]
calippo marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id: why

for consistency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link a blogpost è lì come tampone. Idea in un secondo momento era spiegare RPC, approccio etc, per questo l'ho chiamato così.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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