diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index 3e0512c3a..f41b58c0f 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -12,7 +12,8 @@ import code.api.v1_4_0.{APIMethods140, JSONFactory1_4_0, OBPAPI1_4_0} import code.api.v2_2_0.{APIMethods220, OBPAPI2_2_0} import code.api.v3_0_0.OBPAPI3_0_0 import code.api.v3_1_0.OBPAPI3_1_0 -import code.api.v4_0_0.OBPAPI4_0_0 +import code.api.v4_0_0.{APIMethods400, OBPAPI4_0_0} +import APIMethods400.Implementations4_0_0.genericEndpoint import code.util.Helper.MdcLoggable import com.tesobe.{CacheKeyFromArguments, CacheKeyOmit} import net.liftweb.common.{Box, Empty, Full} @@ -619,7 +620,7 @@ def filterResourceDocs(allResources: List[ResourceDoc], showCore: Option[Boolean val jsonOut = for { requestedApiVersion <- Full(ApiVersion.valueOf(requestedApiVersionString)) ?~! InvalidApiVersionString _ <- booleanToBox(versionIsAllowed(requestedApiVersion), ApiVersionNotSupported) - rd <- getResourceDocsList(requestedApiVersion) + rd <- getResourceDocsList(requestedApiVersion).map(_.filterNot(_.partialFunction == genericEndpoint)) // exclude all DynamicEntity endpoints } yield { // Filter val rdFiltered = filterResourceDocs(rd, showCore, showPSD2, showOBWG, resourceDocTags, partialFunctionNames) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 06c172313..9e29a879e 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -3432,29 +3432,6 @@ object SwaggerDefinitionsJSON { overall_balance = amountOfMoney, overall_balance_date = DateWithMsExampleObject ) - - val dynamicEntityCommons = DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | \"definitions\": { - | \"FooBar\": { - | \"required\": [ - | \"name\" - | ], - | \"properties\": { - | \"name\": { - | \"type\": \"string\", - | \"example\": \"James Brown\" - | }, - | \"number\": { - | \"type\": \"integer\", - | \"example\": \"698761728934\" - | } - | } - | } - | } - |} - |""".stripMargin, dynamicEntityId = Some("dynamic-entity-id")) //The common error or success format. //Just some helper format to use in Json diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala index 5bd245b52..a85d09692 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerJSONFactory.scala @@ -20,12 +20,11 @@ import net.liftweb.util.StringHelpers import scala.collection.mutable.ListBuffer import code.api.v3_1_0.ListResult -import code.dynamicEntity.DynamicEntityCommons +import code.api.v4_0_0.APIMethods400.Implementations4_0_0.genericEndpoint import net.liftweb.common.{EmptyBox, Full} import org.apache.commons.lang3.StringUtils import scala.reflect.runtime.universe -import scala.tools.scalap.scalax.util.StringUtil object SwaggerJSONFactory { //Info Object @@ -794,11 +793,10 @@ object SwaggerJSONFactory { // extract uploaded DynamicEntities definitions, only when processing resourceDocList have DynamicEntity val dynamicEntityDefinitions = resourceDocList - .find(_.exampleRequestBody.isInstanceOf[DynamicEntityCommons]) + .find(_.partialFunction == genericEndpoint) .map(_ => { NewStyle.function.getDynamicEntities() - .map(it => parse(it.metadataJson) \ "definitions") - .map(compactRender(_)) + .map(_.metadataJson) .map(StringUtils.substring(_, 1, -1)) }) .toList.flatten diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 64da317a2..9238e7957 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -2,6 +2,8 @@ package code.api.util import code.api.util.Glossary.{glossaryItems, makeGlossaryItem} +import code.dynamicEntity.{DynamicEntityDefinition, DynamicEntityFooBar, DynamicEntityFullBarFields, DynamicEntityTypeExample} +import com.openbankproject.commons.model.enums.DynamicEntityFieldType case class ConnectorField(value: String, description: String) { @@ -283,6 +285,39 @@ object ExampleValue { // if yes, please rename the follow to lastOkDateExample, and delete outBoundCreateCustomerLastOkDateExample lazy val outBoundCreateCustomerLastOkDateExample = ConnectorField("2019-09-12", "fix me, lastOkDate Date string") + + //this is only for dynamicEntity post or request body example + """ + |{ + | "FooBar": { + | "required": [ + | "name" + | ], + | "properties": { + | "name": { + | "type": "string", + | "example": "James Brown" + | }, + | "number": { + | "type": "integer", + | "example": "698761728934" + | } + | } + | } + |} + |""".stripMargin + + lazy val dynamicEntityRequestBodyExample = DynamicEntityFooBar( + DynamicEntityDefinition( + List("name"), + DynamicEntityFullBarFields( + DynamicEntityTypeExample(DynamicEntityFieldType.string, "James Brown"), + DynamicEntityTypeExample(DynamicEntityFieldType.integer, "698761728934") + ) + ) + ) + + lazy val dynamicEntityResponseBodyExample = dynamicEntityRequestBodyExample.copy(dynamicEntityId = Some("dynamic-entity-id")) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 7bbd8cfb6..c3ca52c06 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -11,7 +11,7 @@ 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 +import code.dynamicEntity.{DynamicEntityCommons, DynamicEntityDefinition} import code.model.dataAccess.AuthUser import code.model.toUserExtended import code.transactionrequests.TransactionRequests.TransactionChallengeTypes._ @@ -23,9 +23,11 @@ import com.openbankproject.commons.model._ import net.liftweb.common.{Box, Full} import net.liftweb.http.rest.RestHelper import net.liftweb.json.Serialization.write -import net.liftweb.json._ +import code.api.util.ExampleValue.{dynamicEntityRequestBodyExample, dynamicEntityResponseBodyExample} +import com.openbankproject.commons.model.enums.DynamicEntityFieldType import net.liftweb.util.StringHelpers import org.atteo.evo.inflector.English +import net.liftweb.json._ import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer @@ -686,30 +688,8 @@ trait APIMethods400 { emptyObjectJson, ListResult( "dynamic_entities", - (List(DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | "definitions": { - | "FooBar": { - | "required": [ - | "name" - | ], - | "properties": { - | "name": { - | "type": "string", - | "example": "James Brown" - | }, - | "number": { - | "type": "integer", - | "example": "698761728934" - | } - | } - | } - | } - |} - |""".stripMargin))) - ) - , + List(dynamicEntityResponseBodyExample) + ), List( UserNotLoggedIn, UserHasMissingRoles, @@ -730,23 +710,17 @@ trait APIMethods400 { dynamicEntities <- Future(NewStyle.function.getDynamicEntities()) } yield { val listCommons: List[DynamicEntityCommons] = dynamicEntities - (ListResult("dynamic_entities", listCommons), HttpCode.`200`(callContext)) + val jObjects = listCommons.map(_.jValue) + (ListResult("dynamic_entities", jObjects), HttpCode.`200`(callContext)) } } } - private def validateDynamicEntityJson(data: DynamicEntityCommons) = { - val metadataJson = net.liftweb.json.parse(data.metadataJson) - - val rqs = (metadataJson \ "definitions" \ data.entityName \ "required").extract[Array[String]] - - val propertiesFields = (metadataJson \ "definitions" \ data.entityName \ "properties").asInstanceOf[JObject].values - require(rqs.toSet.diff(propertiesFields.keySet).isEmpty) - propertiesFields.values.foreach(pair => { - val map = pair.asInstanceOf[Map[String, _]] - require(map("type").isInstanceOf[String]) - require(map("example") != null) - }) + 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( @@ -755,68 +729,20 @@ trait APIMethods400 { nameOf(createDynamicEntity), "POST", "/management/dynamic_entities", - "Add DynamicEntity", - s"""Add a DynamicEntity. + "Create DynamicEntity", + s"""Create a DynamicEntity. | | |${authenticationRequiredMessage(true)} | - |Explanation of Fields: + |Create one DynamicEntity, after created success, the corresponding CURD endpoints will be generated automatically | - |* method_name is required String value - |* connector_name is required String value - |* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false - |* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex - |* parameters is optional array of key value pairs. You can set some paremeters for this method + |Current support filed types as follow: + |${DynamicEntityFieldType.values.map(_.toString).mkString("[", ", ", "]")} | - |note: - | - |* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+" |""", - DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | "definitions": { - | "FooBar": { - | "required": [ - | "name" - | ], - | "properties": { - | "name": { - | "type": "string", - | "example": "James Brown" - | }, - | "number": { - | "type": "integer", - | "example": "698761728934" - | } - | } - | } - | } - |} - |""".stripMargin), - DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | "definitions": { - | "FooBar": { - | "required": [ - | "name" - | ], - | "properties": { - | "name": { - | "type": "string", - | "example": "James Brown" - | }, - | "number": { - | "type": "integer", - | "example": "698761728934" - | } - | } - | } - | } - |} - |""".stripMargin, dynamicEntityId = Some("dynamic-entity-id")), + dynamicEntityRequestBodyExample, + dynamicEntityResponseBodyExample, List( UserNotLoggedIn, UserHasMissingRoles, @@ -833,17 +759,18 @@ trait APIMethods400 { for { (Full(u), callContext) <- authorizedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, canCreateDynamicEntity, callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the ${classOf[DynamicEntityCommons]}, and metadataJson should be the same structure as document example." + failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example." postedData <- NewStyle.function.tryons(failMsg, 400, callContext) { - val data = json.extract[DynamicEntityCommons] - validateDynamicEntityJson(data) - data + validateDynamicEntityJson(json) + + val JField(name, _) = json.asInstanceOf[JObject].obj.head + DynamicEntityCommons(name, compactRender(json)) } Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(postedData) } yield { val commonsData: DynamicEntityCommons = dynamicEntity - (commonsData, HttpCode.`201`(callContext)) + (commonsData.jValue, HttpCode.`201`(callContext)) } } } @@ -861,61 +788,14 @@ trait APIMethods400 { | |${authenticationRequiredMessage(true)} | - |Explanations of Fields: + |Update one DynamicEntity, after update finished, the corresponding CURD endpoints will be changed. | - |* method_name is required String value - |* connector_name is required String value - |* is_bank_id_exact_match is required boolean value, if bank_id_pattern is exact bank_id value, this value is true; if bank_id_pattern is null or a regex, this value is false - |* bank_id_pattern is optional String value, it can be null, a exact bank_id or a regex - |* parameters is optional array of key value pairs. You can set some paremeters for this method - |note: + |Current support filed types as follow: + |${DynamicEntityFieldType.values.map(_.toString).mkString("[", ", ", "]")} | - |* if bank_id_pattern is regex, special characters need to do escape, for example: bank_id_pattern = "some\\-id_pattern_\\d+" |""", - DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | "definitions": { - | "FooBar": { - | "required": [ - | "name" - | ], - | "properties": { - | "name": { - | "type": "string", - | "example": "James Brown" - | }, - | "number": { - | "type": "integer", - | "example": "698761728934" - | } - | } - | } - | } - |} - |""".stripMargin), - DynamicEntityCommons(entityName = "FooBar", metadataJson = - """ - |{ - | "definitions": { - | "FooBar": { - | "required": [ - | "name" - | ], - | "properties": { - | "name": { - | "type": "string", - | "example": "James Brown" - | }, - | "number": { - | "type": "integer", - | "example": "698761728934" - | } - | } - | } - | } - |} - |""".stripMargin, dynamicEntityId = Some("dynamic-entity-id")), + dynamicEntityRequestBodyExample, + dynamicEntityResponseBodyExample, List( UserNotLoggedIn, UserHasMissingRoles, @@ -933,11 +813,11 @@ trait APIMethods400 { (Full(u), callContext) <- authorizedAccess(cc) _ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateDynamicEntity, callContext) - failMsg = s"$InvalidJsonFormat The Json body should be the ${classOf[DynamicEntityCommons]}, and metadataJson should be the same structure as document example." + failMsg = s"$InvalidJsonFormat The Json body should be the same structure as request body example." putData <- NewStyle.function.tryons(failMsg, 400, callContext) { - val data = json.extract[DynamicEntityCommons].copy(dynamicEntityId = Some(dynamicEntityId)) - validateDynamicEntityJson(data) - data + validateDynamicEntityJson(json) + val JField(name, _) = json.asInstanceOf[JObject].obj.head + DynamicEntityCommons(name, compactRender(json), Some(dynamicEntityId)) } (_, _) <- NewStyle.function.getDynamicEntityById(dynamicEntityId, callContext) @@ -945,7 +825,7 @@ trait APIMethods400 { Full(dynamicEntity) <- NewStyle.function.createOrUpdateDynamicEntity(putData) } yield { val commonsData: DynamicEntityCommons = dynamicEntity - (commonsData, HttpCode.`200`(callContext)) + (commonsData.jValue, HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala index 04a251ad3..48e06859a 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/DynamicEntityHelper.scala @@ -60,7 +60,7 @@ object MockerConnector { collection.mutable.ArrayBuffer(docs:_*) } - // TODO the reqestBody and responseBody is not correct ref type + // TODO the requestBody and responseBody is not correct ref type def createDocs(dynamicEntityInfo: DynamicEntityInfo) = { val entityName = dynamicEntityInfo.entityName val idNameInUrl = StringHelpers.snakify(dynamicEntityInfo.idName).toUpperCase() @@ -207,7 +207,7 @@ case class DynamicEntityInfo(definition: String, entityName: String) { ) val definitionJson = json.parse(definition).asInstanceOf[JObject] - val entity = (definitionJson \ "definitions" \ entityName).asInstanceOf[JObject] + val entity = (definitionJson \ entityName).asInstanceOf[JObject] def toResponse(result: JObject, id: Option[String]): JObject = { diff --git a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala index 3cde2e04a..5cf48a4d1 100644 --- a/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala +++ b/obp-api/src/main/scala/code/dynamicEntity/DynamicEntityProvider.scala @@ -1,7 +1,11 @@ package code.dynamicEntity +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.util.SimpleInjector object DynamicEntityProvider extends SimpleInjector { @@ -20,7 +24,27 @@ trait DynamicEntityT { case class DynamicEntityCommons(entityName: String, metadataJson: String, dynamicEntityId: Option[String] = None, - ) extends DynamicEntityT with JsonFieldReName + ) extends DynamicEntityT with JsonFieldReName { + private val definition: JObject = net.liftweb.json.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) => { + val jId: JObject = "dynamicEntityId" -> id + // add dynamicEntityId to JObject + definition merge jId + } + case None => definition + } +} + +/** + * an example schema of DynamicEntity, this is as 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] diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala index 875d44d56..e27f3b7e5 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala @@ -44,4 +44,13 @@ object PemCertificateRole extends OBPEnumeration[PemCertificateRole] { object PSP_AI extends Value object PSP_PI extends Value } -//------api enumerations end ---- \ No newline at end of file +//------api enumerations end ---- +sealed trait DynamicEntityFieldType extends EnumValue +object DynamicEntityFieldType extends OBPEnumeration[DynamicEntityFieldType]{ + object string extends Value + object number extends Value + object integer extends Value + object boolean extends Value +// object array extends Value +// object `object` extends Value //TODO in the future, we consider support nested type +} \ No newline at end of file