softwaremill/tapir

Simple default bundle compatible with Scala toolkit

bishabosha opened this issue · 7 comments

Do you think it would be possible to bundle a bunch of defaults into an object for building simple apps in direct style
e.g. JdkHttpServer, Upickle json

Then there can be fewer imports needed

with this simple bundle it would be useful to recommend for bundling with scala toolkit

for example, if I bundle the jdkhttp, and upickle json handlers in one object like this

package scala.toolkit

import sttp.tapir.Tapir
import sttp.tapir.server.jdkhttp
import sttp.tapir.json.upickle.TapirJsonuPickle

import scala.util.Try

object httpserver extends Tapir
  with TapirJsonuPickle:

  private val ujsonSchema = Schema.schemaForString.map(
    s => Try(ujson.read(s)).toOption
  )(ujson.write(_))

  given Schema[ujson.Value] = ujsonSchema

  def ujsonBody = jsonBody[ujson.Value]

  export sttp.tapir.Schema
  export jdkhttp.{HttpServer, HttpsConfigurator}
  export jdkhttp.{JdkHttpResponseBody, JdkHttpServer, JdkHttpServerInterpreter, JdkHttpServerOptions}

then I can make a very simple main app like so

package app

import scala.toolkit.httpserver.*
import java.time.LocalDate

val date = endpoint.get.in("date").out(ujsonBody)
  .handleSuccess(_ => ujson.Obj("date" -> LocalDate.now().toString))

@main def launch() =
  val server = JdkHttpServer()
    .addEndpoint(date)
    .port(8080)
    .host("localhost")
    .executor(scala.concurrent.ExecutionContext.global)
    .start()

  println("Server started at http://localhost:8080, ctrl-c to stop")

  sys.addShutdownHook:
    server.stop(0)

An open question is then "what about JDK 21?" - then we could bundle the NettySyncServer instead maybe? we'd have to make a policy in scala/toolkit about JDK support though

Definitely NettySyncServer is the way to go, it's more feature-full (websockets work, multiparts are coming). Before exports we took this approach: https://tapir.softwaremill.com/en/latest/mytapir.html, though I think it would be most useful for customising schema derivation etc.

ok so would you accept a PR to add a new artefact that can bundle a few of these things in one import? (based on NettySyncServer, and upickle, probably add the ujsonBody directly to the tapir-json-upickle artifact)

I see also that NettySyncServer requires Ox and is only Scala 3, so this complicates it possibly

Sure why not - we already have a "swagger bundle", the same way we could have a "direct bundle".

I don't understand the ujsonBody, though? We could also consider using pickler in that https://tapir.softwaremill.com/en/latest/endpoint/pickler.html

Oh I wasn't aware of this module - it seems good - the reason I defined ujsonBody in this example was so you can do very basic serialisation of ujson.Value without needing derivation or explicit type parameter, e.g. ujson.Obj("date" -> LocalDate.now().toString)

So there are no default ReadWriters for ujson.Values? If so, we could just provide Schema instances, the way it's done e.g. with circe - no separate methods needed: https://github.com/softwaremill/tapir/blob/7dddc5795669b776c25f2ad7d09be84dc552ec8f/json/circe/src/main/scala/sttp/tapir/json/circe/TapirJsonCirce.scala