From 4da80d8727b70d90427ec3b0ca5639dd18c76e8d Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 25 Jan 2023 00:56:32 +0100 Subject: [PATCH] bugfix/added the missing resource Docs nested fields --- .../code/api/v1_4_0/JSONFactory1_4_0.scala | 53 +++++--- .../api/v1_4_0/JSONFactory1_4_0Test.scala | 37 +++++- .../v1_4_0/JSONFactory1_4_0_LightTest.scala | 118 ++++++++++++++++++ 3 files changed, 193 insertions(+), 15 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0_LightTest.scala diff --git a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index ef6011af0..bf05290bb 100644 --- a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -26,6 +26,8 @@ import java.util.regex.Pattern import com.openbankproject.commons.model.enums.LanguageParam +import java.lang.reflect.Field + object JSONFactory1_4_0 extends MdcLoggable{ implicit def formats: Formats = CustomJsonFormats.formats case class PostCustomerJson( @@ -466,27 +468,49 @@ object JSONFactory1_4_0 extends MdcLoggable{ } } - def prepareJsonFieldDescription(jsonBody: scala.Product, jsonType: String): String = { - jsonBody.productIterator - val (jsonBodyJValue: json.JValue, optionalTypeFields) = jsonBody match { - case JvalueCaseClass(jValue) => - val types = Nil - (jValue, types) - case _ => - val types = jsonBody.getClass() - .getDeclaredFields().toList - .map(f => (f.getName(), f.getType().getCanonicalName().contains("Option"))) - (decompose(jsonBody), types) + def getAllFields(jsonBody: scala.Product): List[Field] = { + def loopAllFields(rootFields: List[Field]) = { + val fields = for { + field <- jsonBody.productIterator.toList if (field.isInstanceOf[scala.Product] && field != jsonBody) + fields = getAllFields(field.asInstanceOf[scala.Product]) + } yield + fields + (rootFields ++ fields.flatten).toSet.toList } + //The root level is a list: eg: List[Users] + if(jsonBody.isInstanceOf[List[Any]] && jsonBody.productIterator.toList.nonEmpty){ + val rootFields: List[Field] = jsonBody.productIterator.toSet.head.getClass.getDeclaredFields.toList + loopAllFields(rootFields) + }else { + jsonBody match { + case JvalueCaseClass(jValue) => + val types = Nil + types + case _ => + val rootFields: List[Field] = jsonBody.getClass().getDeclaredFields().toSet.toList + loopAllFields(rootFields) + } + } + } + + def checkFieldOption(jsonBody: scala.Product, rootFields: List[Field]) = { + val types = rootFields.map(f => (f.getName(), f.getType().getCanonicalName().contains("Option"))) + (decompose(jsonBody), types) + } + + + def prepareJsonFieldDescription(jsonBody: scala.Product, jsonType: String): String = { + val allFields = getAllFields(jsonBody) + val (jsonBodyJValue: json.JValue, allFieldsAndOptionStatus) = checkFieldOption(jsonBody, allFields) // Group by is mandatory criteria and sort those 2 groups by name of the field val jsonBodyFieldsOptional = JsonUtils.collectFieldNames(jsonBodyJValue).keySet.toList - .filter(x => optionalTypeFields.exists(i => i._1 == x && i._2 == true)).sorted + .filter(x => allFieldsAndOptionStatus.exists(i => i._1 == x && i._2 == true)).sorted val jsonBodyFieldsMandatory = JsonUtils.collectFieldNames(jsonBodyJValue).keySet.toList - .filter(x => optionalTypeFields.exists(i => i._1 == x && i._2 == false)).sorted + .filter(x => allFieldsAndOptionStatus.exists(i => i._1 == x && i._2 == false)).sorted val jsonBodyFields = jsonBodyFieldsMandatory ::: jsonBodyFieldsOptional - val jsonFieldsDescription = jsonBodyFields.map(i => prepareDescription(i, optionalTypeFields)) + val jsonFieldsDescription = jsonBodyFields.map(i => prepareDescription(i, allFieldsAndOptionStatus)) val jsonTitleType = if (jsonType.contains("request")) "\n\n\n**JSON request body fields:**\n\n" else "\n\n\n**JSON response body fields:**\n\n" @@ -527,6 +551,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ "" } //3rd: get the fields description from the response body: + //response body can be a nest class, need to loop all the fields. val responseFieldsDescription = prepareJsonFieldDescription(resourceDocUpdatedTags.successResponseBody,"response") urlParametersDescription ++ exampleRequestBodyFieldsDescription ++ responseFieldsDescription } diff --git a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala index f2308bf01..23b8de0c0 100644 --- a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala +++ b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala @@ -1,7 +1,8 @@ package code.api.v1_4_0 -import java.util.Date +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.usersJsonV400 +import java.util.Date import code.api.util.APIUtil.ResourceDoc import code.api.util.{APIUtil, ExampleValue} import code.api.v1_4_0.JSONFactory1_4_0.ResourceDocJson @@ -51,6 +52,40 @@ class JSONFactory1_4_0Test extends V140ServerSetup with DefaultUsers { description.contains("[BANK_ID](/glossary#Bank.bank_id): gh.29.uk") should be (true) } + scenario("prepareJsonFieldDescription should work well - users object") { + val usersJson = usersJsonV400 + val description = JSONFactory1_4_0.prepareJsonFieldDescription(usersJson, "response") + description.contains( + """ + |JSON response body fields: + | + |*user_id = 9ca9a7e4-6d02-40e3-a129-0b2bf89de9b1, + | + |*email = felixsmith@example.com, + | + |*provider_id = , + | + |*provider = , + | + |*username = felixsmith, + | + |*entitlements = , + | + |*views = , + | + |*agreements = , + | + |*is_deleted = false, + | + |*last_marketing_agreement_signed_date = , + | + |*is_locked = false + | + |""".stripMargin + ) should be (false) + println(description) + } + scenario("PrepareUrlParameterDescription should work well, extract the parameters from URL") { val requestUrl1 = "/obp/v4.0.0/banks/BANK_ID/accounts/account_ids/private" val requestUrl1Description = JSONFactory1_4_0.prepareUrlParameterDescription(requestUrl1) diff --git a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0_LightTest.scala b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0_LightTest.scala new file mode 100644 index 000000000..6f597e010 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0_LightTest.scala @@ -0,0 +1,118 @@ +package code.api.v1_4_0 + +import code.api.util.CustomJsonFormats +import code.util.Helper.MdcLoggable +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FeatureSpec, GivenWhenThen, Matchers} + +import java.lang.reflect.Field +import java.util.Date + +class JSONFactory1_4_0_LightTest extends FeatureSpec + with BeforeAndAfterEach + with GivenWhenThen + with BeforeAndAfterAll + with Matchers + with MdcLoggable + with CustomJsonFormats { + + feature("Test JSONFactory1_4_0.getJValueAndAllFields method") { + case class ClassOne( + string1: String = "1" + ) + + case class ClassTwo( + string2: String = "2", + strings2: List[String] = List("List-2"), + ) + + val oneObject = ClassOne() + + case class NestedClass( + classes: List[ClassOne] = List(oneObject) + ) + + val twoObject = ClassTwo() + + case class NestedListClass( + classes1: List[ClassOne] = List(oneObject), + ) + + val nestedClass = NestedClass() + + val nestedListClass = NestedListClass() + + case class ComplexNestedClass( + complexNestedClassString: String = "ComplexNestedClass1", + complexNestedClassInt: Int = 1, + complexNestedClassDate: Date = new Date(), + complexNestedClassOptionSomeInt: Option[Int] = Some(1), + complexNestedClassOptionNoneInt: Option[Int] = None, + classes1: List[ClassOne] = List(oneObject), + classes2: List[ClassTwo] = List(twoObject), + ) + + val complexNestedClass = ComplexNestedClass() + + + + scenario("getJValueAndAllFields -input is the oneObject, basic no nested, no List inside") { + val listFields: List[Field] = JSONFactory1_4_0.getAllFields(oneObject) + + val expectedListFieldsString = "List(private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.string1, " + + "private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.$outer)" + + listFields.toString shouldBe (expectedListFieldsString) +// println(listFields) + } + + scenario("getJValueAndAllFields -input it the nestedClass") { + val listFields: List[Field] = JSONFactory1_4_0.getAllFields(nestedClass) + val expectedListFieldsString = "List(" + + "public static final long scala.collection.immutable.Nil$.serialVersionUID, public static scala.collection.immutable.Nil$ scala.collection.immutable.Nil$.MODULE$, " + + "private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.$outer, " + + "private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$NestedClass$1.$outer, " + + "private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.string1, " + + "private final scala.collection.immutable.List code.api.v1_4_0.JSONFactory1_4_0_LightTest$NestedClass$1.classes)" + listFields.toString shouldBe (expectedListFieldsString) +// println(listFields) + } + + scenario("getJValueAndAllFields - input is the List[nestedClass]") { + val listFields: List[Field] = JSONFactory1_4_0.getAllFields(List(oneObject)) +// it should return all the fields in the List + val expectedListFieldsString = "List(private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.string1, " + + "private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.$outer, " + + "public static scala.collection.immutable.Nil$ scala.collection.immutable.Nil$.MODULE$, " + + "public static final long scala.collection.immutable.Nil$.serialVersionUID)" + listFields.toString shouldBe (expectedListFieldsString) +// println(listFields) + } + + scenario("getJValueAndAllFields -input it the complexNestedClass") { + val listFields: List[Field] = JSONFactory1_4_0.getAllFields(complexNestedClass) + val expectedListFieldsString = "List(" + + "private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.complexNestedClassString, " + + "private final int code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.complexNestedClassInt, " + + "private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.$outer, static final boolean java.lang.String.COMPACT_STRINGS, " + + "public static final long scala.collection.immutable.Nil$.serialVersionUID, " + + "private final scala.collection.immutable.List code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.classes2, " + + "private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassTwo$1.string2, " + + "public static scala.collection.immutable.Nil$ scala.collection.immutable.Nil$.MODULE$, public static final long scala.None$.serialVersionUID, " + + "private final byte java.lang.String.coder, private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.$outer, " + + "private final scala.Option code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.complexNestedClassOptionSomeInt, static final byte java.lang.String.LATIN1, " + + "public static scala.None$ scala.None$.MODULE$, private final java.lang.Object scala.Some.value, private final scala.collection.immutable.List code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.classes1, " + + "private final java.util.Date code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.complexNestedClassDate, " + + "private final scala.collection.immutable.List code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassTwo$1.strings2, " + + "private int java.lang.String.hash, private final java.lang.String code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassOne$1.string1, " + + "private static final long java.lang.String.serialVersionUID, private final code.api.v1_4_0.JSONFactory1_4_0_LightTest code.api.v1_4_0.JSONFactory1_4_0_LightTest$ClassTwo$1.$outer, " + + "private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private final byte[] java.lang.String.value, " + + "public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER, public static final long scala.Some.serialVersionUID, static final byte java.lang.String.UTF16, " + + "private final scala.Option code.api.v1_4_0.JSONFactory1_4_0_LightTest$ComplexNestedClass$1.complexNestedClassOptionNoneInt)" + listFields.toString shouldBe (expectedListFieldsString) +// println(listFields) + } + + + } + +}