mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:27:05 +00:00
Merge pull request #2653 from hongwei1/refactor/AddedResourceDocs
Refactor/added resource docs
This commit is contained in:
commit
876985325c
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@
|
||||
*.code-workspace
|
||||
.zed
|
||||
.cursor
|
||||
.trae
|
||||
.classpath
|
||||
.project
|
||||
.cache
|
||||
|
||||
@ -76,7 +76,7 @@ MAVEN_OPTS="-Xms3G -Xmx6G -XX:MaxMetaspaceSize=2G" mvn -pl obp-http4s-runner -am
|
||||
java -jar obp-http4s-runner/target/obp-http4s-runner.jar
|
||||
```
|
||||
|
||||
The http4s server binds to `http4s.host` / `http4s.port` as configured in your props file (defaults are `127.0.0.1` and `8181`).
|
||||
The http4s server binds to `http4s.host` / `http4s.port` as configured in your props file (defaults are `127.0.0.1` and `8086`).
|
||||
|
||||
### ZED IDE Setup
|
||||
|
||||
|
||||
@ -1691,6 +1691,6 @@ securelogging_mask_email=true
|
||||
############################################
|
||||
|
||||
# Host and port for http4s server (used by bootstrap.http4s.Http4sServer)
|
||||
# Defaults (if not set) are 127.0.0.1 and 8181
|
||||
# Defaults (if not set) are 127.0.0.1 and 8086
|
||||
http4s.host=127.0.0.1
|
||||
http4s.port=8086
|
||||
@ -11,17 +11,15 @@ import org.http4s.implicits._
|
||||
import scala.language.higherKinds
|
||||
object Http4sServer extends IOApp {
|
||||
|
||||
val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] =
|
||||
code.api.v7_0_0.Http4s700.wrappedRoutesV700Services
|
||||
|
||||
val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound
|
||||
|
||||
//Start OBP relevant objects, and settings
|
||||
//Start OBP relevant objects and settings; this step MUST be executed first
|
||||
new bootstrap.http4s.Http4sBoot().boot
|
||||
|
||||
val port = APIUtil.getPropsAsIntValue("http4s.port",8181)
|
||||
val port = APIUtil.getPropsAsIntValue("http4s.port",8086)
|
||||
val host = APIUtil.getPropsValue("http4s.host","127.0.0.1")
|
||||
|
||||
val services: HttpRoutes[IO] = code.api.v7_0_0.Http4s700.wrappedRoutesV700Services
|
||||
|
||||
val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound
|
||||
|
||||
override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder
|
||||
.default[IO]
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
package code.api.ResourceDocs1_4_0
|
||||
|
||||
import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, PARAM_LOCALE, HostName}
|
||||
import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, HostName, PARAM_LOCALE}
|
||||
import code.api.OBPRestHelper
|
||||
import code.api.cache.Caching
|
||||
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
|
||||
import code.api.dynamic.entity.OBPAPIDynamicEntity
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ApiRole.{canReadDynamicResourceDocsAtOneBank, canReadResourceDoc}
|
||||
import code.api.util.ApiTag._
|
||||
@ -20,12 +22,9 @@ import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
|
||||
import code.api.v5_0_0.OBPAPI5_0_0
|
||||
import code.api.v5_1_0.OBPAPI5_1_0
|
||||
import code.api.v6_0_0.OBPAPI6_0_0
|
||||
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
|
||||
import code.api.dynamic.entity.OBPAPIDynamicEntity
|
||||
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
|
||||
import code.util.Helper
|
||||
import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN}
|
||||
import net.liftweb.http.S
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.model.enums.ContentParam
|
||||
import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC}
|
||||
@ -33,6 +32,7 @@ import com.openbankproject.commons.model.{BankId, ListResult, User}
|
||||
import com.openbankproject.commons.util.ApiStandards._
|
||||
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.http.{LiftRules, S}
|
||||
import net.liftweb.http.{InMemoryResponse, LiftRules, PlainTextResponse}
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.JsonAST.{JField, JString, JValue}
|
||||
@ -118,6 +118,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
||||
logger.debug(s"getResourceDocsList says requestedApiVersion is $requestedApiVersion")
|
||||
|
||||
val resourceDocs = requestedApiVersion match {
|
||||
case ApiVersion.v7_0_0 => code.api.v7_0_0.Http4s700.resourceDocs
|
||||
case ApiVersion.v6_0_0 => OBPAPI6_0_0.allResourceDocs
|
||||
case ApiVersion.v5_1_0 => OBPAPI5_1_0.allResourceDocs
|
||||
case ApiVersion.v5_0_0 => OBPAPI5_0_0.allResourceDocs
|
||||
@ -139,6 +140,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
||||
logger.debug(s"There are ${resourceDocs.length} resource docs available to $requestedApiVersion")
|
||||
|
||||
val versionRoutes = requestedApiVersion match {
|
||||
case ApiVersion.v7_0_0 => Nil
|
||||
case ApiVersion.v6_0_0 => OBPAPI6_0_0.routes
|
||||
case ApiVersion.v5_1_0 => OBPAPI5_1_0.routes
|
||||
case ApiVersion.v5_0_0 => OBPAPI5_0_0.routes
|
||||
@ -165,7 +167,10 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
||||
val versionRoutesClasses = versionRoutes.map { vr => vr.getClass }
|
||||
|
||||
// Only return the resource docs that have available routes
|
||||
val activeResourceDocs = resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
|
||||
val activeResourceDocs = requestedApiVersion match {
|
||||
case ApiVersion.v7_0_0 => resourceDocs
|
||||
case _ => resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
|
||||
}
|
||||
|
||||
logger.debug(s"There are ${activeResourceDocs.length} resource docs available to $requestedApiVersion")
|
||||
|
||||
@ -1251,4 +1256,3 @@ so the caller must specify any required filtering by catalog explicitly.
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ TESOBE (http://www.tesobe.com/)
|
||||
|
||||
package code.api.util
|
||||
import bootstrap.liftweb.CustomDBVendor
|
||||
import cats.effect.IO
|
||||
import code.accountholders.AccountHolders
|
||||
import code.api.Constant._
|
||||
import code.api.OAuthHandshake._
|
||||
@ -96,6 +97,7 @@ import net.liftweb.util.Helpers._
|
||||
import net.liftweb.util._
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.http4s.HttpRoutes
|
||||
|
||||
import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
@ -1636,7 +1638,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
isFeatured: Boolean = false,
|
||||
specialInstructions: Option[String] = None,
|
||||
var specifiedUrl: Option[String] = None, // A derived value: Contains the called version (added at run time). See the resource doc for resource doc!
|
||||
createdByBankId: Option[String] = None //we need to filter the resource Doc by BankId
|
||||
createdByBankId: Option[String] = None, //we need to filter the resource Doc by BankId
|
||||
http4sPartialFunction: Http4sEndpoint = None // http4s endpoint handler
|
||||
) {
|
||||
// this code block will be merged to constructor.
|
||||
{
|
||||
@ -2789,6 +2792,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
|
||||
type OBPEndpoint = PartialFunction[Req, CallContext => Box[JsonResponse]]
|
||||
type OBPReturnType[T] = Future[(T, Option[CallContext])]
|
||||
type Http4sEndpoint = Option[HttpRoutes[IO]]
|
||||
|
||||
|
||||
def getAllowedEndpoints (endpoints : Iterable[OBPEndpoint], resourceDocs: ArrayBuffer[ResourceDoc]) : List[OBPEndpoint] = {
|
||||
|
||||
@ -19,6 +19,7 @@ object ApiVersionUtils {
|
||||
v5_0_0 ::
|
||||
v5_1_0 ::
|
||||
v6_0_0 ::
|
||||
v7_0_0 ::
|
||||
`dynamic-endpoint` ::
|
||||
`dynamic-entity` ::
|
||||
scannedApis
|
||||
@ -41,6 +42,7 @@ object ApiVersionUtils {
|
||||
case v5_0_0.fullyQualifiedVersion | v5_0_0.apiShortVersion => v5_0_0
|
||||
case v5_1_0.fullyQualifiedVersion | v5_1_0.apiShortVersion => v5_1_0
|
||||
case v6_0_0.fullyQualifiedVersion | v6_0_0.apiShortVersion => v6_0_0
|
||||
case v7_0_0.fullyQualifiedVersion | v7_0_0.apiShortVersion => v7_0_0
|
||||
case `dynamic-endpoint`.fullyQualifiedVersion | `dynamic-endpoint`.apiShortVersion => `dynamic-endpoint`
|
||||
case `dynamic-entity`.fullyQualifiedVersion | `dynamic-entity`.apiShortVersion => `dynamic-entity`
|
||||
case version if(scannedApis.map(_.fullyQualifiedVersion).contains(version))
|
||||
|
||||
@ -2,18 +2,25 @@ package code.api.v7_0_0
|
||||
|
||||
import cats.data.{Kleisli, OptionT}
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
import code.api.util.{APIUtil, CustomJsonFormats}
|
||||
import code.api.Constant._
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
|
||||
import code.api.ResourceDocs1_4_0.{ResourceDocs140, ResourceDocsAPIMethodsUtil}
|
||||
import code.api.util.APIUtil.{EmptyBody, _}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.api.util.{ApiVersionUtils, CustomJsonFormats, NewStyle}
|
||||
import code.api.v1_4_0.JSONFactory1_4_0
|
||||
import code.api.v4_0_0.JSONFactory400
|
||||
import code.bankconnectors.Connector
|
||||
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
|
||||
import net.liftweb.json.Formats
|
||||
import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion}
|
||||
import net.liftweb.json.JsonAST.prettyRender
|
||||
import net.liftweb.json.Extraction
|
||||
import net.liftweb.json.{Extraction, Formats}
|
||||
import org.http4s._
|
||||
import org.http4s.dsl.io._
|
||||
import org.typelevel.vault.Key
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
import scala.language.{higherKinds, implicitConversions}
|
||||
|
||||
@ -24,12 +31,13 @@ object Http4s700 {
|
||||
implicit val formats: Formats = CustomJsonFormats.formats
|
||||
implicit def convertAnyToJsonString(any: Any): String = prettyRender(Extraction.decompose(any))
|
||||
|
||||
val apiVersion: ScannedApiVersion = ApiVersion.v7_0_0
|
||||
val apiVersionString: String = apiVersion.toString
|
||||
val implementedInApiVersion: ScannedApiVersion = ApiVersion.v7_0_0
|
||||
val versionStatus = ApiVersionStatus.STABLE.toString
|
||||
val resourceDocs = ArrayBuffer[ResourceDoc]()
|
||||
|
||||
case class CallContext(userId: String, requestId: String)
|
||||
import cats.effect.unsafe.implicits.global
|
||||
val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync()
|
||||
val callContextKey: Key[CallContext] =
|
||||
Key.newKey[IO, CallContext].unsafeRunSync()(cats.effect.unsafe.IORuntime.global)
|
||||
|
||||
object CallContextMiddleware {
|
||||
|
||||
@ -42,31 +50,108 @@ object Http4s700 {
|
||||
}
|
||||
}
|
||||
|
||||
val v700Services: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> Root / "obp" / `apiVersionString` / "root" =>
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext]
|
||||
Ok(IO.fromFuture(IO(
|
||||
for {
|
||||
_ <- Future() // Just start async call
|
||||
} yield {
|
||||
convertAnyToJsonString(
|
||||
JSONFactory700.getApiInfoJSON(apiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.")
|
||||
)
|
||||
}
|
||||
)))
|
||||
object Implementations7_0_0 {
|
||||
|
||||
case req @ GET -> Root / "obp" / `apiVersionString` / "banks" =>
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
Ok(IO.fromFuture(IO(
|
||||
for {
|
||||
(banks, callContext) <- code.api.util.NewStyle.function.getBanks(None)
|
||||
} yield {
|
||||
convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
|
||||
}
|
||||
)))
|
||||
// Common prefix: /obp/v7.0.0
|
||||
val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
null,
|
||||
implementedInApiVersion,
|
||||
nameOf(root),
|
||||
"GET",
|
||||
"/root",
|
||||
"Get API Info (root)",
|
||||
s"""Returns information about:
|
||||
|
|
||||
|* API version
|
||||
|* Hosted by information
|
||||
|* Git Commit
|
||||
|${userAuthenticationMessage(false)}""",
|
||||
EmptyBody,
|
||||
apiInfoJSON,
|
||||
List(UnknownError, "no connector set"),
|
||||
apiTagApi :: Nil,
|
||||
http4sPartialFunction = Some(root)
|
||||
)
|
||||
|
||||
// Route: GET /obp/v7.0.0/root
|
||||
val root: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "root" =>
|
||||
val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext]
|
||||
Ok(IO.fromFuture(IO(
|
||||
for {
|
||||
_ <- Future() // Just start async call
|
||||
} yield {
|
||||
convertAnyToJsonString(
|
||||
JSONFactory700.getApiInfoJSON(implementedInApiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.")
|
||||
)
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
null,
|
||||
implementedInApiVersion,
|
||||
nameOf(getBanks),
|
||||
"GET",
|
||||
"/banks",
|
||||
"Get Banks",
|
||||
s"""Get banks on this API instance
|
||||
|Returns a list of banks supported on this server:
|
||||
|
|
||||
|* ID used as parameter in URLs
|
||||
|* Short and full name of bank
|
||||
|* Logo URL
|
||||
|* Website
|
||||
|${userAuthenticationMessage(false)}""",
|
||||
EmptyBody,
|
||||
banksJSON,
|
||||
List(UnknownError),
|
||||
apiTagBank :: Nil,
|
||||
http4sPartialFunction = Some(getBanks)
|
||||
)
|
||||
|
||||
// Route: GET /obp/v7.0.0/banks
|
||||
val getBanks: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "banks" =>
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
Ok(IO.fromFuture(IO(
|
||||
for {
|
||||
(banks, callContext) <- NewStyle.function.getBanks(None)
|
||||
} yield {
|
||||
convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
val getResourceDocsObpV700: HttpRoutes[IO] = HttpRoutes.of[IO] {
|
||||
case req @ GET -> `prefixPath` / "resource-docs" / requestedApiVersionString / "obp" =>
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
val logic = for {
|
||||
httpParams <- NewStyle.function.extractHttpParamsFromUrl(req.uri.renderString)
|
||||
tagsParam = httpParams.filter(_.name == "tags").map(_.values).headOption
|
||||
functionsParam = httpParams.filter(_.name == "functions").map(_.values).headOption
|
||||
localeParam = httpParams.filter(param => param.name == "locale" || param.name == "language").map(_.values).flatten.headOption
|
||||
contentParam = httpParams.filter(_.name == "content").map(_.values).flatten.flatMap(ResourceDocsAPIMethodsUtil.stringToContentParam).headOption
|
||||
apiCollectionIdParam = httpParams.filter(_.name == "api-collection-id").map(_.values).flatten.headOption
|
||||
tags = tagsParam.map(_.map(ResourceDocTag(_)))
|
||||
functions = functionsParam.map(_.toList)
|
||||
requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString))
|
||||
resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil)
|
||||
filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions)
|
||||
resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam)
|
||||
} yield convertAnyToJsonString(resourceDocsJson)
|
||||
Ok(IO.fromFuture(IO(logic)))
|
||||
}
|
||||
|
||||
// All routes combined
|
||||
val allRoutes: HttpRoutes[IO] =
|
||||
Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] =>
|
||||
root(req).orElse(getBanks(req)).orElse(getResourceDocsObpV700(req))
|
||||
}
|
||||
}
|
||||
|
||||
val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(v700Services)
|
||||
val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(Implementations7_0_0.allRoutes)
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,6 @@ class ApiVersionUtilsTest extends V400ServerSetup {
|
||||
versions.map(version => ApiVersionUtils.valueOf(version.fullyQualifiedVersion))
|
||||
|
||||
//NOTE, when we added the new version, better fix this number manually. and also check the versions
|
||||
versions.length shouldBe(24)
|
||||
versions.length shouldBe(25)
|
||||
}}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user