softwaremill / softwaremill/tapir
[BUG] Runtime failure `scala.MatchError: null` when endpoint output is initialized after endpoint declaration
Nobody has claimed this yet.
- Dominant language
- Scala
- Stars
- 1.5k
- Forks
- 468
- Avg merge
- 5h 37m
- Merged PRs (30d)
- 34
Description
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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reported stack-trace locations in SchemasForEndpoints.forOutput and EncodeOutputs, then run the Swagger and Http4s reproductions from the linked gists. Trace how the endpoint's null EndpointIO reaches both interpreters; done should replace the MatchError with safe behavior or a meaningful diagnostic, with coverage for the reproduced declaration order.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- api, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100