JSON serialization: No given instance of type sttp.tapir.typelevel.ParamConcat.Aux

I’m pretty sure I’m missing something pretty simple. My apologies in advance.

I am trying to use jsonBody[T] to serialize outputs, using the upickle JSON libraries. I see compiler failures like

[error] No given instance of type sttp.tapir.typelevel.ParamConcat.Aux[Wrapper, Wrapper, Wrapper] was found for parameter ts of method out in trait EndpointOutputsOps.

Here is a minimal scala-cli reproduction of the problem:

//> using scala "3.3.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-core:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-json-upickle:1.6.0"
//> using dep "com.lihaoyi::upickle:3.1.2"

import sttp.tapir.{Endpoint,Schema}
import sttp.tapir.json.upickle.*
import upickle.default.ReadWriter

case class Wrapper(i : Int)

implicit val rw : ReadWriter[Wrapper] = upickle.default.readwriter[Wrapper]
implicit val wSchema : Schema[Wrapper] = Schema.derived

val endpoint : Endpoint[Unit,Unit,Nothing,Wrapper,Any] =
  endpoint
    .get
    .out(jsonBody[Wrapper])

A workaround is to use stringJsonBody, and write the JSON to a String within server logic. That’s probably fine for my application. But I’d like to understand where I’m going wrong. Thanks in advance for your patience and help.

Indeed the error message is rather misleading, but there’s a couple of problems with the code which makes the compiler lost:

  1. you are defining your own endpoint val, (presumably) using the tapir’s endpoint. But because you define you own endpoint, this ends up being an infinite loop. That’s probably where the compiler is lost (on the recursion). Renaming this to e solves one problem
  2. the type of the endpoint is incorrect - by default, there’s an empty error output of type Unit. So you either have to change the type to reflect that, or use infalliableEndpoint as a starting point.
  3. there’s a recursive implicit warning when you define the ReadWriter. I think you probably wanted to generate the instance with a macro, instead of doing an implicit lookup (which readwriter does, and ends up in an infinite loop, as you are defining this implicit). Replacing this with macroRW solves the problem.

Summing up, here’s a version which compiles:

//> using scala "3.3.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-core:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-json-upickle:1.6.0"
//> using dep "com.lihaoyi::upickle:3.1.2"

import sttp.tapir.{endpoint,Endpoint,Schema}
import sttp.tapir.json.upickle.*
import upickle.default.ReadWriter

case class Wrapper(i : Int)

implicit val rw : ReadWriter[Wrapper] = upickle.default.macroRW[Wrapper]
implicit val wSchema : Schema[Wrapper] = Schema.derived

val e : Endpoint[Unit,Unit,Unit,Wrapper,Any] =
  endpoint
    .get
    .out(jsonBody[Wrapper])

Well, I’m embarrassed.

Fix the unintended recursion and the compiler guides right through the rest. Thanks a ton for taking the time to take a look.

It’s kind of interesting, because ultimately this brainfart was entirely unrelated to tapir code, but the type annotation to Endpoint misdirects the compiler to reporting an error in terms of obscure tapir internals.

Anyway, I’m sorry for taking your time for what ultimately was just a pretty dumb oversight. Thank you again!

No worries. It’s not that common to get such an easy to reproduce bug (in an easy to reproduce form - scala-cli script) so this was quite quick :slight_smile: