Skip to content

Commit

Permalink
finos#1296 add follow-up tests / naming improvements
Browse files Browse the repository at this point in the history
- remove java tests suite that was added only to gauge Java
  friendliness of the API.
  • Loading branch information
junaidzm13 committed May 1, 2024
1 parent d618f9c commit 0a114ad
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ private class ColumnValueParser(private val mapper: SchemaMapper) extends Filter

override def parse(columnName: String, columnValue: String): Either[ErrorMessage, ParsedResult[Any]] = {
mapper.externalSchemaField(columnName) match {
case Some(f) => RawColumnValueParser(f).parse(columnValue).map(ParsedResult(f, _))
case Some(f) => CoreParser(f).parse(columnValue).map(ParsedResult(f, _))
case None => Left(externalFieldNotFoundError(columnName))
}
}

override def parse(columnName: String, columnValues: List[String]): Either[ErrorMessage, ParsedResult[List[Any]]] = {
mapper.externalSchemaField(columnName) match {
case Some(f) => parseValues(RawColumnValueParser(f), columnValues)
case Some(f) => parseValues(CoreParser(f), columnValues)
case None => Left(externalFieldNotFoundError(columnName))
}
}

private def parseValues(parser: RawColumnValueParser,
private def parseValues(parser: CoreParser,
columnValues: List[String]): Either[ErrorMessage, ParsedResult[List[Any]]] = {
val (errors, parsedValues) = columnValues.partitionMap(parser.parse)
val combinedError = errors.mkString("\n")
Expand All @@ -56,8 +56,8 @@ private class ColumnValueParser(private val mapper: SchemaMapper) extends Filter
private def externalFieldNotFoundError(columnName: String): String =
s"Failed to find mapped external field for column `$columnName`"

private case class RawColumnValueParser(field: SchemaField) {
val column: Column = mapper.tableColumn(field.name).get
private case class CoreParser(field: SchemaField) {
val column: Column = mapper.internalVuuColumn(field.name).get

def parse(columnValue: String): Either[ErrorMessage, Any] = {
parseStringToColumnDataType(columnValue).flatMap(convertColumnValueToExternalFieldType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import scala.util.Try
* and vice versa.
* */
trait SchemaMapper {
def tableColumn(extFieldName: String): Option[Column]
def internalVuuColumn(extFieldName: String): Option[Column]
def externalSchemaField(columnName: String): Option[SchemaField]
def toMappedExternalFieldType(columnName: String, columnValue: Any): Option[Any]
def toMappedInternalColumnType(extFieldName: String, extFieldValue: Any): Option[Any]
Expand All @@ -31,7 +31,7 @@ private class SchemaMapperImpl(private val externalSchema: ExternalEntitySchema,
private val externalFieldByColumnName: Map[String, SchemaField] = getExternalSchemaFieldsByColumnName
private val internalColumnByExtFieldName: Map[String, Column] = getTableColumnByExternalField

override def tableColumn(extFieldName: String): Option[Column] = internalColumnByExtFieldName.get(extFieldName)
override def internalVuuColumn(extFieldName: String): Option[Column] = internalColumnByExtFieldName.get(extFieldName)
override def externalSchemaField(columnName: String): Option[SchemaField] = externalFieldByColumnName.get(columnName)

override def toInternalRowMap(externalValues: List[_]): Map[String, Any] = toInternalRowMap(externalValues.toArray)
Expand All @@ -47,13 +47,13 @@ private class SchemaMapperImpl(private val externalSchema: ExternalEntitySchema,

override def toMappedExternalFieldType(columnName: String, columnValue: Any): Option[Any] = {
externalSchemaField(columnName).flatMap(field => {
val col = tableColumn(field.name).get
val col = internalVuuColumn(field.name).get
safeTypeConvert(columnValue, castToAny(col.dataType), field.dataType)
})
}

override def toMappedInternalColumnType(extFieldName: String, extFieldValue: Any): Option[Any] = {
tableColumn(extFieldName).flatMap(col => {
internalVuuColumn(extFieldName).flatMap(col => {
val field = externalSchemaField(col.name).get
safeTypeConvert(extFieldValue, castToAny(field.dataType), col.dataType)
})
Expand Down

This file was deleted.

This file was deleted.

160 changes: 93 additions & 67 deletions vuu/src/test/scala/org/finos/vuu/util/schema/SchemaMapperTest.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.finos.vuu.util.schema

import org.finos.vuu.core.module.vui.VuiStateModule.stringToFieldDef
import org.finos.vuu.core.table.{Column, Columns, SimpleColumn}
import org.finos.vuu.core.table.{Column, SimpleColumn}
import org.finos.vuu.util.schema.SchemaMapper.InvalidSchemaMapException
import org.finos.vuu.util.schema.SchemaMapperTest.{externalFields, externalSchema, fieldsMap, fieldsMapWithoutAssetClass, internalColumns}
import org.finos.vuu.util.schema.SchemaMapperTest.{column, externalSchema, fieldsMap, fieldsMapWithoutAssetClass, givenColumns, givenExternalSchema, internalColumns, schemaField}
import org.finos.vuu.util.types.{TypeConverter, TypeConverterContainerBuilder}
import org.scalatest.featurespec.AnyFeatureSpec
import org.scalatest.matchers.should.Matchers
Expand Down Expand Up @@ -80,36 +79,36 @@ class SchemaMapperTest extends AnyFeatureSpec with Matchers {
field.get shouldEqual SchemaField("externalRic", classOf[String], 1)
}

Scenario("returns None if column not present in the mapped fields") {
Scenario("returns None if column not present in mapped fields") {
val field = mapper.externalSchemaField("assetClass")
field shouldEqual None
}
}

Feature("tableColumn") {
Feature("internalVuuColumn") {
val mapper = SchemaMapperBuilder(externalSchema, internalColumns)
.withFieldsMap(fieldsMapWithoutAssetClass)
.build()

Scenario("can get internal column from external field name") {
val column = mapper.tableColumn("externalRic")
val column = mapper.internalVuuColumn("externalRic")
column.get shouldEqual SimpleColumn("ric", 1, classOf[String])
}

Scenario("returns None if external field not present in the mapped fields") {
val column = mapper.tableColumn("assetClass")
Scenario("returns None if external field not present in mapped fields") {
val column = mapper.internalVuuColumn("assetClass")
column shouldEqual None
}
}

Feature("toMappedExternalFieldType") {
val bigDecimalSchemaField = SchemaField("bigDecimalPrice", classOf[BigDecimal], 0)
val doubleColumn = SimpleColumn("doublePrice", 0, classOf[Double])
val bigDecimalSchemaField = schemaField("bigDecimalPrice", classOf[BigDecimal], 0)
val doubleColumn = column("doublePrice", classOf[Double], 0)
val tcContainer = TypeConverterContainerBuilder().withoutDefaults()
.withConverter(TypeConverter[BigDecimal, Double](classOf[BigDecimal], classOf[Double], _.doubleValue()))
.withConverter(TypeConverter[Double, BigDecimal](classOf[Double], classOf[BigDecimal], new BigDecimal(_)))
.build()
val schemaMapper = SchemaMapperBuilder(TestEntitySchema(List(bigDecimalSchemaField)), Array(doubleColumn))
val schemaMapper = SchemaMapperBuilder(givenExternalSchema(bigDecimalSchemaField), givenColumns(doubleColumn))
.withFieldsMap(Map("bigDecimalPrice" -> "doublePrice"))
.withTypeConverters(tcContainer)
.build()
Expand All @@ -131,13 +130,13 @@ class SchemaMapperTest extends AnyFeatureSpec with Matchers {
}

Feature("toMappedInternalColumnType") {
val bigDecimalSchemaField = SchemaField("bigDecimalPrice", classOf[BigDecimal], 0)
val doubleColumn = SimpleColumn("doublePrice", 0, classOf[Double])
val bigDecimalSchemaField = schemaField("bigDecimalPrice", classOf[BigDecimal], 0)
val doubleColumn = column("doublePrice", classOf[Double], 0)
val tcContainer = TypeConverterContainerBuilder().withoutDefaults()
.withConverter(TypeConverter[BigDecimal, Double](classOf[BigDecimal], classOf[Double], _.doubleValue()))
.withConverter(TypeConverter[Double, BigDecimal](classOf[Double], classOf[BigDecimal], new BigDecimal(_)))
.build()
val schemaMapper = SchemaMapperBuilder(TestEntitySchema(List(bigDecimalSchemaField)), Array(doubleColumn))
val schemaMapper = SchemaMapperBuilder(givenExternalSchema(bigDecimalSchemaField), givenColumns(doubleColumn))
.withFieldsMap(Map("bigDecimalPrice" -> "doublePrice"))
.withTypeConverters(tcContainer)
.build()
Expand All @@ -160,76 +159,98 @@ class SchemaMapperTest extends AnyFeatureSpec with Matchers {

Feature("validation on instantiation") {
Scenario("fails when mapped external field not found in external schema") {
val externalSchema = givenExternalSchema(schemaField("externalId"))
val columns = givenColumns(column("ric"))

val exception = intercept[InvalidSchemaMapException](
SchemaMapperBuilder(externalSchema, Columns.fromNames("ric".int()))
.withFieldsMap(Map("non-existent" -> "ric")).build()
SchemaMapperBuilder(externalSchema, columns).withFieldsMap(Map("externalRic" -> "ric")).build()
)

exception shouldBe a[RuntimeException]
exception.getMessage should include regex s"[Ff]ield `non-existent` not found"
exception.getMessage should include regex s"[Ff]ield `externalRic` not found"
}

Scenario("fails when mapped internal field not found in internal columns") {
val externalSchema = givenExternalSchema(schemaField("externalId"))
val columns = givenColumns(column("ric"))

val exception = intercept[InvalidSchemaMapException](
SchemaMapperBuilder(externalSchema, Columns.fromNames("id".int()))
.withFieldsMap(Map("externalId" -> "absent-col")).build()
SchemaMapperBuilder(externalSchema, columns).withFieldsMap(Map("externalId" -> "id")).build()
)

exception shouldBe a[RuntimeException]
exception.getMessage should include regex "[Cc]olumn `absent-col` not found"
exception.getMessage should include regex "[Cc]olumn `id` not found"
}

Scenario("fails when external->internal map contains duplicated internal fields") {
val externalSchema = givenExternalSchema(schemaField("externalId"), schemaField("parentId"))
val columns = givenColumns(column("id"))

val exception = intercept[InvalidSchemaMapException](
SchemaMapperBuilder(externalSchema, Columns.fromNames("id".int(), "ric".string()))
.withFieldsMap(Map("externalId" -> "id", "externalRic" -> "id"))
.build()
SchemaMapperBuilder(externalSchema, columns).withFieldsMap(Map("externalId" -> "id", "parentId" -> "id")).build()
)

exception shouldBe a[RuntimeException]
exception.getMessage should include("duplicated column names")
}

Scenario("fails when types differ b/w mapped fields and type converter is not provided") {
val externalSchema = givenExternalSchema(schemaField("externalId", classOf[Long], index = 0))
val columns = givenColumns(column("id", classOf[String], index = 0))
val emptyTypeConverterContainer = TypeConverterContainerBuilder().withoutDefaults().build()

val exception = intercept[InvalidSchemaMapException](
SchemaMapperBuilder(externalSchema, internalColumns)
.withFieldsMap(fieldsMap)
SchemaMapperBuilder(externalSchema, columns)
.withFieldsMap(Map("externalId" -> "id"))
.withTypeConverters(emptyTypeConverterContainer)
.build()
)

exception.getMessage should include regex ".*TypeConverter.* not found.*"
exception.message should include("[ java.lang.Double->java.lang.String ]")
exception.message should include("[ java.lang.String->java.lang.Double ]")
exception.message should include("[ java.lang.Long->java.lang.String ]")
exception.message should include("[ java.lang.String->java.lang.Long ]")
}
}

Feature("Build schema mapper without user-defined fields map") {
Scenario("can generate mapper with exact fields matched by index") {
val mapper = SchemaMapperBuilder(externalSchema, internalColumns).build()
val externalSchema = givenExternalSchema(schemaField("externalId", index = 0), schemaField("priceExt", index = 1))
val columns = givenColumns(column("id", index = 0), column("price", index = 1))

mapper.tableColumn("externalId").get.name shouldEqual "id"
mapper.tableColumn("externalRic").get.name shouldEqual "ric"
mapper.tableColumn("assetClass").get.name shouldEqual "assetClass"
mapper.tableColumn("price").get.name shouldEqual "price"
mapper.tableColumn("side").get.name shouldEqual "side"
val mapper = SchemaMapperBuilder(externalSchema, columns).build()

mapper.internalVuuColumn("externalId").get.name shouldEqual "id"
mapper.internalVuuColumn("priceExt").get.name shouldEqual "price"
}

Scenario("can generate mapper with exact fields even when the fields are not ordered by their index") {
val externalSchema = givenExternalSchema(schemaField("priceExt", index = 1), schemaField("externalId", index = 0))
val columns = givenColumns(column("id", index = 0), column("price", index = 1))

val mapper = SchemaMapperBuilder(externalSchema, columns).build()

mapper.internalVuuColumn("externalId").get.name shouldEqual "id"
mapper.internalVuuColumn("priceExt").get.name shouldEqual "price"
}

Scenario("can generate mapper when an external field has no matched column") {
val mapper = SchemaMapperBuilder(externalSchema, internalColumns.slice(0, 3)).build()
val externalSchema = givenExternalSchema(schemaField("priceExt", index = 1), schemaField("externalId", index = 0))
val columns = givenColumns(column("id", index = 0))

val mapper = SchemaMapperBuilder(externalSchema, columns).build()

mapper.tableColumn("externalId") shouldBe empty
mapper.tableColumn("externalRic").get.name shouldEqual "ric"
mapper.tableColumn("assetClass").get.name shouldEqual "assetClass"
mapper.tableColumn("price").get.name shouldEqual "price"
mapper.tableColumn("side") shouldBe empty
mapper.internalVuuColumn("externalId").get.name shouldEqual "id"
mapper.internalVuuColumn("priceExt") shouldBe empty
}

Scenario("can generate mapper when a column has no matched external field") {
val mapper = SchemaMapperBuilder(TestEntitySchema(externalFields.slice(0, 3)), internalColumns).build()
val externalSchema = givenExternalSchema(schemaField("priceExt", index = 1))
val columns = givenColumns(column("id", index = 0), column("price", index = 1))

mapper.tableColumn("externalId").get.name shouldEqual "id"
mapper.tableColumn("externalRic") shouldBe empty
mapper.tableColumn("assetClass").get.name shouldBe "assetClass"
mapper.tableColumn("price").get.name shouldEqual "price"
mapper.tableColumn("side") shouldBe empty
val mapper = SchemaMapperBuilder(externalSchema, columns).build()

mapper.externalSchemaField("price").get.name shouldEqual "priceExt"
mapper.externalSchemaField("id") shouldBe empty
}
}
}
Expand All @@ -240,36 +261,41 @@ private case class TestDto(externalId: Int, externalRic: String, assetClass: Str

private object SchemaMapperTest {

// no need to be sorted by their index
val externalFields: List[SchemaField] = List(
SchemaField("externalId", classOf[Int], 0),
SchemaField("assetClass", classOf[String], 2),
SchemaField("price", classOf[String], 3),
SchemaField("externalRic", classOf[String], 1),
SchemaField("side", classOf[java.lang.Character], 4),
private def givenExternalSchema(fields: SchemaField*): ExternalEntitySchema = TestEntitySchema(fields.toList)

private def schemaField(name: String, dataType: Class[_] = classOf[Any], index: Int = -1): SchemaField =
SchemaField(name, dataType, index)

private def givenColumns(columns: Column*): Array[Column] = columns.toArray

private def column(name: String, dataType: Class[_] = classOf[Any], index: Int = -1): Column =
SimpleColumn(name, index, dataType)

private val externalFields: List[SchemaField] = List(
schemaField("externalId", classOf[Int], 0),
schemaField("externalRic", classOf[String], 1),
schemaField("assetClass", classOf[String], 2),
schemaField("price", classOf[String], 3),
schemaField("side", classOf[java.lang.Character], 4),
)

val externalSchema: TestEntitySchema = TestEntitySchema(externalFields)

val internalColumns: Array[Column] = {
val columns = Columns.fromNames(
"id".int(),
"ric".string(),
"assetClass".string(),
"price".double(),
"side".char(),
)
// no need to be sorted by their index
columns.tail.appended(columns.head)
}
private val externalSchema: ExternalEntitySchema = givenExternalSchema(externalFields: _*)

private val internalColumns: Array[Column] = givenColumns(
column("id", classOf[Int], 0),
column("ric", classOf[String], 1),
column("assetClass", classOf[String], 2),
column("price", classOf[Double], 3),
column("side", classOf[Char], 4),
)

val fieldsMap: Map[String, String] = Map(
private val fieldsMap: Map[String, String] = Map(
"externalId" -> "id",
"externalRic" -> "ric",
"price" -> "price",
"assetClass" -> "assetClass",
"side" -> "side",
)

val fieldsMapWithoutAssetClass: Map[String, String] = fieldsMap.filter({ case (k, _) => k != "assetClass"})
private val fieldsMapWithoutAssetClass: Map[String, String] = fieldsMap.filter({ case (k, _) => k != "assetClass"})
}

0 comments on commit 0a114ad

Please sign in to comment.