mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 18:46:46 +00:00
dynamic_entity_refactor_simplify: add doc for restconnector dynamicEntityProcess method, fix all found bugs, and refactor code
This commit is contained in:
parent
d0c8e10d38
commit
74fc6f5bfc
@ -36,9 +36,10 @@ object ErrorMessages {
|
||||
|
||||
// DynamicEntity Exceptions (OBP-09XXX)
|
||||
val DynamicEntityNotFoundByDynamicEntityId = "OBP-09001: DynamicEntity not found. Please specify a valid value for dynamic_entity_id."
|
||||
val DynamicEntityEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
|
||||
val DynamicEntityEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
|
||||
val DynamicEntityNameAlreadyExists = "OBP-09002: DynamicEntity's entityName already exists. Please specify a different value for entityName."
|
||||
val DynamicEntityNotExists = "OBP-09003: DynamicEntity not exists. Please check entityName."
|
||||
val DynamicEntityMissArgument = "OBP-09004: DynamicEntity process related argument is missing."
|
||||
val EntityNotFoundByEntityId = "OBP-09005: Entity not found. Please specify a valid value for entityId."
|
||||
|
||||
// General messages (OBP-10XXX)
|
||||
val InvalidJsonFormat = "OBP-10001: Incorrect json format."
|
||||
|
||||
@ -1413,7 +1413,7 @@ object NewStyle {
|
||||
case false => {
|
||||
val entityNameExists = existsDynamicEntity.isDefined
|
||||
// validate whether entityName is exists
|
||||
val errorMsg = s"$DynamicEntityEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
|
||||
val errorMsg = s"$DynamicEntityNameAlreadyExists current entityName is '${dynamicEntity.entityName}'."
|
||||
Helper.booleanToFuture(errorMsg)(!entityNameExists).map { _ =>
|
||||
DynamicEntityProvider.connectorMethodProvider.vend.createOrUpdate(dynamicEntity)
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package code.api.v4_0_0
|
||||
|
||||
import code.api.ChargePolicy
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ApiRole._
|
||||
@ -12,7 +11,8 @@ import code.api.util._
|
||||
import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJsonV140}
|
||||
import code.api.v2_1_0._
|
||||
import code.api.v3_1_0.ListResult
|
||||
import code.dynamicEntity.{DynamicEntityCommons, DynamicEntityDefinition}
|
||||
import code.api.{APIFailureNewStyle, ChargePolicy}
|
||||
import code.dynamicEntity.DynamicEntityCommons
|
||||
import code.model.dataAccess.AuthUser
|
||||
import code.model.toUserExtended
|
||||
import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._
|
||||
@ -23,7 +23,7 @@ import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
|
||||
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
|
||||
import net.liftweb.common.{Box, Failure, Full}
|
||||
import net.liftweb.common.{Box, Full, ParamFailure}
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
import net.liftweb.json.JsonAST.JValue
|
||||
import net.liftweb.json.Serialization.write
|
||||
@ -718,13 +718,6 @@ trait APIMethods400 {
|
||||
}
|
||||
}
|
||||
|
||||
private def validateDynamicEntityJson(metadataJson: JValue) = {
|
||||
val jFields = metadataJson.asInstanceOf[JObject].obj
|
||||
require(jFields.size == 1, "json format for create or update DynamicEntity is not correct, it should have a single key value for structure definition")
|
||||
val JField(_, definition) = jFields.head
|
||||
definition.extract[DynamicEntityDefinition]
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
@ -761,17 +754,12 @@ trait APIMethods400 {
|
||||
for {
|
||||
(Full(u), callContext) <- authorizedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", u.userId, canCreateDynamicEntity, callContext)
|
||||
failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example."
|
||||
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
|
||||
validateDynamicEntityJson(json)
|
||||
|
||||
val JField(entityName, _) = json.asInstanceOf[JObject].obj.head
|
||||
DynamicEntityCommons(entityName, compactRender(json))
|
||||
}
|
||||
|
||||
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData, callContext)
|
||||
jsonObject = json.asInstanceOf[JObject]
|
||||
dynamicEntity = DynamicEntityCommons(jsonObject, None)
|
||||
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext)
|
||||
} yield {
|
||||
val commonsData: DynamicEntityCommons = dynamicEntity
|
||||
val commonsData: DynamicEntityCommons = result
|
||||
(commonsData.jValue, HttpCode.`201`(callContext))
|
||||
}
|
||||
}
|
||||
@ -815,18 +803,11 @@ trait APIMethods400 {
|
||||
(Full(u), callContext) <- authorizedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext)
|
||||
|
||||
failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example."
|
||||
putData <- NewStyle.function.tryons(failMsg, 400, callContext) {
|
||||
validateDynamicEntityJson(json)
|
||||
val JField(name, _) = json.asInstanceOf[JObject].obj.head
|
||||
DynamicEntityCommons(name, compactRender(json), Some(dynamicEntityId))
|
||||
}
|
||||
|
||||
(_, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext)
|
||||
|
||||
Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData, callContext)
|
||||
jsonObject = json.asInstanceOf[JObject]
|
||||
dynamicEntity = DynamicEntityCommons(jsonObject, Some(dynamicEntityId))
|
||||
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(dynamicEntity, callContext)
|
||||
} yield {
|
||||
val commonsData: DynamicEntityCommons = dynamicEntity
|
||||
val commonsData: DynamicEntityCommons = result
|
||||
(commonsData.jValue, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
@ -868,9 +849,11 @@ trait APIMethods400 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private def unboxResult[T](box: Box[T]): T = {
|
||||
if(box.isInstanceOf[Failure]) {
|
||||
throw new Exception(box.asInstanceOf[Failure].msg)
|
||||
if(box.isInstanceOf[ParamFailure[APIFailureNewStyle]]) {
|
||||
fullBoxOrException[Any](box)
|
||||
}
|
||||
|
||||
box.openOrThrowException("impossible error")
|
||||
@ -880,7 +863,6 @@ trait APIMethods400 {
|
||||
val listName = StringHelpers.snakify(English.plural(entityName))
|
||||
for {
|
||||
(box: Box[JArray], _) <- NewStyle.function.invokeDynamicConnector(GET_ALL, entityName, None, None, Some(cc))
|
||||
// resultList = APIUtil.unboxFullOrFail(box, Some(cc))
|
||||
resultList = unboxResult(box)
|
||||
} yield {
|
||||
import net.liftweb.json.JsonDSL._
|
||||
@ -891,7 +873,6 @@ trait APIMethods400 {
|
||||
case EntityName(entityName, id) JsonGet req => {cc =>
|
||||
for {
|
||||
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(GET_ONE, entityName, None, Some(id), Some(cc))
|
||||
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
|
||||
entity = unboxResult(box)
|
||||
} yield {
|
||||
(entity, HttpCode.`200`(Some(cc)))
|
||||
@ -900,7 +881,6 @@ trait APIMethods400 {
|
||||
case EntityName(entityName) :: Nil JsonPost json -> _ => {cc =>
|
||||
for {
|
||||
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(CREATE, entityName, Some(json.asInstanceOf[JObject]), None, Some(cc))
|
||||
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
|
||||
entity = unboxResult(box)
|
||||
} yield {
|
||||
(entity, HttpCode.`201`(Some(cc)))
|
||||
@ -909,7 +889,6 @@ trait APIMethods400 {
|
||||
case EntityName(entityName, id) JsonPut json -> _ => { cc =>
|
||||
for {
|
||||
(box: Box[JObject], _) <- NewStyle.function.invokeDynamicConnector(UPDATE, entityName, Some(json.asInstanceOf[JObject]), Some(id), Some(cc))
|
||||
// entity = APIUtil.unboxFullOrFail(box, Some(cc))
|
||||
entity = unboxResult(box)
|
||||
} yield {
|
||||
(entity, HttpCode.`200`(Some(cc)))
|
||||
@ -918,7 +897,6 @@ trait APIMethods400 {
|
||||
case EntityName(entityName, id) JsonDelete req => { cc =>
|
||||
for {
|
||||
(box: Box[JValue], _) <- NewStyle.function.invokeDynamicConnector(DELETE, entityName, None, Some(id), Some(cc))
|
||||
// deleteResult = APIUtil.unboxFullOrFail(box, Some(cc))
|
||||
deleteResult = unboxResult(box)
|
||||
} yield {
|
||||
(deleteResult, HttpCode.`200`(Some(cc)))
|
||||
|
||||
@ -220,16 +220,8 @@ case class DynamicEntityInfo(definition: String, entityName: String) {
|
||||
val fieldNameToType: Map[String, Class[_]] = fieldNameToTypeName
|
||||
.mapValues(jsonTypeMap(_))
|
||||
|
||||
val requiredFieldNames: Set[String] = (entity \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s).toSet
|
||||
|
||||
val fields = result.obj.filter(it => fieldNameToType.keySet.contains(it.name))
|
||||
|
||||
def check(v: Boolean, msg: String) = if (!v) throw new RuntimeException(msg)
|
||||
// if there are field type are not match the definitions, there must be bug.
|
||||
fields.foreach(it => check(fieldNameToType(it.name).isInstance(it.value), s"""$InvalidJsonFormat "${it.name}" required type is "${fieldNameToTypeName(it.name)}"."""))
|
||||
// if there are required field not presented, must be some bug.
|
||||
requiredFieldNames.foreach(it => check(fields.exists(_.name == it), s"""$InvalidJsonFormat required field "$it" not presented."""))
|
||||
|
||||
(id, fields.exists(_.name == idName)) match {
|
||||
case (Some(idValue), false) => JObject(JField(idName, JString(idValue)) :: fields)
|
||||
case _ => JObject(fields)
|
||||
|
||||
@ -17,6 +17,7 @@ import net.liftweb.json.JValue
|
||||
import net.liftweb.json.JsonAST.JNothing
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.language.postfixOps
|
||||
@ -55,16 +56,14 @@ object ConnectorEndpoints extends RestHelper{
|
||||
val value = invokeMethod(methodSymbol, paramValues :_*)
|
||||
|
||||
// convert any to Future[(Box[_], Option[CallContext])] type
|
||||
val futureValue: Future[(Box[_], Option[CallContext])] = toStandaredFuture(value)
|
||||
val futureValue: Future[(Box[_], Option[CallContext])] = toStandardFuture(value)
|
||||
|
||||
for {
|
||||
(Full(data), callContext) <- futureValue.map {it =>
|
||||
APIUtil.fullBoxOrException(it._1 ~> APIFailureNewStyle("", 400, optionCC.map(_.toLight)))
|
||||
it
|
||||
}
|
||||
(boxedData, _) <- futureValue
|
||||
data = APIUtil.fullBoxOrException(boxedData ~> APIFailureNewStyle("", 400, optionCC.map(_.toLight)))
|
||||
inboundAdapterCallContext = nameOf(InboundAdapterCallContext)
|
||||
//convert first letter to small case
|
||||
inboundAdapterCallContextKey = Character.toLowerCase(inboundAdapterCallContext.charAt(0)) + inboundAdapterCallContext.substring(1)
|
||||
inboundAdapterCallContextKey = StringUtils.uncapitalize(inboundAdapterCallContext)
|
||||
inboundAdapterCallContextValue = InboundAdapterCallContext(cc.correlationId)
|
||||
} yield {
|
||||
// NOTE: if any filed type is BigDecimal, it is can't be serialized by lift json
|
||||
@ -184,7 +183,8 @@ object ConnectorEndpoints extends RestHelper{
|
||||
mirrorObj.reflectMethod(method).apply(args :_*)
|
||||
}
|
||||
|
||||
def toStandaredFuture(obj: Any): Future[(Box[_], Option[CallContext])] = {
|
||||
@tailrec
|
||||
def toStandardFuture(obj: Any): Future[(Box[_], Option[CallContext])] = {
|
||||
obj match {
|
||||
case null => Future((Empty, None))
|
||||
case future: Future[_] => {
|
||||
@ -202,10 +202,10 @@ object ConnectorEndpoints extends RestHelper{
|
||||
}
|
||||
case Full(data) => {
|
||||
data match {
|
||||
case _: (_, _) => toStandaredFuture(Future(obj))
|
||||
case _: (_, _) => toStandardFuture(Future(obj))
|
||||
case _ => {
|
||||
val fillCallContext = obj.asInstanceOf[Box[_]].map((_, None))
|
||||
toStandaredFuture(Future(fillCallContext))
|
||||
toStandardFuture(Future(fillCallContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ import code.cards.MappedPhysicalCard
|
||||
import code.context.{UserAuthContextProvider, UserAuthContextUpdateProvider}
|
||||
import code.customer._
|
||||
import code.customeraddress.CustomerAddressX
|
||||
import code.dynamicEntity.DynamicEntityProvider
|
||||
import code.dynamicEntity.{DynamicEntityProvider, DynamicEntityT}
|
||||
import code.fx.{FXRate, MappedFXRate, fx}
|
||||
import code.kycchecks.KycChecks
|
||||
import code.kycdocuments.KycDocuments
|
||||
@ -60,8 +60,7 @@ import com.openbankproject.commons.model.{AccountApplication, AccountAttribute,
|
||||
import com.tesobe.CacheKeyFromArguments
|
||||
import com.tesobe.model.UpdateBankAccount
|
||||
import net.liftweb.common._
|
||||
import net.liftweb.json
|
||||
import net.liftweb.json.{JArray, JBool, JDouble, JInt, JObject, JString, JValue}
|
||||
import net.liftweb.json.{JArray, JBool, JObject, JValue}
|
||||
import net.liftweb.mapper.{By, _}
|
||||
import net.liftweb.util.Helpers.{tryo, _}
|
||||
import net.liftweb.util.Mailer
|
||||
@ -2801,50 +2800,30 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
val dynamicEntityBox = DynamicEntityProvider.connectorMethodProvider.vend.getByEntityName(entityName)
|
||||
// do validate, any validate process fail will return immediately
|
||||
if(dynamicEntityBox.isEmpty) {
|
||||
return Helper.booleanToFuture(s"$DynamicEntityEntityNotExists entity's name is '$entityName'")(dynamicEntityBox.isDefined)
|
||||
return Helper.booleanToFuture(s"$DynamicEntityNotExists entity's name is '$entityName'")(false)
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
} else if(entityId.isDefined && !persistedEntities.contains(entityId.get -> entityName)) {
|
||||
val id = entityId.get
|
||||
val idName = StringUtils.uncapitalize(entityName) + "Id"
|
||||
}
|
||||
|
||||
return Helper.booleanToFuture(s"$InvalidUrl not exists $entityName of $idName = $id")(false)
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
} else if(requestBody.isDefined) {
|
||||
val dynamicEntity = dynamicEntityBox.openOrThrowException(DynamicEntityEntityNotExists)
|
||||
|
||||
val jsonTypeMap = Map[String, Class[_]](
|
||||
("boolean", classOf[JBool]),
|
||||
("string", classOf[JString]),
|
||||
("array", classOf[JArray]),
|
||||
("integer", classOf[JInt]),
|
||||
("number", classOf[JDouble]),
|
||||
)
|
||||
val definitionJson = json.parse(dynamicEntity.metadataJson).asInstanceOf[JObject]
|
||||
val entity = (definitionJson \ entityName).asInstanceOf[JObject]
|
||||
val requiredFieldNames: Set[String] = (entity \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s).toSet
|
||||
|
||||
val fieldNameToTypeName: Map[String, String] = (entity \ "properties")
|
||||
.asInstanceOf[JObject]
|
||||
.obj
|
||||
.map(field => (field.name, (field.value \ "type").asInstanceOf[JString].s))
|
||||
.toMap
|
||||
|
||||
val fieldNameToType: Map[String, Class[_]] = fieldNameToTypeName
|
||||
.mapValues(jsonTypeMap(_))
|
||||
val bodyJson = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
|
||||
val fields = bodyJson.obj.filter(it => fieldNameToType.keySet.contains(it.name))
|
||||
|
||||
// if there are field type are not match the definitions, there must be bug.
|
||||
val invalidTypes = fields.filterNot(it => fieldNameToType(it.name).isInstance(it.value))
|
||||
val invalidTypeNames = invalidTypes.map(_.name).mkString("[", ",", "]")
|
||||
val missingRequiredFields = requiredFieldNames.filterNot(it => fields.exists(_.name == it))
|
||||
val missingFieldNames = missingRequiredFields.mkString("[", ",", "]")
|
||||
|
||||
if(invalidTypes.nonEmpty) {
|
||||
return Helper.booleanToFuture(s"$InvalidJsonFormat these field type not correct: $invalidTypeNames")(invalidTypes.isEmpty)
|
||||
if(operation == CREATE || operation == UPDATE) {
|
||||
if(requestBody.isEmpty) {
|
||||
return Helper.booleanToFuture(s"$InvalidJsonFormat requestBody is required for $operation operation.")(false)
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
} else if(missingRequiredFields.nonEmpty) {
|
||||
return Helper.booleanToFuture(s"$InvalidJsonFormat some required fields are missing: $missingFieldNames")(missingRequiredFields.isEmpty)
|
||||
}
|
||||
val dynamicEntity: DynamicEntityT = dynamicEntityBox.openOrThrowException(DynamicEntityNotExists)
|
||||
val validateResult: Either[String, Unit] = dynamicEntity.validateEntityJson(requestBody.get)
|
||||
if(validateResult.isLeft) {
|
||||
return Helper.booleanToFuture(s"$InvalidJsonFormat details: ${validateResult.left.get}")(validateResult.isRight)
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
}
|
||||
}
|
||||
if(operation == GET_ONE || operation == UPDATE || operation == DELETE) {
|
||||
if (entityId.isEmpty) {
|
||||
return Helper.booleanToFuture(s"$InvalidJsonFormat entityId is required for $operation operation.")(entityId.isEmpty || StringUtils.isBlank(entityId.get))
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
}
|
||||
if (!persistedEntities.contains(entityId.get -> entityName)) {
|
||||
val id = entityId.get
|
||||
return Helper.booleanToFuture(s"$EntityNotFoundByEntityId please check: entityId = $id", 404)(false)
|
||||
.map(it => (it.map(_.asInstanceOf[JValue]), callContext))
|
||||
}
|
||||
}
|
||||
@ -2860,7 +2839,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
}
|
||||
case CREATE | UPDATE => {
|
||||
val body = requestBody.getOrElse(throw new RuntimeException(s"$DynamicEntityMissArgument please supply the requestBody."))
|
||||
val persistedEntity = MockerConnector.persist(entityName, body, entityId)
|
||||
val id = if(operation == CREATE) None else entityId
|
||||
val persistedEntity = MockerConnector.persist(entityName, body, id)
|
||||
Full(persistedEntity)
|
||||
}
|
||||
case DELETE => {
|
||||
|
||||
@ -9242,6 +9242,61 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
|
||||
}
|
||||
|
||||
//---------------- dynamic end ---------------------please don't modify this line
|
||||
|
||||
messageDocs += dynamicEntityProcessDoc
|
||||
def dynamicEntityProcessDoc = MessageDoc(
|
||||
process = "obp.dynamicEntityProcess",
|
||||
messageFormat = messageFormat,
|
||||
description = "operate commited dynamic entity data",
|
||||
outboundTopic = None,
|
||||
inboundTopic = None,
|
||||
exampleOutboundMessage = (
|
||||
OutBoundDynamicEntityProcessDoc(outboundAdapterCallContext= OutboundAdapterCallContext(correlationId=correlationIdExample.value,
|
||||
sessionId=Some(sessionIdExample.value),
|
||||
consumerId=Some(consumerIdExample.value),
|
||||
generalContext=Some(List( BasicGeneralContext(key=keyExample.value,
|
||||
value=valueExample.value))),
|
||||
outboundAdapterAuthInfo=Some( OutboundAdapterAuthInfo(userId=Some(userIdExample.value),
|
||||
username=Some(usernameExample.value),
|
||||
linkedCustomers=Some(List( BasicLinkedCustomer(customerId=customerIdExample.value,
|
||||
customerNumber=customerNumberExample.value,
|
||||
legalName=legalNameExample.value))),
|
||||
userAuthContext=Some(List( BasicUserAuthContext(key=keyExample.value,
|
||||
value=valueExample.value))),
|
||||
authViews=Some(List( AuthView(view= ViewBasic(id=viewIdExample.value,
|
||||
name=viewNameExample.value,
|
||||
description=viewDescriptionExample.value),
|
||||
account= AccountBasic(id=accountIdExample.value,
|
||||
accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value,
|
||||
address=accountRoutingAddressExample.value)),
|
||||
customerOwners=List( InternalBasicCustomer(bankId=bankIdExample.value,
|
||||
customerId=customerIdExample.value,
|
||||
customerNumber=customerNumberExample.value,
|
||||
legalName=legalNameExample.value,
|
||||
dateOfBirth=parseDate(dateOfBirthExample.value).getOrElse(sys.error("dateOfBirthExample.value is not validate date format.")))),
|
||||
userOwners=List( InternalBasicUser(userId=userIdExample.value,
|
||||
emailAddress=emailExample.value,
|
||||
name=usernameExample.value))))))))),
|
||||
operation = DynamicEntityOperation.UPDATE,
|
||||
entityName = "FooBar",
|
||||
requestBody = Some(FooBar(name = "James Brown", number = 1234567890)),
|
||||
entityId = Some("foobar-id-value"))
|
||||
),
|
||||
exampleInboundMessage = (
|
||||
InBoundDynamicEntityProcessDoc(inboundAdapterCallContext= InboundAdapterCallContext(correlationId=correlationIdExample.value,
|
||||
sessionId=Some(sessionIdExample.value),
|
||||
generalContext=Some(List( BasicGeneralContext(key=keyExample.value,
|
||||
value=valueExample.value)))),
|
||||
status= Status(errorCode=statusErrorCodeExample.value,
|
||||
backendMessages=List( InboundStatusMessage(source=sourceExample.value,
|
||||
status=inboundStatusMessageStatusExample.value,
|
||||
errorCode=inboundStatusMessageErrorCodeExample.value,
|
||||
text=inboundStatusMessageTextExample.value))),
|
||||
data=FooBar(name = "James Brown", number = 1234567890, fooBarId = Some("foobar-id-value")))
|
||||
),
|
||||
adapterImplementation = Some(AdapterImplementation("- Core", 1))
|
||||
)
|
||||
|
||||
override def dynamicEntityProcess(operation: DynamicEntityOperation,
|
||||
entityName: String,
|
||||
requestBody: Option[JObject],
|
||||
@ -9365,7 +9420,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
|
||||
val future: Future[Box[Box[T]]] = extractBody(entity) map { msg =>
|
||||
tryo {
|
||||
val errorMsg = parse(msg).extract[ErrorMessage]
|
||||
val failure: Box[T] = ParamFailure(errorMsg.message, "")
|
||||
val failure: Box[T] = ParamFailure("", APIFailureNewStyle(errorMsg.message, status.intValue()))
|
||||
failure
|
||||
} ~> APIFailureNewStyle(msg, status.intValue())
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package code.dynamicEntity
|
||||
|
||||
import code.api.util.ErrorMessages.InvalidJsonFormat
|
||||
import com.openbankproject.commons.model.enums.DynamicEntityFieldType
|
||||
import com.openbankproject.commons.model.{Converter, JsonFieldReName}
|
||||
import net.liftweb.common.Box
|
||||
import net.liftweb.json.JsonAST.JString
|
||||
import net.liftweb.json.JsonDSL._
|
||||
import net.liftweb.json.{JField, JObject, JsonAST}
|
||||
import net.liftweb.json.{JArray, JBool, JDouble, JField, JInt, JNothing, JNull, JObject, JString, compactRender, parse}
|
||||
import net.liftweb.util.SimpleInjector
|
||||
|
||||
object DynamicEntityProvider extends SimpleInjector {
|
||||
@ -19,13 +19,11 @@ trait DynamicEntityT {
|
||||
def dynamicEntityId: Option[String]
|
||||
def entityName: String
|
||||
def metadataJson: String
|
||||
}
|
||||
|
||||
case class DynamicEntityCommons(entityName: String,
|
||||
metadataJson: String,
|
||||
dynamicEntityId: Option[String] = None,
|
||||
) extends DynamicEntityT with JsonFieldReName {
|
||||
private val definition: JObject = net.liftweb.json.parse(metadataJson).asInstanceOf[JObject]
|
||||
|
||||
//---------util methods
|
||||
|
||||
private lazy val definition: JObject = parse(metadataJson).asInstanceOf[JObject]
|
||||
//convert metadataJson to JValue, so the final json field metadataJson have no escaped " to \", have good readable
|
||||
lazy val jValue = dynamicEntityId match {
|
||||
case Some(id) => {
|
||||
@ -35,18 +33,156 @@ case class DynamicEntityCommons(entityName: String,
|
||||
}
|
||||
case None => definition
|
||||
}
|
||||
|
||||
/**
|
||||
* validate the commit json whether fulfil DynamicEntity schema
|
||||
* @param entityJson commit json object to add new instance of given dynamic entity
|
||||
* @return return Success[Unit], or return Left[String] error message
|
||||
*/
|
||||
def validateEntityJson(entityJson: JObject): Either[String, Unit] = {
|
||||
val required: List[String] = (definition \ entityName \ "required").asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s)
|
||||
|
||||
val missingProperties = required diff entityJson.obj.map(_.name)
|
||||
|
||||
if(missingProperties.nonEmpty) {
|
||||
return Left(s"$InvalidJsonFormat The 'required' field's not be fulfilled, missing properties: ${missingProperties.mkString(", ")}")
|
||||
}
|
||||
|
||||
val invalidPropertyMsg = (definition \ entityName \ "properties").asInstanceOf[JObject].obj
|
||||
.map(it => {
|
||||
val JField(propertyName, propertyDef: JObject) = it
|
||||
val propertyTypeName = (propertyDef \ "type").asInstanceOf[JString].s
|
||||
(propertyName, propertyTypeName)
|
||||
})
|
||||
.map(it => {
|
||||
val (propertyName, propertyType) = it
|
||||
val propertyValue = entityJson \ propertyName
|
||||
propertyType match {
|
||||
case _ if propertyValue == JNothing || propertyValue == JNull => "" // required properties already checked.
|
||||
case "string" if !propertyValue.isInstanceOf[JString] => s"$InvalidJsonFormat The type of '$propertyName' should be string"
|
||||
case "number" if !propertyValue.isInstanceOf[JDouble] => s"$InvalidJsonFormat The type of '$propertyName' should be number"
|
||||
case "integer" if !propertyValue.isInstanceOf[JInt] => s"$InvalidJsonFormat The type of '$propertyName' should be integer"
|
||||
case "boolean" if !propertyValue.isInstanceOf[JBool] => s"$InvalidJsonFormat The type of '$propertyName' should be boolean"
|
||||
case "array" if !propertyValue.isInstanceOf[JArray] => s"$InvalidJsonFormat The type of '$propertyName' should be array"
|
||||
case "object" if !propertyValue.isInstanceOf[JObject] => s"$InvalidJsonFormat The type of '$propertyName' should be object"
|
||||
case _ => ""
|
||||
}
|
||||
})
|
||||
.filter(_.nonEmpty)
|
||||
.mkString("; ")
|
||||
if(invalidPropertyMsg.nonEmpty) {
|
||||
Left(invalidPropertyMsg)
|
||||
} else {
|
||||
Right(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class DynamicEntityCommons(entityName: String,
|
||||
metadataJson: String,
|
||||
dynamicEntityId: Option[String] = None
|
||||
) extends DynamicEntityT with JsonFieldReName
|
||||
|
||||
object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommons] {
|
||||
|
||||
/**
|
||||
* create DynamicEntityCommons object, and do validation
|
||||
*
|
||||
* @param jsonObject the follow schema json:
|
||||
* {{{
|
||||
* {
|
||||
* "FooBar": {
|
||||
* "required": [
|
||||
* "name"
|
||||
* ],
|
||||
* "properties": {
|
||||
* "name": {
|
||||
* "type": "string",
|
||||
* "example": "James Brown"
|
||||
* },
|
||||
* "number": {
|
||||
* "type": "integer",
|
||||
* "example": "698761728934"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
* @param dynamicEntityId
|
||||
* @return object of DynamicEntityCommons
|
||||
*/
|
||||
def apply(jsonObject: JObject, dynamicEntityId: Option[String]): DynamicEntityCommons = {
|
||||
val fields = jsonObject.obj
|
||||
|
||||
// validate whether json is object and have a single field, currently support one entity definition
|
||||
require(fields.nonEmpty, s"$InvalidJsonFormat The Json root object should have a single entity, but current have none.")
|
||||
require(fields.size == 1, s"$InvalidJsonFormat The Json root object should have a single entity, but current entityNames: ${fields.map(_.name).mkString(", ")}")
|
||||
|
||||
val JField(entityName, metadataJson) = fields.head
|
||||
|
||||
// validate entityName corresponding value is json object
|
||||
val metadataStr = compactRender(metadataJson)
|
||||
require(metadataJson.isInstanceOf[JObject], s"$InvalidJsonFormat The $entityName should have an object value, but current value is: $metadataStr")
|
||||
|
||||
val required = metadataJson \ "required"
|
||||
|
||||
// validate 'required' field exists and is a json array[string]
|
||||
require(required != JNothing , s"$InvalidJsonFormat There must be 'required' field in $entityName, and type is json array[string]")
|
||||
require(required.isInstanceOf[JArray] && required.asInstanceOf[JArray].arr.forall(_.isInstanceOf[JString]), s"$InvalidJsonFormat The 'required' field's type of $entityName should be array[string]")
|
||||
|
||||
val properties = metadataJson \ "properties"
|
||||
|
||||
// validate 'properties' field exists and is json object
|
||||
require(properties != JNothing , s"$InvalidJsonFormat There must be 'required' field in $entityName, and type is array[string]")
|
||||
require(properties.isInstanceOf[JObject], s"$InvalidJsonFormat The 'properties' field's type of $entityName should be json object")
|
||||
|
||||
val propertiesObj = properties.asInstanceOf[JObject]
|
||||
|
||||
val requiredFields = required.asInstanceOf[JArray].arr.map(_.asInstanceOf[JString].s)
|
||||
|
||||
val allFields = propertiesObj.obj
|
||||
|
||||
// validate there is no required field missing in properties
|
||||
val notFoundRequiredField = requiredFields.diff(allFields.map(_.name))
|
||||
require(metadataJson.isInstanceOf[JObject], s"$InvalidJsonFormat In the $entityName, all 'required' fields should be present, these are missing: ${notFoundRequiredField.mkString(", ")}")
|
||||
|
||||
// validate all properties have a type and example
|
||||
allFields.foreach(field => {
|
||||
val JField(fieldName, value) = field
|
||||
require(value.isInstanceOf[JObject], s"$InvalidJsonFormat The property of $fieldName's type should be json object")
|
||||
|
||||
// 'type' exists and value should be one of allowed type
|
||||
val fieldType = value \ "type"
|
||||
require(fieldType.isInstanceOf[JString] && fieldType.asInstanceOf[JString].s.nonEmpty, s"$InvalidJsonFormat The property of $fieldName's 'type' field should be exists and type is json string")
|
||||
require(allowedFieldType.contains(fieldType.asInstanceOf[JString].s), s"$InvalidJsonFormat The property of $fieldName's 'type' field should be json string and value should be one of: ${allowedFieldType.mkString(", ")}")
|
||||
|
||||
// example is exists
|
||||
val fieldExample = value \ "example"
|
||||
require(fieldExample != JNothing, s"$InvalidJsonFormat The property of $fieldName's 'example' field should be exists")
|
||||
})
|
||||
|
||||
DynamicEntityCommons(entityName, compactRender(jsonObject), dynamicEntityId)
|
||||
}
|
||||
|
||||
private val allowedFieldType: Set[String] = Set(
|
||||
"string",
|
||||
"number",
|
||||
"integer",
|
||||
"boolean",
|
||||
"array",
|
||||
// "object",
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* an example schema of DynamicEntity, this is as request body example usage
|
||||
* example case classes, as an example schema of DynamicEntity, for request body example usage
|
||||
* @param FooBar
|
||||
*/
|
||||
case class DynamicEntityFooBar(FooBar: DynamicEntityDefinition, dynamicEntityId: Option[String] = None)
|
||||
case class DynamicEntityDefinition(required: List[String],properties: DynamicEntityFullBarFields)
|
||||
case class DynamicEntityFullBarFields(name: DynamicEntityTypeExample, number: DynamicEntityTypeExample)
|
||||
case class DynamicEntityTypeExample(`type`: DynamicEntityFieldType, example: String)
|
||||
|
||||
object DynamicEntityCommons extends Converter[DynamicEntityT, DynamicEntityCommons]
|
||||
//-------------------example case class end
|
||||
|
||||
|
||||
trait DynamicEntityProvider {
|
||||
|
||||
@ -1165,4 +1165,13 @@ case class OutBoundDynamicEntityProcess (outboundAdapterCallContext: OutboundAda
|
||||
entityName: String,
|
||||
requestBody: Option[JObject],
|
||||
entityId: Option[String]) extends TopicTrait
|
||||
case class InBoundDynamicEntityProcess (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: JValue) extends InBoundTrait[JValue]
|
||||
case class InBoundDynamicEntityProcess (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: JValue) extends InBoundTrait[JValue]
|
||||
|
||||
// because swagger generate not support JValue type, so here supply too xxxDoc TO generate correct request and response body example
|
||||
case class FooBar(name: String, number: Int, fooBarId: Option[String] = None)
|
||||
case class OutBoundDynamicEntityProcessDoc (outboundAdapterCallContext: OutboundAdapterCallContext,
|
||||
operation: DynamicEntityOperation,
|
||||
entityName: String,
|
||||
requestBody: Option[FooBar],
|
||||
entityId: Option[String]) extends TopicTrait
|
||||
case class InBoundDynamicEntityProcessDoc (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: FooBar) extends InBoundTrait[FooBar]
|
||||
@ -1,6 +1,6 @@
|
||||
package com.openbankproject.commons.util
|
||||
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.common.{Box, Empty, Failure, Full}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.annotation.tailrec
|
||||
@ -10,6 +10,7 @@ import scala.language.postfixOps
|
||||
import scala.reflect.runtime.universe._
|
||||
import scala.reflect.runtime.{universe => ru}
|
||||
import scala.util.Success
|
||||
import net.liftweb.json.JValue
|
||||
|
||||
object ReflectUtils {
|
||||
private[this] val mirror: ru.Mirror = ru.runtimeMirror(getClass().getClassLoader)
|
||||
@ -506,9 +507,18 @@ object ReflectUtils {
|
||||
def toValueObject(t: Any): Any = {
|
||||
t match {
|
||||
case null => null
|
||||
case v: JValue => v
|
||||
case Some(v) => toValueObject(v)
|
||||
case Full(v) => toValueObject(v)
|
||||
case None|Empty => null
|
||||
case v: Failure => v
|
||||
case Left(v) => Left(toValueObject(v))
|
||||
case v: Right[_, _] => v.map(toValueObject)
|
||||
case v: Success[_]=> v.map(toValueObject)
|
||||
case scala.util.Failure(v) => v
|
||||
case it: Iterable[_] => it.map(toValueObject)
|
||||
case array: Array[_] => array.map(toValueObject)
|
||||
case v if(getType(v).typeSymbol.asClass.isCaseClass) => v
|
||||
case v if getType(v).typeSymbol.asClass.isCaseClass => v
|
||||
case other => {
|
||||
val mirrorObj = mirror.reflect(other)
|
||||
mirrorObj.symbol.info.decls
|
||||
|
||||
Loading…
Reference in New Issue
Block a user