mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:47:18 +00:00
feature(http4s): add EndpointHelpers for simplified endpoint implementations
- Add EndpointHelpers object with reusable endpoint execution patterns - Implement executeAndRespond helper for Future-based business logic execution - Implement withUser helper to extract and validate User from CallContext - Implement withBank helper to extract and validate Bank from CallContext - Implement withUserAndBank helper for endpoints requiring both User and Bank - Add comprehensive documentation and usage examples for each helper - Import EndpointHelpers in Http4s700 for endpoint implementation - Reduce boilerplate in endpoint implementations by centralizing common patterns - Improve code consistency and maintainability across http4s endpoints
This commit is contained in:
parent
df54e60fd0
commit
11e4a71cc4
@ -11,12 +11,14 @@ import net.liftweb.http.provider.HTTPParam
|
||||
import net.liftweb.json.{Extraction, compactRender}
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import org.http4s._
|
||||
import org.http4s.dsl.io._
|
||||
import org.http4s.headers.`Content-Type`
|
||||
import org.typelevel.ci.CIString
|
||||
import org.typelevel.vault.Key
|
||||
|
||||
import java.util.{Date, UUID}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
import scala.language.higherKinds
|
||||
|
||||
/**
|
||||
@ -78,6 +80,135 @@ object Http4sRequestAttributes {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper methods to simplify endpoint implementations.
|
||||
* These eliminate boilerplate for common patterns in http4s endpoints.
|
||||
*/
|
||||
object EndpointHelpers {
|
||||
import net.liftweb.json.{Extraction, Formats}
|
||||
import net.liftweb.json.JsonAST.prettyRender
|
||||
|
||||
/**
|
||||
* Execute a Future-based business logic function and return JSON response.
|
||||
* Handles Future execution, JSON conversion, and Ok response creation.
|
||||
*
|
||||
* Usage:
|
||||
* {{{
|
||||
* case req @ GET -> Root / "banks" =>
|
||||
* executeAndRespond(req) { implicit cc =>
|
||||
* for {
|
||||
* (banks, callContext) <- NewStyle.function.getBanks(Some(cc))
|
||||
* } yield JSONFactory400.createBanksJson(banks)
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @param req The http4s request
|
||||
* @param f Business logic function that takes CallContext and returns Future[A]
|
||||
* @param formats Implicit JSON formats for serialization
|
||||
* @tparam A The result type (will be converted to JSON)
|
||||
* @return IO[Response[IO]] with JSON body
|
||||
*/
|
||||
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)
|
||||
} yield response
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute business logic that requires validated User from CallContext.
|
||||
* Extracts User from CallContext, executes business logic, and returns JSON response.
|
||||
*
|
||||
* Usage:
|
||||
* {{{
|
||||
* case req @ GET -> Root / "cards" =>
|
||||
* withUser(req) { (user, cc) =>
|
||||
* for {
|
||||
* (cards, callContext) <- NewStyle.function.getPhysicalCardsForUser(user, Some(cc))
|
||||
* } yield JSONFactory1_3_0.createPhysicalCardsJSON(cards, user)
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @param req The http4s request
|
||||
* @param f Business logic function that takes (User, CallContext) and returns Future[A]
|
||||
* @param formats Implicit JSON formats for serialization
|
||||
* @tparam A The result type (will be converted to JSON)
|
||||
* @return IO[Response[IO]] with JSON body
|
||||
*/
|
||||
def withUser[A](req: Request[IO])(f: (User, CallContext) => Future[A])(implicit formats: Formats): IO[Response[IO]] = {
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
user <- IO.fromOption(cc.user.toOption)(new RuntimeException("User not found in CallContext"))
|
||||
result <- IO.fromFuture(IO(f(user, cc)))
|
||||
jsonString = prettyRender(Extraction.decompose(result))
|
||||
response <- Ok(jsonString)
|
||||
} yield response
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute business logic that requires validated Bank from CallContext.
|
||||
* Extracts Bank from CallContext, executes business logic, and returns JSON response.
|
||||
*
|
||||
* Usage:
|
||||
* {{{
|
||||
* case req @ GET -> Root / "banks" / bankId / "accounts" =>
|
||||
* withBank(req) { (bank, cc) =>
|
||||
* for {
|
||||
* (accounts, callContext) <- NewStyle.function.getBankAccounts(bank, Some(cc))
|
||||
* } yield JSONFactory400.createAccountsJson(accounts)
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @param req The http4s request
|
||||
* @param f Business logic function that takes (Bank, CallContext) and returns Future[A]
|
||||
* @param formats Implicit JSON formats for serialization
|
||||
* @tparam A The result type (will be converted to JSON)
|
||||
* @return IO[Response[IO]] with JSON body
|
||||
*/
|
||||
def withBank[A](req: Request[IO])(f: (Bank, CallContext) => Future[A])(implicit formats: Formats): IO[Response[IO]] = {
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
bank <- IO.fromOption(cc.bank)(new RuntimeException("Bank not found in CallContext"))
|
||||
result <- IO.fromFuture(IO(f(bank, cc)))
|
||||
jsonString = prettyRender(Extraction.decompose(result))
|
||||
response <- Ok(jsonString)
|
||||
} yield response
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute business logic that requires both User and Bank from CallContext.
|
||||
* Extracts both from CallContext, executes business logic, and returns JSON response.
|
||||
*
|
||||
* Usage:
|
||||
* {{{
|
||||
* case req @ GET -> Root / "banks" / bankId / "cards" =>
|
||||
* withUserAndBank(req) { (user, bank, cc) =>
|
||||
* for {
|
||||
* (cards, callContext) <- NewStyle.function.getPhysicalCardsForBank(bank, user, obpQueryParams, Some(cc))
|
||||
* } yield JSONFactory1_3_0.createPhysicalCardsJSON(cards, user)
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @param req The http4s request
|
||||
* @param f Business logic function that takes (User, Bank, CallContext) and returns Future[A]
|
||||
* @param formats Implicit JSON formats for serialization
|
||||
* @tparam A The result type (will be converted to JSON)
|
||||
* @return IO[Response[IO]] with JSON body
|
||||
*/
|
||||
def withUserAndBank[A](req: Request[IO])(f: (User, Bank, CallContext) => Future[A])(implicit formats: Formats): IO[Response[IO]] = {
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
user <- IO.fromOption(cc.user.toOption)(new RuntimeException("User not found in CallContext"))
|
||||
bank <- IO.fromOption(cc.bank)(new RuntimeException("Bank not found in CallContext"))
|
||||
result <- IO.fromFuture(IO(f(user, bank, cc)))
|
||||
jsonString = prettyRender(Extraction.decompose(result))
|
||||
response <- Ok(jsonString)
|
||||
} yield response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -10,7 +10,7 @@ import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.http4s.{Http4sRequestAttributes, ResourceDocMiddleware}
|
||||
import code.api.util.http4s.Http4sRequestAttributes.RequestOps
|
||||
import code.api.util.http4s.Http4sRequestAttributes.{RequestOps, EndpointHelpers}
|
||||
import code.api.util.{ApiRole, ApiVersionUtils, CallContext, CustomJsonFormats, NewStyle}
|
||||
import code.api.v1_3_0.JSONFactory1_3_0
|
||||
import code.api.v1_4_0.JSONFactory1_4_0
|
||||
@ -105,15 +105,11 @@ object Http4s700 {
|
||||
// Route: GET /obp/v7.0.0/banks
|
||||
val getBanks: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "banks" =>
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
result <- IO.fromFuture(IO {
|
||||
EndpointHelpers.executeAndRespond(req) { implicit cc =>
|
||||
for {
|
||||
(banks, callContext) <- NewStyle.function.getBanks(Some(cc))
|
||||
} yield convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
|
||||
})
|
||||
response <- Ok(result)
|
||||
} yield response
|
||||
} yield JSONFactory400.createBanksJson(banks)
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
@ -135,16 +131,11 @@ object Http4s700 {
|
||||
// Authentication handled by ResourceDocMiddleware based on AuthenticatedUserIsRequired
|
||||
val getCards: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "cards" =>
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
user <- IO.fromOption(cc.user.toOption)(new RuntimeException("User not found in CallContext"))
|
||||
result <- IO.fromFuture(IO {
|
||||
EndpointHelpers.withUser(req) { (user, cc) =>
|
||||
for {
|
||||
(cards, callContext) <- NewStyle.function.getPhysicalCardsForUser(user, Some(cc))
|
||||
} yield convertAnyToJsonString(JSONFactory1_3_0.createPhysicalCardsJSON(cards, user))
|
||||
})
|
||||
response <- Ok(result)
|
||||
} yield response
|
||||
} yield JSONFactory1_3_0.createPhysicalCardsJSON(cards, user)
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
@ -167,19 +158,13 @@ object Http4s700 {
|
||||
// Authentication and bank validation handled by ResourceDocMiddleware
|
||||
val getCardsForBank: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "banks" / bankId / "cards" =>
|
||||
implicit val cc: CallContext = req.callContext
|
||||
for {
|
||||
user <- IO.fromOption(cc.user.toOption)(new RuntimeException("User not found in CallContext"))
|
||||
bank <- IO.fromOption(cc.bank)(new RuntimeException("Bank not found in CallContext"))
|
||||
result <- IO.fromFuture(IO {
|
||||
EndpointHelpers.withUserAndBank(req) { (user, bank, cc) =>
|
||||
for {
|
||||
httpParams <- NewStyle.function.extractHttpParamsFromUrl(req.uri.renderString)
|
||||
(obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, Some(cc))
|
||||
(cards, callContext) <- NewStyle.function.getPhysicalCardsForBank(bank, user, obpQueryParams, callContext)
|
||||
} yield convertAnyToJsonString(JSONFactory1_3_0.createPhysicalCardsJSON(cards, user))
|
||||
})
|
||||
response <- Ok(result)
|
||||
} yield response
|
||||
} yield JSONFactory1_3_0.createPhysicalCardsJSON(cards, user)
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user