mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:27:05 +00:00
feature/(http4s): handle errors in executeAndRespond and improve error parsing
Refactor executeAndRespond to properly handle exceptions from Future and convert them to HTTP responses using ErrorResponseConverter. This ensures consistent error handling across HTTP4S endpoints. Simplify error response creation by parsing APIFailureNewStyle exceptions from JSON message instead of direct type matching, making error handling more robust. Update API version validation in Http4s700 to use NewStyle.function.tryons and Helper.booleanToFuture for consistent error handling patterns. Adjust test to use proper error message constant for invalid API version.
This commit is contained in:
parent
b6c7360a2d
commit
747d761c9b
@ -1,10 +1,9 @@
|
||||
package code.api.util.http4s
|
||||
|
||||
import cats.effect._
|
||||
import code.api.APIFailureNewStyle
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.CallContext
|
||||
import net.liftweb.common.{Failure => LiftFailure}
|
||||
import net.liftweb.json.{JInt, JString, parseOpt}
|
||||
import net.liftweb.json.compactRender
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import org.http4s._
|
||||
@ -30,6 +29,8 @@ object ErrorResponseConverter {
|
||||
|
||||
implicit val formats: Formats = CustomJsonFormats.formats
|
||||
private val jsonContentType: `Content-Type` = `Content-Type`(MediaType.application.json)
|
||||
private val internalFieldsFailCode = "failCode"
|
||||
private val internalFieldsFailMsg = "failMsg"
|
||||
|
||||
/**
|
||||
* OBP standard error response format.
|
||||
@ -51,39 +52,20 @@ object ErrorResponseConverter {
|
||||
* Convert any error to http4s Response[IO].
|
||||
*/
|
||||
def toHttp4sResponse(error: Throwable, callContext: CallContext): IO[Response[IO]] = {
|
||||
error match {
|
||||
case e: APIFailureNewStyle => apiFailureToResponse(e, callContext)
|
||||
case _ => unknownErrorToResponse(error, callContext)
|
||||
parseApiFailureFromExceptionMessage(error).map { failure =>
|
||||
createErrorResponse(failure.code, failure.message, callContext)
|
||||
}.getOrElse {
|
||||
unknownErrorToResponse(error, callContext)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert APIFailureNewStyle to http4s Response.
|
||||
* Uses failCode as HTTP status and failMsg as error message.
|
||||
*/
|
||||
def apiFailureToResponse(failure: APIFailureNewStyle, callContext: CallContext): IO[Response[IO]] = {
|
||||
val errorJson = OBPErrorResponse(failure.failCode, failure.failMsg)
|
||||
val status = org.http4s.Status.fromInt(failure.failCode).getOrElse(org.http4s.Status.BadRequest)
|
||||
IO.pure(
|
||||
Response[IO](status)
|
||||
.withEntity(toJsonString(errorJson))
|
||||
.withContentType(jsonContentType)
|
||||
.putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Lift Box Failure to http4s Response.
|
||||
* Returns 400 Bad Request with failure message.
|
||||
*/
|
||||
def boxFailureToResponse(failure: LiftFailure, callContext: CallContext): IO[Response[IO]] = {
|
||||
val errorJson = OBPErrorResponse(400, failure.msg)
|
||||
IO.pure(
|
||||
Response[IO](org.http4s.Status.BadRequest)
|
||||
.withEntity(toJsonString(errorJson))
|
||||
.withContentType(jsonContentType)
|
||||
.putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId))
|
||||
)
|
||||
private def parseApiFailureFromExceptionMessage(error: Throwable): Option[OBPErrorResponse] = {
|
||||
Option(error.getMessage).flatMap(parseOpt).flatMap { json =>
|
||||
(json \ internalFieldsFailCode, json \ internalFieldsFailMsg) match {
|
||||
case (JInt(code), JString(message)) => Some(OBPErrorResponse(code.toInt, message))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +73,7 @@ object ErrorResponseConverter {
|
||||
* Returns 500 Internal Server Error.
|
||||
*/
|
||||
def unknownErrorToResponse(e: Throwable, callContext: CallContext): IO[Response[IO]] = {
|
||||
val errorJson = OBPErrorResponse(500, s"$UnknownError: ${e.getMessage}")
|
||||
val errorJson = OBPErrorResponse(500, UnknownError)
|
||||
IO.pure(
|
||||
Response[IO](org.http4s.Status.InternalServerError)
|
||||
.withEntity(toJsonString(errorJson))
|
||||
|
||||
@ -106,9 +106,14 @@ object Http4sRequestAttributes {
|
||||
def executeAndRespond[A](req: Request[IO])(f: CallContext => Future[A])(implicit formats: Formats): IO[Response[IO]] = {
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
result <- IO.fromFuture(IO(f(cc)))
|
||||
jsonString = prettyRender(Extraction.decompose(result))
|
||||
response <- Ok(jsonString)
|
||||
attempted <- IO.fromFuture(IO(f(cc))).attempt
|
||||
response <- attempted match {
|
||||
case Right(result) =>
|
||||
val jsonString = prettyRender(Extraction.decompose(result))
|
||||
Ok(jsonString)
|
||||
case Left(error) =>
|
||||
ErrorResponseConverter.toHttp4sResponse(error, cc)
|
||||
}
|
||||
} yield response
|
||||
}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ import org.http4s.dsl.io._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
import scala.language.{higherKinds, implicitConversions}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import code.util.Helper
|
||||
|
||||
object Http4s700 {
|
||||
|
||||
@ -217,16 +217,24 @@ object Http4s700 {
|
||||
.map(_.trim)
|
||||
.filter(_.nonEmpty)
|
||||
|
||||
Try(ApiVersionUtils.valueOf(requestedApiVersionString)) match {
|
||||
case Success(requestedApiVersion) if requestedApiVersion == ApiVersion.v7_0_0 =>
|
||||
val http4sOnlyDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs.toList, tags, functions)
|
||||
EndpointHelpers.executeAndRespond(req) { _ =>
|
||||
Future.successful(JSONFactory1_4_0.createResourceDocsJson(http4sOnlyDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true))
|
||||
EndpointHelpers.executeAndRespond(req) { _ =>
|
||||
for {
|
||||
requestedApiVersion <- NewStyle.function.tryons(
|
||||
failMsg = s"$InvalidApiVersionString Current value: $requestedApiVersionString",
|
||||
failCode = 400,
|
||||
callContext = Some(cc)
|
||||
) {
|
||||
ApiVersionUtils.valueOf(requestedApiVersionString)
|
||||
}
|
||||
case Success(_) =>
|
||||
ErrorResponseConverter.createErrorResponse(400, s"API Version not supported by this server: $requestedApiVersionString", cc)
|
||||
case Failure(_) =>
|
||||
ErrorResponseConverter.createErrorResponse(400, s"Invalid API Version: $requestedApiVersionString", cc)
|
||||
_ <- Helper.booleanToFuture(
|
||||
failMsg = s"$InvalidApiVersionString This server supports only ${ApiVersion.v7_0_0}. Current value: $requestedApiVersionString",
|
||||
failCode = 400,
|
||||
cc = Some(cc)
|
||||
) {
|
||||
requestedApiVersion == ApiVersion.v7_0_0
|
||||
}
|
||||
http4sOnlyDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs.toList, tags, functions)
|
||||
} yield JSONFactory1_4_0.createResourceDocsJson(http4sOnlyDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package code.api.v7_0_0
|
||||
import cats.effect.IO
|
||||
import cats.effect.unsafe.implicits.global
|
||||
import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc}
|
||||
import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound, UserHasMissingRoles}
|
||||
import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound, InvalidApiVersionString, UserHasMissingRoles}
|
||||
import code.setup.ServerSetupWithTestData
|
||||
import net.liftweb.json.JValue
|
||||
import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString}
|
||||
@ -333,7 +333,8 @@ class Http4s700RoutesTest extends ServerSetupWithTestData {
|
||||
case JObject(fields) =>
|
||||
toFieldMap(fields).get("message") match {
|
||||
case Some(JString(message)) =>
|
||||
message should include("API Version not supported")
|
||||
message should include(InvalidApiVersionString)
|
||||
message should include("v6.0.0")
|
||||
case _ =>
|
||||
fail("Expected message field as JSON string for invalid-version response")
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user