my endpoint looks like this, but when I make a request and I don’t specify the header I want to receive a 400 not a 415 as it does now, how could I map it?
You can use a custom DecodeFailureHandler, here’s a scala-cli example:
//> using lib "com.softwaremill.sttp.tapir::tapir-core:1.9.9"
//> using lib "com.softwaremill.sttp.tapir::tapir-http4s-server:1.9.9"
//> using lib "org.http4s::http4s-blaze-server:0.23.16"
//> using lib "org.http4s::http4s-dsl:0.23.25"
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.server.blaze._
import org.http4s.implicits._
import scala.concurrent.ExecutionContext.{global => ecGlobal}
import sttp.tapir._
import sttp.tapir.server.http4s.Http4sServerInterpreter
import cats.effect.unsafe.implicits.global
import sttp.model.Header
import sttp.model.MediaType
import sttp.tapir.server.http4s.Http4sServerOptions
import sttp.tapir.server.interceptor.decodefailure.DecodeFailureHandler
import sttp.tapir.server.interceptor.DecodeFailureContext
import sttp.monad.MonadError
import sttp.tapir.server.model.ValuedEndpointOutput
import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler
import sttp.tapir.DecodeResult.Mismatch
import sttp.tapir.DecodeResult.InvalidValue
import sttp.model.StatusCode
import sttp.tapir.EndpointIO.FixedHeader
import sttp.model.HeaderNames
val helloEndpoint = endpoint.post
.in("hello")
.in(header(Header.contentType(MediaType.ApplicationXWwwFormUrlencoded)))
.out(stringBody)
//------------------------------------------------------------
val customDecodeFailureHandler: DecodeFailureHandler[IO] = new DecodeFailureHandler[IO] {
val defaultHandler = DefaultDecodeFailureHandler[IO]
override def apply(ctx: DecodeFailureContext)(implicit monad: MonadError[IO]): IO[Option[ValuedEndpointOutput[_]]] =
ctx.failingInput match {
case FixedHeader(Header(HeaderNames.ContentType, "application/x-www-form-urlencoded"), _, _) =>
IO.pure(Some(ValuedEndpointOutput(statusCode.and(sttp.tapir.headers).and(stringBody), (StatusCode.BadRequest, List.empty, "Your bad request message"))))
case other =>
defaultHandler(ctx)
}
}
val options = Http4sServerOptions.customiseInterceptors[IO].decodeFailureHandler(customDecodeFailureHandler).options
//------------------------------------------------------------
val helloRoute = Http4sServerInterpreter[IO](options).toRoutes(helloEndpoint.serverLogic[IO](_ => IO(Right("Hello"))))
val httpApp: HttpApp[IO] = helloRoute.orNotFound
val server = BlazeServerBuilder[IO]
.withExecutionContext(ecGlobal)
.bindHttp(8080, "localhost")
.withHttpApp(httpApp)
.resource
def runServer: IO[Unit] = server.use(_ => IO.never)
def main(args: Array[String]): Unit = {
runServer.unsafeRunSync()
}
Note that this handler will be applied gobally. If you want to limit it to certain endpoints and leave default handling in others, consider adding endpoint attributes to the condition.
@kciesielski however … the code only specified to return 415 when there’s a mismatch on the header values. If the header is missing (which I think should be represented as DecodeResult.Missing), then you would get a 400 I suppose?
@M_Perez are you sure you are not sending any Content-Type header in your request?
@adamw hmm I checked that in the morning and I was pretty sure it also returned 415 for missing header (which made me wonder too), but now I repeated the test and I’m getting 400 with message:
Invalid value for: header Content-Type: application/x-www-form-urlencoded (missing)
as expected.
@M_Perez please check if your header is indeed missing. Maybe it’s set with unexpected value by the client library or some entities between client and server (proxies, etc).