Skip to content

Commit

Permalink
Add JSON examples (#4058)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw authored Sep 23, 2024
1 parent ac73d28 commit 3515bc1
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 6 deletions.
6 changes: 2 additions & 4 deletions .scalafix.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
OrganizeImports {
groupedImports = Merge
removeUnused = false
}
OrganizeImports.groupedImports = AggressiveMerge
OrganizeImports.targetDialect = Scala3
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -919,8 +919,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter"))
.settings(
name := "tapir-jsoniter-scala",
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.11",
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.30.11" % Test,
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % Versions.jsoniter,
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % Versions.jsoniter % Test,
scalaTest.value % Test
)
)
Expand Down Expand Up @@ -2052,6 +2052,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
"io.opentelemetry" % "opentelemetry-sdk" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-sdk-metrics" % Versions.openTelemetry,
"io.opentelemetry" % "opentelemetry-exporter-otlp" % Versions.openTelemetry,
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % Versions.jsoniter,
scalaTest.value,
logback
),
Expand All @@ -2070,6 +2071,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
http4sServerZio,
iron,
jdkhttpServer,
jsoniterScala,
nettyServer,
nettyServerCats,
nettyServerSync,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-cats:1.11.4
//> using dep com.softwaremill.sttp.client3::core:3.9.7
//> using dep ch.qos.logback:logback-classic:1.5.8

package sttp.tapir.examples

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server:1.11.4
//> using dep com.softwaremill.sttp.client3::core:3.9.7
//> using dep ch.qos.logback:logback-classic:1.5.8

package sttp.tapir.examples

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.11.4
//> using dep ch.qos.logback:logback-classic:1.5.8

package sttp.tapir.examples

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// {cat=JSON; effects=Direct; server=Netty; JSON=circe}: Return a JSON response with Circe and auto-dervied codecs

//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.11.4
//> using dep ch.qos.logback:logback-classic:1.5.8

package sttp.tapir.examples.json

import sttp.tapir.*
import sttp.tapir.server.netty.sync.NettySyncServer
import sttp.tapir.json.circe.*
import sttp.tapir.generic.auto.*
import io.circe.generic.auto.*

@main def circeNettySyncServer(): Unit =
case class Country(name: String)
case class Author(name: String, country: Country)
case class Genre(name: String)
case class Book(title: String, genre: Genre, year: Int, author: Author)

val helloWorld = endpoint.get
.in("hello")
// both Tapir's Schema & circe's Encoder/Decoder are automatically derived thanks to the `auto` imports
.out(jsonBody[Book])
.handleSuccess(_ => Book("The Witcher: The Last Wish", Genre("Fantasy"), 1993, Author("Andrzej Sapkowski", Country("Poland"))))

NettySyncServer().addEndpoint(helloWorld).startAndWait()
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// {cat=JSON; effects=Direct; server=Netty; JSON=circe}: Return a JSON body which optionally serializes as `null`

//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-json-circe:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.11.4
//> using dep ch.qos.logback:logback-classic:1.5.8

package sttp.tapir.examples.json

import sttp.tapir.*
import sttp.tapir.server.netty.sync.NettySyncServer
import sttp.tapir.json.circe.*
import sttp.tapir.generic.auto.*
import io.circe.generic.auto.*
import io.circe.{Encoder, Json}

@main def circeNullBody(): Unit =
// the data class
case class Data(value: Int)

// helper class, which will allow us to serialize the response body to `null`
// this is the same as an `Option`, however a `None` body by default serializes as an empty body (always)
// integration as below is needed for clients which require the body to be exactly `null`
enum OrNull[+T]:
case Value(value: T)
case Null

// we need to overwrite the encoder that is derived for `OrNull` by default using auto-derivation
// if needed, a `Decoder` would have to be defined in a similar way
given encodeOrNull[A](using Encoder[A]): Encoder[OrNull[A]] = new Encoder[OrNull[A]]:
def apply(a: OrNull[A]): Json =
a match
case OrNull.Value(v) => summon[Encoder[A]](v)
case OrNull.Null => Json.Null

val helloWorld = endpoint.get
.in("hello")
.in(query[Int]("value"))
.out(jsonBody[OrNull[Data]])
.handleSuccess(v => if v == 0 then OrNull.Null else OrNull.Value(Data(v)))

NettySyncServer().addEndpoint(helloWorld).startAndWait()
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// {cat=JSON; effects=Direct; server=Netty; JSON=jsoniter}: Return a JSON response with Jsoniter

//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-jsoniter-scala:1.11.4
//> using dep com.softwaremill.sttp.tapir::tapir-netty-server-sync:1.11.4
//> using dep ch.qos.logback:logback-classic:1.5.8
//> using dep com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:2.30.11

package sttp.tapir.examples.json

import sttp.tapir.*
import sttp.tapir.server.netty.sync.NettySyncServer
import sttp.tapir.json.jsoniter.*
import sttp.tapir.generic.auto.*
import com.github.plokhotnyuk.jsoniter_scala.core.*
import com.github.plokhotnyuk.jsoniter_scala.macros.*

@main def jsoniterNettySyncServer(): Unit =
case class Country(name: String)
case class Author(name: String, country: Country)
case class Genre(name: String)
case class Book(title: String, genre: Genre, year: Int, author: Author)

// we need to derive jsoniter's codec for the top-level class
given bookCodec: JsonValueCodec[Book] = JsonCodecMaker.make

val helloWorld = endpoint.get
.in("hello")
// uses the derived JsonValueCodec & an auto-derived Tapir's Schema, through the `auto` import
.out(jsonBody[Book])
.handleSuccess(_ => Book("The Witcher: The Last Wish", Genre("Fantasy"), 1993, Author("Andrzej Sapkowski", Country("Poland"))))

NettySyncServer().addEndpoint(helloWorld).startAndWait()
1 change: 1 addition & 0 deletions project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ object Versions {
val nettyAll = "4.1.113.Final"
val logback = "1.5.8"
val slf4j = "2.0.16"
val jsoniter = "2.30.11"
}

0 comments on commit 3515bc1

Please sign in to comment.