diff --git a/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala b/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala index 6876060ce..4ab0448ed 100644 --- a/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala +++ b/modules/core/src/smithy4s/http/HttpUnaryServerCodecs.scala @@ -194,8 +194,14 @@ object HttpUnaryServerCodecs { def encodeOutput(o: O) = F.map(base)(outputW.write(_, o)) def encodeError(e: E) = F.map(base)(errorW.write(_, e)) - def httpContractErrorEncoder(e: HttpContractError) = - F.map(base)(httpContractErrorWriters.write(_, e).withStatusCode(400)) + + def httpContractErrorEncoder(e: HttpContractError) = { + val statusCode = e match { + case _: UpstreamServiceError => 500 + case _ => 400 + } + F.map(base)(httpContractErrorWriters.write(_, e).withStatusCode(statusCode)) + } def throwableEncoders(throwable: Throwable): F[HttpResponse[Blob]] = throwable match { case e: HttpContractError => httpContractErrorEncoder(e) diff --git a/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala b/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala index 702fe0f53..406a85144 100644 --- a/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala +++ b/modules/tests/src/smithy4s/tests/PizzaAdminServiceImpl.scala @@ -20,6 +20,7 @@ import cats.effect._ import cats.implicits._ import smithy4s.Timestamp import smithy4s.example._ +import smithy4s.http.UpstreamServiceError import java.util.UUID @@ -51,6 +52,7 @@ class PizzaAdminServiceImpl(ref: Ref[IO, State]) extends PizzaAdminService[IO] { ): IO[AddMenuItemResult] = for { _ <- IO.raiseError(Boom).whenA(restaurant == "boom") + _ <- IO.raiseError(UpstreamServiceError("Upstream service failure")).whenA(restaurant == "upstreamServiceError") _ <- IO .raiseError( PriceError( diff --git a/modules/tests/src/smithy4s/tests/PizzaSpec.scala b/modules/tests/src/smithy4s/tests/PizzaSpec.scala index b17982521..fb691ed63 100644 --- a/modules/tests/src/smithy4s/tests/PizzaSpec.scala +++ b/modules/tests/src/smithy4s/tests/PizzaSpec.scala @@ -30,6 +30,7 @@ import smithy4s.http.HttpPayloadError import smithy4s.example.PizzaAdminService import smithy4s.http.CaseInsensitive import smithy4s.http.HttpContractError +import smithy4s.http.UpstreamServiceError import weaver._ import cats.Show import org.http4s.EntityDecoder @@ -463,6 +464,33 @@ abstract class PizzaSpec } } + routerTest("Upstream service error returns 500") { (client, uri, log) => + val badMenuItem = Json.obj( + "food" -> pizzaItem, + "price" -> Json.fromFloatOrNull(9.0f) + ) + + for { + res <- client.send[Json]( + POST(badMenuItem, uri / "restaurant" / "upstreamServiceError" / "menu" / "item"), + log + ) + } yield { + val (code, headers, body) = res + val expectedBody = + Json.obj( + "upstreamServiceError" -> Json.obj( + "message" -> Json.fromString("Upstream service failure") + ) + ) + val discriminator = headers.get("X-Error-Type").flatMap(_.headOption) + + expect(code == 500) && + expect(body == expectedBody) && + expect(discriminator == None) + } + } + type Res = (Client[IO], Uri) def sharedResource: Resource[IO, (Client[IO], Uri)] = for { stateRef <- Resource.eval( @@ -476,6 +504,8 @@ abstract class PizzaSpec smithy4s.example.GenericClientError("Oops") case PizzaAdminServiceImpl.Boom => smithy4s.example.GenericServerError("Crash") + case UpstreamServiceError(message) => + UpstreamServiceError(message) case t: Throwable if !t.isInstanceOf[HttpContractError] => // This pattern allows checking that errors specified in specs // do not get intercepted by mapErrors/flatMapErrors methods.