Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tapir server to ignore any validations #3682

Open
ShahOdin opened this issue Apr 13, 2024 · 9 comments · May be fixed by #3785
Open

tapir server to ignore any validations #3682

ShahOdin opened this issue Apr 13, 2024 · 9 comments · May be fixed by #3785
Assignees

Comments

@ShahOdin
Copy link

let's say my api is supposed to return two "happy" path scenarios:

  • 200
  • 204

I don't want to complicate my service logic with handling the 204 case. instead I want to model it as an error that my service logic throws so my service logic considers it an error whereas the customer happily sees it as 204. with the default setup, the opinionated tapir gets in the way and manipulates things and I end up giving me 400.

Also, how can I throw my own error in case of the user not having provided a header as part of the security input for example? I don't want tapir to automatically craft a 401.

Basically, how can I get tapir to be as dumb as possible and let me control everything dynamically with my (Error, Status)?

@adamw
Copy link
Member

adamw commented Apr 14, 2024

If I understand you correctly, using the statusCode output should solve your problem. You can either use a fixed status code, e.g. .errorOut(statusCode(StatusCode.Accepted)), or a dynamic one, e.g. .out(statusCode), then you'll have to provide the status code as part of the server logic's result.

@shah-ovo
Copy link

Thanks! I'll double check but think I was getting a 400 where I had explicitly asked for 204 using the status output as you mentioned. Will add a minimal example when I'm home.

What about the explicit error on the missing header that's currently handled by tapir?

@adamw
Copy link
Member

adamw commented Apr 14, 2024

I think a custom DecodeFailureHandler (docs) set via the server options should do the trick

@shah-ovo
Copy link

I think a custom DecodeFailureHandler (docs) set via the server options should do the trick

Yeah this did it:

  def handleMissingAcceptHeader[F[_]: Applicative]: DecodeFailureHandler[F] =
    new DecodeFailureHandler[F]:
      override def apply(ctx: DecodeFailureContext)(implicit
          monad: MonadError[F]
      ): F[Option[ValuedEndpointOutput[_]]] = ctx.failingInput match
        case EndpointIO.Header("X-Api-Key", _, _) =>
          hardcoded401
        case _ => DefaultDecodeFailureHandler[F](ctx)
with the hardcoded value being (click to expand):
val hardcoded401 = server.model
  .ValuedEndpointOutput(
    statusCode.and(headers).and(jsonBody[Unauthorised]),
    (
    StatusCode(401),
    List(
        Header("www-authenticate", "ApiKey"),
        Header("content-type", "text/plain; charset=UTF-8")
    ),
    Unauthorised(
        ErrorMessage("Authentication has failed or has not yet been provided.")
    )
    )
)
.some
.pure[F]

thank you.

As for the other issue, sorry it doesn't give you 400, but a 500. Here is a minimal example:

val serverOptions: Http4sServerOptions[IO] = Http4sServerOptions
      .customiseInterceptors[IO]
      .options

    endpoint
      .in("hello")
      .errorOut(statusCode)
      .errorOut(stringBody)
      .serverLogic(_ =>
        IO.pure(
          Left(
            (StatusCode(204), "I'm being silenced!")
          )
        )
      )
      .pipe(Http4sServerInterpreter[IO](serverOptions).toRoutes)
      .run(
        Request[IO](
          uri = Uri.unsafeFromString(
            "hello"
          )
        )
      )
      .value
      .unsafeRunSync()
      .get
      .status

gives you 500, and not 204. How can I override this?

@adamw
Copy link
Member

adamw commented Apr 15, 2024

Ah, if you take a look at the exception that is being thrown by the interpreter, it's because you are trying to respond with a 204 (no content), and set a body. If you don't return a body, then this works fine.

@shah-ovo
Copy link

Sure, but as a dev I want to override that! http4s lets me do that I think. Does tapir let me get around that?

@adamw
Copy link
Member

adamw commented Apr 16, 2024

I don't think so, though it would be a matter of removing this case:

case (StatusCode.NoContent | StatusCode.NotModified, Some(_)) =>

Is there a use-case where you want to send a body with a 204?

@ShahOdin
Copy link
Author

Is there a use-case where you want to send a body with a 204?

From the open api spec I have been given:

"no_content": {
        "content": {
          "application/json": {
            "example": "No data exists for the requested time period",
            "schema": {
              "type": "string"
            }
          }
        }

@adamw
Copy link
Member

adamw commented Apr 17, 2024

Interesting :) Well, if that's an obstacle, can you create a PR which removes this check?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants