Skip to content

Commit

Permalink
finos#1296 TypeConverterContainer improvements + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
junaidzm13 committed Apr 26, 2024
1 parent d43d0f9 commit 4709a10
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 15 deletions.
18 changes: 13 additions & 5 deletions vuu/src/main/scala/org/finos/vuu/util/schema/SchemaMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import org.finos.vuu.core.table.Column
import org.finos.vuu.util.schema.SchemaMapper.InvalidSchemaMapException
import org.finos.vuu.util.schema.typeConversion.{TypeConverter, TypeConverterContainer, TypeConverterContainerBuilder, TypeUtils}

import scala.util.Try


/**
* This class provides utility methods related to mapping external fields to internal columns
Expand Down Expand Up @@ -50,21 +52,27 @@ 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
typeConverterContainer.convert(columnValue, castToAny(col.dataType), field.dataType)
safeTypeConvert(columnValue, castToAny(col.dataType), field.dataType)
})
}

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

override def convertExternalValueToString(extFieldName: String, extFieldValue: Any): Option[String] = {
extFieldsMap.get(extFieldName).flatMap(
f => typeConverterContainer.convert(extFieldValue, castToAny(f.dataType), classOf[String])
)
extFieldsMap.get(extFieldName).flatMap(f => safeTypeConvert(extFieldValue, castToAny(f.dataType), classOf[String]))
}

/**
* Required since we're using `Any` type, so good to guard against any values being passed in that doesn't
* match the field/column type.
* */
private def safeTypeConvert[T](value: Any, fromClass: Class[Any], toClass: Class[T]): Option[T] = {
Try(typeConverterContainer.convert(value, fromClass, toClass).get).toOption
}

private def castToAny(cls: Class[_]): Class[Any] = cls.asInstanceOf[Class[Any]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.finos.vuu.util.schema.typeConversion

import scala.util.Try

trait TypeConverterContainer {
def convert[From, To](value: From, fromClass: Class[From], toClass: Class[To]): Option[To]
def typeConverter[From, To](fromClass: Class[From], toClass: Class[To]): Option[TypeConverter[From, To]]
Expand All @@ -14,9 +12,9 @@ private case class TypeConverterContainerImpl(

override def convert[From, To](value: From, fromClass: Class[From], toClass: Class[To]): Option[To] = {
if (TypeUtils.areTypesEqual(fromClass, toClass)) {
return Try(value.asInstanceOf[To]).toOption
return Some(value.asInstanceOf[To])
}
typeConverter[From, To](fromClass, toClass).flatMap(tc => Try(tc.convert(value)).toOption)
typeConverter[From, To](fromClass, toClass).map(tc => tc.convert(value))
}

override def typeConverter[From, To](fromClass: Class[From], toClass: Class[To]): Option[TypeConverter[From, To]] = {
Expand All @@ -25,7 +23,7 @@ private case class TypeConverterContainerImpl(
}

private def typeConverter[From, To](name: String): Option[TypeConverter[From, To]] = {
typeConverterByName.get(name).flatMap(tc => Try(tc.asInstanceOf[TypeConverter[From, To]]).toOption)
typeConverterByName.get(name).map(tc => tc.asInstanceOf[TypeConverter[From, To]])
}
}

Expand All @@ -39,6 +37,15 @@ case class TypeConverterContainerBuilder private (private val converters: List[T
this.copy(converters = converters ++ List(t))
}

def with2WayConverter[T1, T2](cls1: Class[T1],
cls2: Class[T2],
converter1: T1 => T2,
converter2: T2 => T1): TypeConverterContainerBuilder = {
val tc1 = TypeConverter(cls1, cls2, converter1)
val tc2 = TypeConverter(cls2, cls1, converter2)
this.copy(converters = converters ++ List(tc1, tc2))
}

def withoutDefaults(): TypeConverterContainerBuilder = this.copy(withDefaults = false)

def build(): TypeConverterContainer = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ class TypeConverterContainerTest extends AnyFeatureSpec with Matchers {
.build()

Scenario("contains default converters") {
tcContainer.typeConverter(classOf[String], classOf[Long]).nonEmpty should be(true)
tcContainer.typeConverter(classOf[String], classOf[Double]).nonEmpty should be(true)
tcContainer.typeConverter(classOf[String], classOf[Long]) should not be empty
tcContainer.typeConverter(classOf[String], classOf[Double]) should not be empty
}

Scenario("contains user defined converters") {
tcContainer.typeConverter(userDefinedConverter.fromClass, userDefinedConverter.toClass).nonEmpty should be(true)
tcContainer.typeConverter(userDefinedConverter.fromClass, userDefinedConverter.toClass) should not be empty
tcContainer.typeConverter(
userDefinedConverterOverridesADefault.fromClass, userDefinedConverterOverridesADefault.toClass
).nonEmpty shouldBe true
) should not be empty
}

Scenario("user defined overrides any default converters for the same types") {
Expand All @@ -48,7 +48,39 @@ class TypeConverterContainerTest extends AnyFeatureSpec with Matchers {
}

Scenario("contains added user defined converter") {
tcContainer.typeConverter(userDefinedConverter.fromClass, userDefinedConverter.toClass).nonEmpty shouldBe true
tcContainer.typeConverter(userDefinedConverter.fromClass, userDefinedConverter.toClass) should not be empty
}
}

Feature("TypeConverterContainer.convert") {
val converter = TypeConverter[Double, String](classOf[Double], classOf[String], _.toString)
val container = TypeConverterContainerBuilder()
.withoutDefaults()
.withConverter(converter)
.build()

Scenario("should simply return the same value if From & To types are equal even if no converter found") {
container.convert[Char, java.lang.Character]('A', classOf[Char], classOf[java.lang.Character]).get should equal('A')
}

Scenario("should return converted value if required type converter exists") {
container.convert(10.55, classOf[Double], classOf[String]).get should equal("10.55")
}

Scenario("should return empty if type converter not found") {
container.convert(10.55, classOf[Double], classOf[Long]) shouldBe empty
}
}

Feature("TypeConverterContainerBuilder.with2WayConverter") {
val container = TypeConverterContainerBuilder()
.withoutDefaults()
.with2WayConverter[Long, String](classOf[Long], classOf[String], _.toString, _.toLong)
.build()

Scenario("should build both converters") {
container.typeConverter(classOf[Long], classOf[String]) should not be empty
container.typeConverter(classOf[String], classOf[Long]) should not be empty
}
}
}

0 comments on commit 4709a10

Please sign in to comment.