tapir
tapir copied to clipboard
[BUG] Runtime failure `scala.MatchError: null` when endpoint output is initialized after endpoint declaration
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.
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