softwaremill / softwaremill/tapir

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

Open
#3,006 1 comment 0 reactions 0 assignees View on GitHub

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.