tapir icon indicating copy to clipboard operation
tapir copied to clipboard

[BUG] Runtime failure `scala.MatchError: null` when endpoint output is initialized after endpoint declaration

Open majk-p opened this issue 1 year ago • 1 comments

Tapir version: 1.6.0

Scala version: 3.3.0

Describe the bug

What is the problem?

I want to implement an endpoint that uses an extracted EndpointIO as an out like this:

val authorize: Endpoint[Unit, Unit, Unit, CookieValueWithMeta, Any] =
  endpoint
    .in("auth" / "authorize")
    .summary("Create user session")
    .get
    .out(setSessionCookie)

val setSessionCookie: sttp.tapir.EndpointIO.Header[CookieValueWithMeta] =
  setCookie("SESSION")

And want to generate the Swagger description out of it.

When I declare the EndpointIO after the endpoint definition without making it lazy, the code fails with scala.MatchError: null:

Exception in thread "main" scala.MatchError: null
        at sttp.tapir.docs.apispec.schema.SchemasForEndpoints.forOutput(SchemasForEndpoints.scala:61)
        at sttp.tapir.docs.apispec.schema.SchemasForEndpoints.forOutput(SchemasForEndpoints.scala:58)
        at sttp.tapir.docs.apispec.schema.SchemasForEndpoints.$anonfun$1(SchemasForEndpoints.scala:21)
        at scala.collection.immutable.List.flatMap(List.scala:293)
        at scala.collection.immutable.List.flatMap(List.scala:79)
        at sttp.tapir.docs.apispec.schema.SchemasForEndpoints.apply(SchemasForEndpoints.scala:21)
        at sttp.tapir.docs.openapi.EndpointToOpenAPIDocs$.toOpenAPI(EndpointToOpenAPIDocs.scala:22)
        at sttp.tapir.docs.openapi.OpenAPIDocsInterpreter.toOpenAPI(OpenAPIDocsInterpreter.scala:41)
        at sttp.tapir.docs.openapi.OpenAPIDocsInterpreter.toOpenAPI$(OpenAPIDocsInterpreter.scala:8)
        at sttp.tapir.docs.openapi.OpenAPIDocsInterpreter$$anon$1.toOpenAPI(OpenAPIDocsInterpreter.scala:60)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter.fromEndpoints(SwaggerInterpreter.scala:22)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter.fromEndpoints$(SwaggerInterpreter.scala:11)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter$$anon$1.fromEndpoints(SwaggerInterpreter.scala:90)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter.fromServerEndpoints(SwaggerInterpreter.scala:58)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter.fromServerEndpoints$(SwaggerInterpreter.scala:11)
        at sttp.tapir.swagger.bundle.SwaggerInterpreter$$anon$1.fromServerEndpoints(SwaggerInterpreter.scala:90)
        at Main$.run(e4c100c66c7cb3561c3138bcae32e1c7-master/error.scala:38)
        at cats.effect.IOApp.main(IOApp.scala:396)
        at cats.effect.IOApp.main$(IOApp.scala:143)
        at Main$.main(e4c100c66c7cb3561c3138bcae32e1c7-master/error.scala:34)
        at Main.main(e4c100c66c7cb3561c3138bcae32e1c7-master/error.scala)

The problem is even more serious when you don't do swagger and instead just try to use server interpreter, as this code then fails in the runtime:

[io-compute-1] ERROR sttp.tapir.server.http4s.Http4sDefaultServerLog - Exception when handling request: GET /auth/authorize, by: GET /auth/authorize, took: 203ms
scala.MatchError: null
        at sttp.tapir.server.interpreter.EncodeOutputs.apply(EncodeOutputs.scala:19)
        at sttp.tapir.server.interpreter.EncodeOutputs.applyPair(EncodeOutputs.scala:31)
        at sttp.tapir.server.interpreter.EncodeOutputs.apply(EncodeOutputs.scala:18)
        at sttp.tapir.server.interpreter.ServerInterpreter$$anon$4.apply(ServerInterpreter.scala:219)
        at sttp.tapir.server.interpreter.ServerInterpreter$$anon$3.onDecodeSuccess$$anonfun$2(ServerInterpreter.scala:200)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at adaptError$extension @ org.http4s.ember.server.internal.ServerHelpers$.runConnection(ServerHelpers.scala:422)
        at adaptError$extension @ org.http4s.ember.server.internal.ServerHelpers$.runConnection(ServerHelpers.scala:422)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)
        at adaptError$extension @ org.http4s.ember.server.internal.ServerHelpers$.runConnection(ServerHelpers.scala:422)
        at flatMap @ sttp.tapir.integ.cats.effect.CatsMonadError.flatMap(CatsMonadError.scala:9)
        at map @ sttp.tapir.integ.cats.effect.CatsMonadError.map(CatsMonadError.scala:8)

How to reproduce?

Maybe you can provide code to reproduce the problem?

Following gists provide the code that reproduces the problem:

Swagger variant: https://gist.github.com/majk-p/e4c100c66c7cb3561c3138bcae32e1c7 - can be run using scala-cli

Http4s server variant https://gist.github.com/majk-p/90908f751a6a94be6a7c9817d5a958c7 - also run with scala-cli, make sure to send a request using curl http://localhost:9999/auth/authorize to see the error in logs

Expected behavior In the best case scenario I'd expect a compilation error instead of runtime. If that's difficult, perhaps at least the error message could be more meaningful, it took me a while to sort it out.

majk-p avatar Jul 03 '23 08:07 majk-p

I think it's just Scala's semantics that's at play here - if you are forward-referencing an uninitialized field it will be null. I don't think we can do anything about that.

I think using a lazy val setSessionCookie would fix the problem

adamw avatar Jul 26 '23 07:07 adamw