feature/add_required_field_annotation : make required field information shown in generated swagger doc

This commit is contained in:
shuang 2020-01-21 12:12:19 +08:00
parent 5453c78ddc
commit 7787919f15
10 changed files with 191 additions and 102 deletions

View File

@ -1,6 +1,7 @@
package code.api.ResourceDocs1_4_0
import java.util.Date
import code.api.Constant._
import code.api.Constant
import code.api.UKOpenBanking.v2_0_0.JSONFactory_UKOpenBanking_200
@ -25,7 +26,7 @@ import com.openbankproject.commons.model
import com.openbankproject.commons.model.PinResetReason.{FORGOT, GOOD_SECURITY_PRACTICE}
import com.openbankproject.commons.model.enums.CardAttributeType
import com.openbankproject.commons.model.{UserAuthContextUpdateStatus, ViewBasic, _}
import com.openbankproject.commons.util.ReflectUtils
import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, ReflectUtils, RequiredArgs, RequiredInfo}
import scala.collection.immutable.List
@ -432,7 +433,12 @@ object SwaggerDefinitionsJSON {
example_inbound_message = defaultJValue,
outboundAvroSchema = Some(defaultJValue),
inboundAvroSchema = Some(defaultJValue),
adapter_implementation = adapterImplementationJson
adapter_implementation = adapterImplementationJson,
requiredFieldInfo = {
val fieldNameApiVersions = FieldNameApiVersions("data.bankId", List(ApiVersion.v3_1_0.toString))
val requiredInfo = RequiredInfo(List(fieldNameApiVersions))
Some(requiredInfo)
}
)
val messageDocsJson = MessageDocsJson(message_docs = List(messageDocJson))

View File

@ -27,9 +27,9 @@ object CustomJsonFormats {
val formats: Formats = net.liftweb.json.DefaultFormats + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer
val losslessFormats: Formats = net.liftweb.json.DefaultFormats.lossless + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer
val emptyHintFormats = DefaultFormats.withHints(ShortTypeHints(List())) + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
implicit val nullTolerateFormats = formats + JNothingSerializer
@ -37,7 +37,7 @@ object CustomJsonFormats {
val dateFormat = net.liftweb.json.DefaultFormats.dateFormat
override val typeHints = ShortTypeHints(rolesMappedToClasses)
} + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer
} + BigDecimalSerializer + FiledRenameSerializer + ListResultSerializer + EnumValueSerializer + JsonAbleSerializer
}
object BigDecimalSerializer extends Serializer[BigDecimal] {

View File

@ -8,16 +8,13 @@ import code.api.v3_1_0.ListResult
import code.crm.CrmEvent.CrmEvent
import code.transactionrequests.TransactionRequestTypeCharge
import com.openbankproject.commons.model.{Product, _}
import com.openbankproject.commons.util.{EnumValue, OBPEnumeration}
import com.openbankproject.commons.util.{EnumValue, OBPEnumeration, ReflectUtils}
import net.liftweb.common.Full
import net.liftweb.json
import net.liftweb.json.{JDouble, JInt, JString}
import net.liftweb.json.JsonAST.{JArray, JBool, JObject, JValue}
import net.liftweb.util.StringHelpers
import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
object JSONFactory1_4_0 {
@ -403,19 +400,13 @@ object JSONFactory1_4_0 {
case v => v
}
val r = currentMirror.reflect(extractedEntity)
val mapOfFields: Map[String, Any] = extractedEntity match {
case ListResult(name, results) => Map((name, results))
case JObject(jFields) => jFields.map(it => (it.name, it.value)).toMap
case _ => r.symbol.typeSignature.members.toStream
.collect { case s: TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.get)
.toMap
case _ => ReflectUtils.getFieldValues(extractedEntity.asInstanceOf[AnyRef])()
}
val convertParamName = (name: String) => extractedEntity match {
case _ : JsonFieldReName => StringHelpers.snakify(name)
case _ => name
@ -457,7 +448,9 @@ object JSONFactory1_4_0 {
case Some(i: String) => "\"" + key + """": {"type":"string"}"""
case List(i: String, _*) => "\"" + key + """": {"type": "array","items": {"type": "string"}}"""
case Some(List(i: String, _*)) => "\"" + key + """": {"type": "array","items": {"type": "string"}}"""
//Int
case Array(i: String, _*) => "\"" + key + """": {"type": "array","items": {"type": "string"}}"""
case Some(Array(i: String, _*)) => "\"" + key + """": {"type": "array","items": {"type": "string"}}"""
//Int
case _: Int | _:JInt => "\"" + key + """": {"type":"integer"}"""
case Some(i: Int) => "\"" + key + """": {"type":"integer"}"""
case List(i: Int, _*) => "\"" + key + """": {"type": "array","items": {"type": "integer"}}"""

View File

@ -410,7 +410,7 @@ trait APIMethods220 {
messageDocs <- Full{connectorObject.messageDocs.toList}
} yield {
val json = JSONFactory220.createMessageDocsJson(messageDocs)
successJsonResponse(Extraction.decompose(json))
successJsonResponse(Extraction.decompose(json)(CustomJsonFormats.formats))
}
}
}

View File

@ -46,7 +46,7 @@ import code.model._
import com.openbankproject.commons.model.Product
import code.users.Users
import com.openbankproject.commons.model._
import com.openbankproject.commons.util.{ReflectUtils, RequiredArgs, RequiredFieldValidation}
import com.openbankproject.commons.util.{ReflectUtils, RequiredFieldValidation, RequiredInfo}
import net.liftweb.common.{Box, Full}
import net.liftweb.json.Extraction.decompose
import net.liftweb.json.JsonAST.JValue
@ -830,7 +830,7 @@ object JSONFactory220 extends CustomJsonFormats {
outboundAvroSchema: Option[JValue] = None,
inboundAvroSchema: Option[JValue] = None,
adapter_implementation : AdapterImplementationJson,
requiredFieldInfo: Map[String, RequiredArgs] = Map.empty
requiredFieldInfo: Option[RequiredInfo] = None
)
case class AdapterImplementationJson(
@ -849,8 +849,6 @@ object JSONFactory220 extends CustomJsonFormats {
def createMessageDocJson(md: MessageDoc): MessageDocJson = {
val inBoundType = ReflectUtils.getType(md.exampleInboundMessage)
val regex = Pattern.compile("""(code|com\.openbankproject\.commons)\..+""")
def findRequiredInfoFor(tp: Type): Boolean = regex.matcher(tp.toString).matches()
MessageDocJson(
process = md.process,
@ -868,7 +866,11 @@ object JSONFactory220 extends CustomJsonFormats {
md.adapterImplementation.map(_.group).getOrElse(""),
md.adapterImplementation.map(_.suggestedOrder).getOrElse(100)
),
requiredFieldInfo = RequiredFieldValidation.getAllNestedRequiredInfo(inBoundType, findRequiredInfoFor)
requiredFieldInfo = {
val requiredArgses = RequiredFieldValidation.getAllNestedRequiredInfo(inBoundType)
val requiredInfo = RequiredInfo(requiredArgses)
Some(requiredInfo)
}
)
}

View File

@ -1,5 +1,9 @@
package com.openbankproject.commons.util
import java.util.regex.Pattern
import scala.collection.{IterableLike, immutable}
import scala.collection.generic.CanBuildFrom
import scala.reflect.runtime.universe.Type
/**
* function utils
*/
@ -14,4 +18,48 @@ object Functions {
def doNothing[T, D]: PartialFunction[T,D] = {
case _ if false => ???
}
def truePredicate[T]: T => Boolean = _ => true
def falsePredicate[T]: T => Boolean = _ => false
private val obpTypeNamePattern = Pattern.compile("""(code|com\.openbankproject\.commons)\..+""")
def isOBPType(tp: Type) = obpTypeNamePattern.matcher(tp.typeSymbol.fullName).matches()
def isOBPClass(clazz: Class[_]) = obpTypeNamePattern.matcher(clazz.getName).matches()
implicit class RichCollection[A, Repr](iterable: IterableLike[A, Repr]){
def distinctBy[B, That](f: A => B)(implicit canBuildFrom: CanBuildFrom[Repr, A, That]) = {
val builder = canBuildFrom(iterable.repr)
val set = scala.collection.mutable.Set[B]()
iterable.foreach(it => {
val calculatedElement = f(it)
if(set.add(calculatedElement)) {
builder += it
}
})
builder.result
}
def toMapByKey[K](f: A => K): immutable.Map[K, A] = {
val b = immutable.Map.newBuilder[K, A]
for (x <- iterable)
b += f(x) -> x
b.result()
}
def toMapByValue[V](f: A => V): immutable.Map[A, V] = {
val b = immutable.Map.newBuilder[A, V]
for (x <- iterable)
b += x -> f(x)
b.result()
}
def toMap[K, V](keyFn: A => K, valueFn: A => V): immutable.Map[K, V] = {
val b = immutable.Map.newBuilder[K, V]
for (x <- iterable)
b += keyFn(x) -> valueFn(x)
b.result()
}
}
}

View File

@ -58,9 +58,9 @@ object ReflectUtils {
val TermName(fieldName) = it.name
if(it.isLazy) {
// get lazy value
fieldName -> instanceMirror.reflectMethod(it.asMethod)()
fieldName.trim -> instanceMirror.reflectMethod(it.asMethod)()
} else {
fieldName -> instanceMirror.reflectField(it).get
fieldName.trim -> instanceMirror.reflectField(it).get
}
})
.toMap

View File

@ -1,12 +1,13 @@
package com.openbankproject.commons.util
import java.util.Objects
import com.openbankproject.commons.util.ApiVersion.allVersion
import net.liftweb.json.JValue
import net.liftweb.json.JsonAST.{JArray, JString}
import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString}
import scala.annotation.StaticAnnotation
import scala.collection.immutable.ListMap
import scala.reflect.runtime.universe._
import Functions.RichCollection
/**
@ -37,8 +38,36 @@ class OBPRequired(value: Array[ApiVersion] = Array(ApiVersion.allVersion),
exclude: Array[ApiVersion] = Array.empty
) extends StaticAnnotation
/**
* cooperate with RequiredInfo to deal with generate swagger doc and json serialization problem (show wrong structure of json)
* @param filedName
* @param apiVersions
*/
case class FieldNameApiVersions(filedName: String, apiVersions: List[String])
case class RequiredArgs(include: Array[ApiVersion],
/**
* cooperate with RequiredInfo to deal with generate swagger doc and json serialization problem (show wrong structure of json)
* @param infos
*/
case class RequiredInfo(infos: List[FieldNameApiVersions]) extends JsonAble {
override def toJValue: JObject = {
val jFields = infos.map(info => JField(
info.filedName,
JArray(info.apiVersions.map(JString(_))))
)
JObject(jFields)
}
}
object RequiredInfo {
def apply(requiredArgs: Seq[RequiredArgs]): RequiredInfo = {
val fieldNameApiVersionses = requiredArgs.toList.map(arg => FieldNameApiVersions(arg.fieldPath, arg.apiVersions))
RequiredInfo(fieldNameApiVersionses)
}
}
case class RequiredArgs(fieldPath:String, include: Array[ApiVersion],
exclude: Array[ApiVersion] = Array.empty) extends JsonAble {
{
val includeAll = include.contains(allVersion)
@ -69,10 +98,10 @@ case class RequiredArgs(include: Array[ApiVersion],
}
override def equals(obj: Any): Boolean = obj match {
case RequiredArgs(inc, exc) => include.sameElements(inc) && exclude.sameElements(exc)
case RequiredArgs(path, inc, exc) => Objects.equals(fieldPath, path) && include.sameElements(inc) && exclude.sameElements(exc)
case _ => false
}
override def toJValue: JValue = (include, exclude) match {
override val toJValue: JArray = (include, exclude) match {
case (_, Array()) =>
val includeList = include.toList.map(_.toString).map(JString(_))
JArray(includeList)
@ -80,6 +109,10 @@ case class RequiredArgs(include: Array[ApiVersion],
val excludeList = include.toList.map("-" + _.toString).map(JString(_))
JArray(excludeList)
}
val apiVersions: List[String] = (include, exclude) match {
case (_, Array()) => include.toList.map(_.toString)
case _ => include.toList.map("-" + _.toString)
}
}
object RequiredFieldValidation {
@ -123,35 +156,43 @@ object RequiredFieldValidation {
* @param tp to process type
* @return map of field name to RequiredArgs
*/
def getAnnotations(tp: Type): Map[String, RequiredArgs] = {
def getAnnotations(tp: Type): Iterable[RequiredArgs] = {
val members = tp.members
val constructors = members.filter(_.isConstructor).map(_.asMethod)
def getFieldNameAndAnnotation(symbol: Symbol): List[(String, RequiredArgs)] = {
(symbol.name.decodedName, getAnnotation(symbol)) match{
case (TermName(name), Some(requiredArgs)) => List(name.trim -> requiredArgs)
case _ => Nil
def getFieldNameAndAnnotation(symbol: Symbol): Option[RequiredArgs] = {
val fieldName = symbol.name.decodedName.toString.trim
getAnnotation(fieldName, symbol) match{
case some: Some[RequiredArgs] => some
case _ => None
}
}
// constructor param name to RequiredArgs
val constructorParamToRequiredArgs: Map[String, RequiredArgs] = constructors
val constructorParamToRequiredArgs: Iterable[RequiredArgs] = constructors
.flatMap(_.paramLists.head) // all the constructor's parameters
.flatMap(getFieldNameAndAnnotation)
.toMap
.map(getFieldNameAndAnnotation)
.collect {
case Some(requiredArgs) => requiredArgs
}
val constructorParamNames = constructorParamToRequiredArgs.map(_.fieldPath).toSet
// those annotated field name to RequiredArgs
val annotatedFieldNameToRequiredArgs: Map[String, RequiredArgs] =
members.filter(it => {
!it.isConstructor && !constructorParamToRequiredArgs.contains(it.name.decodedName.toString.trim)
})
.flatMap(getFieldNameAndAnnotation)
.toMap
val requiredFields: Map[String, RequiredArgs] = constructorParamToRequiredArgs ++ annotatedFieldNameToRequiredArgs
requiredFields
val annotatedFieldNameToRequiredArgs: Iterable[RequiredArgs] =
members
.filter(it => {
!it.isConstructor && !constructorParamNames.contains(it.name.decodedName.toString.trim)
})
.map(getFieldNameAndAnnotation)
.collect {
case Some(requiredArgs) => requiredArgs
}
.distinctBy(_.fieldPath)
constructorParamToRequiredArgs ++ annotatedFieldNameToRequiredArgs
}
def getAnnotation(symbol: Symbol): Option[RequiredArgs] = {
def getAnnotation(fieldName: String, symbol: Symbol): Option[RequiredArgs] = {
val annotation: Option[Annotation] =
(symbol :: symbol.overrides)
.flatMap(_.annotations)
@ -159,47 +200,45 @@ object RequiredFieldValidation {
annotation.map { it: Annotation =>
it.tree.children.tail match {
case (include: Tree)::(exclude: Tree)::Nil => RequiredArgs(getVersions(include), getVersions(exclude))
case (include: Tree)::(exclude: Tree)::Nil => RequiredArgs(fieldName, getVersions(include), getVersions(exclude))
}
}
}
private def getAllNestedRequiredInfo(tp: Type,
fieldName: String,
predicate: Type => Boolean
): Map[String, RequiredArgs] = {
def getAllNestedRequiredInfo(tp: Type,
predicate: Type => Boolean = Functions.isOBPType,
fieldName: String = null
): Seq[RequiredArgs] = {
if(!predicate(tp)) {
return Map.empty
return Nil
}
val fieldToRequiredInfo: Map[String, RequiredArgs] = getAnnotations(tp)
val fieldToRequiredInfo: Iterable[RequiredArgs] = getAnnotations(tp)
// current type's fields full path to RequiredInfo
val currentPathToRequiredInfo = fieldName match {
case null => fieldToRequiredInfo
case _ => fieldToRequiredInfo.map(it=> (s"$fieldName.${it._1}", it._2))
case _ => fieldToRequiredInfo.map(it=> it.copy(fieldPath = s"$fieldName.${it.fieldPath}"))
}
// find all sub fields RequiredInfo
val subPathToRequiredInfo: Map[String, RequiredArgs] = tp.members.collect {
case m: MethodSymbolApi if m.isGetter => (m.name.decodedName.toString.trim, ReflectUtils.getNestFirstTypeArg(m.returnType))
case m: TermSymbolApi if m.isCaseAccessor || m.isVal => (m.name.decodedName.toString.trim, m.info)
}.filter(tuple => predicate(tuple._2))
val subPathToRequiredInfo: Iterable[RequiredArgs] = tp.members.collect {
case m: MethodSymbolApi if m.isGetter => {
(m.name.decodedName.toString.trim, ReflectUtils.getNestFirstTypeArg(m.returnType))
}
case m: TermSymbolApi if m.isCaseAccessor || m.isVal => {
(m.name.decodedName.toString.trim, ReflectUtils.getNestFirstTypeArg(m.info))
}
} .filter(tuple => predicate(tuple._2))
.distinctBy(_._1)
.flatMap(pair => {
val (memberName, membersType) = pair
val subFieldName = if(fieldName == null) memberName else s"$fieldName.$memberName"
getAllNestedRequiredInfo(membersType, subFieldName, predicate)
}).toMap
getAllNestedRequiredInfo(membersType, predicate, subFieldName)
})
currentPathToRequiredInfo ++ subPathToRequiredInfo
}
def getAllNestedRequiredInfo(tp: Type,
predicate: Type => Boolean = _ => true
): Map[String, RequiredArgs] = {
val unSortedResult = getAllNestedRequiredInfo(tp, null, predicate)
ListMap(unSortedResult.toSeq.sortBy(_._1): _*)
(currentPathToRequiredInfo ++ subPathToRequiredInfo).toSeq.sortBy(_.fieldPath)
}
}

View File

@ -1,65 +1,66 @@
package com.openbankproject.commons.util
import com.openbankproject.commons.util.ApiVersion
import com.openbankproject.commons.util.ApiVersion._
import org.scalatest.{FlatSpec, Matchers, Tag}
import org.scalatest.PartialFunctionValues._
import scala.reflect.runtime.universe._
import Functions.RichCollection
class RequiredFieldValidationTest extends FlatSpec with Matchers {
object tag extends Tag("RequiredFieldValidation")
"when annotated at constructor param and overriding val" should "all the annotations be extract by call RequiredFieldValidation.getAnnotations" taggedAs tag in {
val symbols = RequiredFieldValidation.getAnnotations(typeOf[Bar])
val symbols = RequiredFieldValidation.getAnnotations(typeOf[Bar]).toMapByKey(_.fieldPath)
symbols should have size 3
symbols("name") should equal(RequiredArgs(Array(ApiVersion.allVersion), Array(v3_0_0)))
symbols("name") should equal(RequiredArgs("name", Array(ApiVersion.allVersion), Array(v3_0_0)))
symbols("age") should equal (
RequiredArgs(Array(v2_0_0, v1_2_1, v1_4_0), Array.empty)
RequiredArgs("age", Array(v2_0_0, v1_2_1, v1_4_0), Array.empty)
)
symbols("email") should equal(RequiredArgs(Array(ApiVersion.allVersion), Array()))
symbols("email") should equal(RequiredArgs("email", Array(ApiVersion.allVersion), Array()))
}
"method RequiredFieldValidation.getAllNestedRequiredInfo" should "extract all nested required info" taggedAs tag in {
val stringToArgs: Map[String, RequiredArgs] = RequiredFieldValidation.getAllNestedRequiredInfo(typeOf[Outer])
val requiredArgses = RequiredFieldValidation.getAllNestedRequiredInfo(typeOf[Outer])
val stringToArgs: Map[String, RequiredArgs] = requiredArgses.toMapByKey(_.fieldPath)
val expectedRequireFooAnnoInfo = RequiredArgs(Array( v1_4_0))
stringToArgs.valueAt("requireFoo") should equal (expectedRequireFooAnnoInfo)
val expectedRequireFooAnnoInfo = RequiredArgs("", Array( v1_4_0))
stringToArgs.valueAt("requireFoo") should equal (expectedRequireFooAnnoInfo.copy(fieldPath = "requireFoo"))
val expectedNameAnnoInfo = RequiredArgs(Array(ApiVersion.allVersion))
val expectedAgeAnnoInfo = RequiredArgs(Array(v2_0_0, v1_2_1, v1_4_0))
val expectedEmailAnnoInfo = RequiredArgs(Array(ApiVersion.allVersion), Array(v2_0_0))
val expectedNameAnnoInfo = RequiredArgs("", Array(ApiVersion.allVersion))
val expectedAgeAnnoInfo = RequiredArgs("", Array(v2_0_0, v1_2_1, v1_4_0))
val expectedEmailAnnoInfo = RequiredArgs("", Array(ApiVersion.allVersion), Array(v2_0_0))
stringToArgs.valueAt("foo.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("foo.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("foo.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("foo.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "foo.name"))
stringToArgs.valueAt("foo.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "foo.age"))
stringToArgs.valueAt("foo.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "foo.email"))
stringToArgs.valueAt("requireFoo.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("requireFoo.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("requireFoo.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("requireFoo.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "requireFoo.name"))
stringToArgs.valueAt("requireFoo.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "requireFoo.age"))
stringToArgs.valueAt("requireFoo.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "requireFoo.email"))
stringToArgs.valueAt("list.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("list.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("list.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("list.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "list.name"))
stringToArgs.valueAt("list.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "list.age"))
stringToArgs.valueAt("list.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "list.email"))
stringToArgs.valueAt("array.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("array.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("array.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("array.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "array.name"))
stringToArgs.valueAt("array.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "array.age"))
stringToArgs.valueAt("array.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "array.email"))
val expectedMiddleRequiredAnnoInfo = RequiredArgs(Array(ApiVersion.allVersion))
stringToArgs.valueAt("middle.middleRequired") should equal (expectedMiddleRequiredAnnoInfo)
val expectedMiddleRequiredAnnoInfo = RequiredArgs("", Array(ApiVersion.allVersion))
stringToArgs.valueAt("middle.middleRequired") should equal (expectedMiddleRequiredAnnoInfo.copy(fieldPath = "middle.middleRequired"))
stringToArgs.valueAt("middle.middleRequired.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("middle.middleRequired.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("middle.middleRequired.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("middle.middleRequired.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "middle.middleRequired.name"))
stringToArgs.valueAt("middle.middleRequired.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "middle.middleRequired.age"))
stringToArgs.valueAt("middle.middleRequired.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "middle.middleRequired.email"))
stringToArgs.valueAt("middle.middleNoRequire.name") should equal (expectedNameAnnoInfo)
stringToArgs.valueAt("middle.middleNoRequire.age") should equal (expectedAgeAnnoInfo)
stringToArgs.valueAt("middle.middleNoRequire.email") should equal (expectedEmailAnnoInfo)
stringToArgs.valueAt("middle.middleNoRequire.name") should equal (expectedNameAnnoInfo.copy(fieldPath = "middle.middleNoRequire.name"))
stringToArgs.valueAt("middle.middleNoRequire.age") should equal (expectedAgeAnnoInfo.copy(fieldPath = "middle.middleNoRequire.age"))
stringToArgs.valueAt("middle.middleNoRequire.email") should equal (expectedEmailAnnoInfo.copy(fieldPath = "middle.middleNoRequire.email"))
stringToArgs should have size (20)
}

View File

@ -11,7 +11,7 @@
<inceptionYear>2011</inceptionYear>
<properties>
<scala.version>2.12</scala.version>
<scala.compiler>2.12.10</scala.compiler>
<scala.compiler>2.12.6</scala.compiler>
<akka.version>2.5.19</akka.version>
<akka-streams-kafka.version>0.22</akka-streams-kafka.version>
<kafka.version>1.1.0</kafka.version>