Adding additional errors to PartialServerEndpoint using errorOutVariant

Hi! I have a reusable PartialEndpoint defined with security logic and basic error output. In the final endpoints I have to add some additional errors based on its logic. The problem comes when I use errorOutVariant method. Additional errors are available in Swagger docs, but when I execute endpoint, then that additional error response cannot be mapped causing 500 Internal server error. The exception is java.lang.IllegalArgumentException: None of the mappings defined in the one-of output.
On the other hand, when I use errorOutVariantPrepend or errorOutVariantsFromCurrent everything works fine. In some cases I have few errors to add and the best option for me is to use errorOutVariants method - but it also fails. Am I doing something wrong here?

  val secureEndpoint: PartialServerEndpoint[SecurityInputs, String, Unit, HttpError, Unit, Any, Future] =
    endpoint
      .securityIn(auth.apiKey(header[Option[String]]("Authorization")))
      .securityIn(auth.apiKey(header[Option[String]]("TwoFactorToken")))
      .errorOut(oneOf[HttpError](badRequest, internal))
      .serverSecurityLogic(_ => Future.successful(Right("Foo")))

  val notWork = secureEndpoint.get
    .in("foo")
    .errorOutVariant(notFound)
    .serverLogic(_ => _ => Future.successful(Left(NotFoundError())))

  val work = secureEndpoint.get
    .in("foo" and "valid")
    .errorOutVariantPrepend(notFound)
    .serverLogic(_ => _ => Future.successful(Left(NotFoundError())))

Can you share some more code - the definitions of HttpError and notFound?

If NotFoundError is a subtype of HttpError, then declaring oneOf[HttpError](badRequest, internal) says that the output handles all outputs of the given type - here the given type is HttpError. So tapir sees that there is output that claims to handle HttpError, and looks for a mapping which matches the exact type. However, it doesn’t find one for NotFoundError, hence the exception. If you prepend the variant, then the NotFoundError is checked first, before going into the catch-all oneOf[HttpError](...).

1 Like

Yes, that’s the case. All errors are subtype of HttpError

sealed trait HttpError {
  def error: String
}

case class NotFoundError(error: String = "Resource not found") extends HttpError
case class BadRequestError(error: String = "Bad request") extends HttpError
case class InternalError(error: String = "Internal server error") extends HttpError

And defined as oneOfVatiant

  val badRequest: EndpointOutput.OneOfVariant[BadRequestError] = oneOfVariant(
    StatusCode.BadRequest,
    jsonBody[BadRequestError]
  )

  val internal: EndpointOutput.OneOfVariant[InternalError] = oneOfVariant(
    StatusCode.InternalServerError,
    jsonBody[InternalError]
  )

  val notFound: EndpointOutput.OneOfVariant[NotFoundError] = oneOfVariant(
    StatusCode.NotFound,
    jsonBody[NotFoundError]
  )

Now I get how it works. Thanks!