Skip to content

Commit 8769853

Browse files
authored
Merge pull request #2653 from hongwei1/refactor/AddedResourceDocs
Refactor/added resource docs
2 parents bbc4401 + c99cb73 commit 8769853

File tree

9 files changed

+146
-52
lines changed

9 files changed

+146
-52
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*.code-workspace
1212
.zed
1313
.cursor
14+
.trae
1415
.classpath
1516
.project
1617
.cache

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ MAVEN_OPTS="-Xms3G -Xmx6G -XX:MaxMetaspaceSize=2G" mvn -pl obp-http4s-runner -am
7676
java -jar obp-http4s-runner/target/obp-http4s-runner.jar
7777
```
7878

79-
The http4s server binds to `http4s.host` / `http4s.port` as configured in your props file (defaults are `127.0.0.1` and `8181`).
79+
The http4s server binds to `http4s.host` / `http4s.port` as configured in your props file (defaults are `127.0.0.1` and `8086`).
8080

8181
### ZED IDE Setup
8282

obp-api/src/main/resources/props/sample.props.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,6 @@ securelogging_mask_email=true
16911691
############################################
16921692

16931693
# Host and port for http4s server (used by bootstrap.http4s.Http4sServer)
1694-
# Defaults (if not set) are 127.0.0.1 and 8181
1694+
# Defaults (if not set) are 127.0.0.1 and 8086
16951695
http4s.host=127.0.0.1
16961696
http4s.port=8086

obp-api/src/main/scala/bootstrap/http4s/Http4sServer.scala

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@ import org.http4s.implicits._
1111
import scala.language.higherKinds
1212
object Http4sServer extends IOApp {
1313

14-
val services: Kleisli[({type λ[β$0$] = OptionT[IO, β$0$]})#λ, Request[IO], Response[IO]] =
15-
code.api.v7_0_0.Http4s700.wrappedRoutesV700Services
16-
17-
val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound
18-
19-
//Start OBP relevant objects, and settings
14+
//Start OBP relevant objects and settings; this step MUST be executed first
2015
new bootstrap.http4s.Http4sBoot().boot
2116

22-
val port = APIUtil.getPropsAsIntValue("http4s.port",8181)
17+
val port = APIUtil.getPropsAsIntValue("http4s.port",8086)
2318
val host = APIUtil.getPropsValue("http4s.host","127.0.0.1")
2419

20+
val services: HttpRoutes[IO] = code.api.v7_0_0.Http4s700.wrappedRoutesV700Services
21+
22+
val httpApp: Kleisli[IO, Request[IO], Response[IO]] = (services).orNotFound
2523

2624
override def run(args: List[String]): IO[ExitCode] = EmberServerBuilder
2725
.default[IO]

obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package code.api.ResourceDocs1_4_0
22

3-
import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, PARAM_LOCALE, HostName}
3+
import code.api.Constant.{GET_DYNAMIC_RESOURCE_DOCS_TTL, GET_STATIC_RESOURCE_DOCS_TTL, HostName, PARAM_LOCALE}
44
import code.api.OBPRestHelper
55
import code.api.cache.Caching
6+
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
7+
import code.api.dynamic.entity.OBPAPIDynamicEntity
68
import code.api.util.APIUtil._
79
import code.api.util.ApiRole.{canReadDynamicResourceDocsAtOneBank, canReadResourceDoc}
810
import code.api.util.ApiTag._
@@ -20,19 +22,17 @@ import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0}
2022
import code.api.v5_0_0.OBPAPI5_0_0
2123
import code.api.v5_1_0.OBPAPI5_1_0
2224
import code.api.v6_0_0.OBPAPI6_0_0
23-
import code.api.dynamic.endpoint.OBPAPIDynamicEndpoint
24-
import code.api.dynamic.entity.OBPAPIDynamicEntity
2525
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
2626
import code.util.Helper
2727
import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN}
28-
import net.liftweb.http.S
2928
import com.github.dwickern.macros.NameOf.nameOf
3029
import com.openbankproject.commons.model.enums.ContentParam
3130
import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC}
3231
import com.openbankproject.commons.model.{BankId, ListResult, User}
3332
import com.openbankproject.commons.util.ApiStandards._
3433
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
3534
import net.liftweb.common.{Box, Empty, Full}
35+
import net.liftweb.http.{LiftRules, S}
3636
import net.liftweb.http.{InMemoryResponse, LiftRules, PlainTextResponse}
3737
import net.liftweb.json
3838
import net.liftweb.json.JsonAST.{JField, JString, JValue}
@@ -118,6 +118,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
118118
logger.debug(s"getResourceDocsList says requestedApiVersion is $requestedApiVersion")
119119

120120
val resourceDocs = requestedApiVersion match {
121+
case ApiVersion.v7_0_0 => code.api.v7_0_0.Http4s700.resourceDocs
121122
case ApiVersion.v6_0_0 => OBPAPI6_0_0.allResourceDocs
122123
case ApiVersion.v5_1_0 => OBPAPI5_1_0.allResourceDocs
123124
case ApiVersion.v5_0_0 => OBPAPI5_0_0.allResourceDocs
@@ -139,6 +140,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
139140
logger.debug(s"There are ${resourceDocs.length} resource docs available to $requestedApiVersion")
140141

141142
val versionRoutes = requestedApiVersion match {
143+
case ApiVersion.v7_0_0 => Nil
142144
case ApiVersion.v6_0_0 => OBPAPI6_0_0.routes
143145
case ApiVersion.v5_1_0 => OBPAPI5_1_0.routes
144146
case ApiVersion.v5_0_0 => OBPAPI5_0_0.routes
@@ -165,7 +167,10 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
165167
val versionRoutesClasses = versionRoutes.map { vr => vr.getClass }
166168

167169
// Only return the resource docs that have available routes
168-
val activeResourceDocs = resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
170+
val activeResourceDocs = requestedApiVersion match {
171+
case ApiVersion.v7_0_0 => resourceDocs
172+
case _ => resourceDocs.filter(rd => versionRoutesClasses.contains(rd.partialFunction.getClass))
173+
}
169174

170175
logger.debug(s"There are ${activeResourceDocs.length} resource docs available to $requestedApiVersion")
171176

@@ -1251,4 +1256,3 @@ so the caller must specify any required filtering by catalog explicitly.
12511256

12521257

12531258
}
1254-

obp-api/src/main/scala/code/api/util/APIUtil.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ TESOBE (http://www.tesobe.com/)
2727

2828
package code.api.util
2929
import bootstrap.liftweb.CustomDBVendor
30+
import cats.effect.IO
3031
import code.accountholders.AccountHolders
3132
import code.api.Constant._
3233
import code.api.OAuthHandshake._
@@ -96,6 +97,7 @@ import net.liftweb.util.Helpers._
9697
import net.liftweb.util._
9798
import org.apache.commons.io.IOUtils
9899
import org.apache.commons.lang3.StringUtils
100+
import org.http4s.HttpRoutes
99101

100102
import java.io.InputStream
101103
import java.net.URLDecoder
@@ -1636,7 +1638,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
16361638
isFeatured: Boolean = false,
16371639
specialInstructions: Option[String] = None,
16381640
var specifiedUrl: Option[String] = None, // A derived value: Contains the called version (added at run time). See the resource doc for resource doc!
1639-
createdByBankId: Option[String] = None //we need to filter the resource Doc by BankId
1641+
createdByBankId: Option[String] = None, //we need to filter the resource Doc by BankId
1642+
http4sPartialFunction: Http4sEndpoint = None // http4s endpoint handler
16401643
) {
16411644
// this code block will be merged to constructor.
16421645
{
@@ -2789,6 +2792,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
27892792

27902793
type OBPEndpoint = PartialFunction[Req, CallContext => Box[JsonResponse]]
27912794
type OBPReturnType[T] = Future[(T, Option[CallContext])]
2795+
type Http4sEndpoint = Option[HttpRoutes[IO]]
27922796

27932797

27942798
def getAllowedEndpoints (endpoints : Iterable[OBPEndpoint], resourceDocs: ArrayBuffer[ResourceDoc]) : List[OBPEndpoint] = {

obp-api/src/main/scala/code/api/util/ApiVersionUtils.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ object ApiVersionUtils {
1919
v5_0_0 ::
2020
v5_1_0 ::
2121
v6_0_0 ::
22+
v7_0_0 ::
2223
`dynamic-endpoint` ::
2324
`dynamic-entity` ::
2425
scannedApis
@@ -41,6 +42,7 @@ object ApiVersionUtils {
4142
case v5_0_0.fullyQualifiedVersion | v5_0_0.apiShortVersion => v5_0_0
4243
case v5_1_0.fullyQualifiedVersion | v5_1_0.apiShortVersion => v5_1_0
4344
case v6_0_0.fullyQualifiedVersion | v6_0_0.apiShortVersion => v6_0_0
45+
case v7_0_0.fullyQualifiedVersion | v7_0_0.apiShortVersion => v7_0_0
4446
case `dynamic-endpoint`.fullyQualifiedVersion | `dynamic-endpoint`.apiShortVersion => `dynamic-endpoint`
4547
case `dynamic-entity`.fullyQualifiedVersion | `dynamic-entity`.apiShortVersion => `dynamic-entity`
4648
case version if(scannedApis.map(_.fullyQualifiedVersion).contains(version))

obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala

Lines changed: 120 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@ package code.api.v7_0_0
22

33
import cats.data.{Kleisli, OptionT}
44
import cats.effect._
5-
import cats.implicits._
6-
import code.api.util.{APIUtil, CustomJsonFormats}
5+
import code.api.Constant._
6+
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
7+
import code.api.ResourceDocs1_4_0.{ResourceDocs140, ResourceDocsAPIMethodsUtil}
8+
import code.api.util.APIUtil.{EmptyBody, _}
9+
import code.api.util.ApiTag._
10+
import code.api.util.ErrorMessages._
11+
import code.api.util.{ApiVersionUtils, CustomJsonFormats, NewStyle}
12+
import code.api.v1_4_0.JSONFactory1_4_0
713
import code.api.v4_0_0.JSONFactory400
8-
import code.bankconnectors.Connector
9-
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
10-
import net.liftweb.json.Formats
14+
import com.github.dwickern.macros.NameOf.nameOf
15+
import com.openbankproject.commons.ExecutionContext.Implicits.global
16+
import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus, ScannedApiVersion}
1117
import net.liftweb.json.JsonAST.prettyRender
12-
import net.liftweb.json.Extraction
18+
import net.liftweb.json.{Extraction, Formats}
1319
import org.http4s._
1420
import org.http4s.dsl.io._
1521
import org.typelevel.vault.Key
1622

23+
import scala.collection.mutable.ArrayBuffer
1724
import scala.concurrent.Future
1825
import scala.language.{higherKinds, implicitConversions}
1926

@@ -24,12 +31,13 @@ object Http4s700 {
2431
implicit val formats: Formats = CustomJsonFormats.formats
2532
implicit def convertAnyToJsonString(any: Any): String = prettyRender(Extraction.decompose(any))
2633

27-
val apiVersion: ScannedApiVersion = ApiVersion.v7_0_0
28-
val apiVersionString: String = apiVersion.toString
34+
val implementedInApiVersion: ScannedApiVersion = ApiVersion.v7_0_0
35+
val versionStatus = ApiVersionStatus.STABLE.toString
36+
val resourceDocs = ArrayBuffer[ResourceDoc]()
2937

3038
case class CallContext(userId: String, requestId: String)
31-
import cats.effect.unsafe.implicits.global
32-
val callContextKey: Key[CallContext] = Key.newKey[IO, CallContext].unsafeRunSync()
39+
val callContextKey: Key[CallContext] =
40+
Key.newKey[IO, CallContext].unsafeRunSync()(cats.effect.unsafe.IORuntime.global)
3341

3442
object CallContextMiddleware {
3543

@@ -42,31 +50,108 @@ object Http4s700 {
4250
}
4351
}
4452

45-
val v700Services: HttpRoutes[IO] = HttpRoutes.of[IO] {
46-
case req @ GET -> Root / "obp" / `apiVersionString` / "root" =>
47-
import com.openbankproject.commons.ExecutionContext.Implicits.global
48-
val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext]
49-
Ok(IO.fromFuture(IO(
50-
for {
51-
_ <- Future() // Just start async call
52-
} yield {
53-
convertAnyToJsonString(
54-
JSONFactory700.getApiInfoJSON(apiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.")
55-
)
56-
}
57-
)))
58-
59-
case req @ GET -> Root / "obp" / `apiVersionString` / "banks" =>
60-
import com.openbankproject.commons.ExecutionContext.Implicits.global
61-
Ok(IO.fromFuture(IO(
62-
for {
63-
(banks, callContext) <- code.api.util.NewStyle.function.getBanks(None)
64-
} yield {
65-
convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
66-
}
67-
)))
53+
object Implementations7_0_0 {
54+
55+
// Common prefix: /obp/v7.0.0
56+
val prefixPath = Root / ApiPathZero.toString / implementedInApiVersion.toString
57+
58+
59+
resourceDocs += ResourceDoc(
60+
null,
61+
implementedInApiVersion,
62+
nameOf(root),
63+
"GET",
64+
"/root",
65+
"Get API Info (root)",
66+
s"""Returns information about:
67+
|
68+
|* API version
69+
|* Hosted by information
70+
|* Git Commit
71+
|${userAuthenticationMessage(false)}""",
72+
EmptyBody,
73+
apiInfoJSON,
74+
List(UnknownError, "no connector set"),
75+
apiTagApi :: Nil,
76+
http4sPartialFunction = Some(root)
77+
)
78+
79+
// Route: GET /obp/v7.0.0/root
80+
val root: HttpRoutes[IO] = HttpRoutes.of[IO] {
81+
case req @ GET -> `prefixPath` / "root" =>
82+
val callContext = req.attributes.lookup(callContextKey).get.asInstanceOf[CallContext]
83+
Ok(IO.fromFuture(IO(
84+
for {
85+
_ <- Future() // Just start async call
86+
} yield {
87+
convertAnyToJsonString(
88+
JSONFactory700.getApiInfoJSON(implementedInApiVersion, s"Hello, ${callContext.userId}! Your request ID is ${callContext.requestId}.")
89+
)
90+
}
91+
)))
92+
}
93+
94+
resourceDocs += ResourceDoc(
95+
null,
96+
implementedInApiVersion,
97+
nameOf(getBanks),
98+
"GET",
99+
"/banks",
100+
"Get Banks",
101+
s"""Get banks on this API instance
102+
|Returns a list of banks supported on this server:
103+
|
104+
|* ID used as parameter in URLs
105+
|* Short and full name of bank
106+
|* Logo URL
107+
|* Website
108+
|${userAuthenticationMessage(false)}""",
109+
EmptyBody,
110+
banksJSON,
111+
List(UnknownError),
112+
apiTagBank :: Nil,
113+
http4sPartialFunction = Some(getBanks)
114+
)
115+
116+
// Route: GET /obp/v7.0.0/banks
117+
val getBanks: HttpRoutes[IO] = HttpRoutes.of[IO] {
118+
case req @ GET -> `prefixPath` / "banks" =>
119+
import com.openbankproject.commons.ExecutionContext.Implicits.global
120+
Ok(IO.fromFuture(IO(
121+
for {
122+
(banks, callContext) <- NewStyle.function.getBanks(None)
123+
} yield {
124+
convertAnyToJsonString(JSONFactory400.createBanksJson(banks))
125+
}
126+
)))
127+
}
128+
129+
val getResourceDocsObpV700: HttpRoutes[IO] = HttpRoutes.of[IO] {
130+
case req @ GET -> `prefixPath` / "resource-docs" / requestedApiVersionString / "obp" =>
131+
import com.openbankproject.commons.ExecutionContext.Implicits.global
132+
val logic = for {
133+
httpParams <- NewStyle.function.extractHttpParamsFromUrl(req.uri.renderString)
134+
tagsParam = httpParams.filter(_.name == "tags").map(_.values).headOption
135+
functionsParam = httpParams.filter(_.name == "functions").map(_.values).headOption
136+
localeParam = httpParams.filter(param => param.name == "locale" || param.name == "language").map(_.values).flatten.headOption
137+
contentParam = httpParams.filter(_.name == "content").map(_.values).flatten.flatMap(ResourceDocsAPIMethodsUtil.stringToContentParam).headOption
138+
apiCollectionIdParam = httpParams.filter(_.name == "api-collection-id").map(_.values).flatten.headOption
139+
tags = tagsParam.map(_.map(ResourceDocTag(_)))
140+
functions = functionsParam.map(_.toList)
141+
requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString))
142+
resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil)
143+
filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions)
144+
resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam)
145+
} yield convertAnyToJsonString(resourceDocsJson)
146+
Ok(IO.fromFuture(IO(logic)))
147+
}
148+
149+
// All routes combined
150+
val allRoutes: HttpRoutes[IO] =
151+
Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] =>
152+
root(req).orElse(getBanks(req)).orElse(getResourceDocsObpV700(req))
153+
}
68154
}
69155

70-
val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(v700Services)
156+
val wrappedRoutesV700Services: HttpRoutes[IO] = CallContextMiddleware.withCallContext(Implementations7_0_0.allRoutes)
71157
}
72-

obp-api/src/test/scala/code/util/ApiVersionUtilsTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ class ApiVersionUtilsTest extends V400ServerSetup {
2020
versions.map(version => ApiVersionUtils.valueOf(version.fullyQualifiedVersion))
2121

2222
//NOTE, when we added the new version, better fix this number manually. and also check the versions
23-
versions.length shouldBe(24)
23+
versions.length shouldBe(25)
2424
}}
2525
}

0 commit comments

Comments
 (0)