diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Column.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Column.kt index 933e07e..d926255 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Column.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Column.kt @@ -48,7 +48,13 @@ data class Column( /** * True if the column is nullable */ - val isNullable: Boolean + val isNullable: Boolean, + + /** + * A converter which will be used in [Table.mapMapper] + * default value is set to maintain source compatibility with possible constructor invocations + */ + val mapConverter: (String) -> T = NoMapConverter ) { /** * A type-safe variant of `to` @@ -64,4 +70,10 @@ data class Column( override fun toString(): String { return "Column($name id=$id version=$version nullable=$isNullable)" // Prevent NPE in debugger on "property" } -} \ No newline at end of file + + companion object { + val NoMapConverter: (String) -> Nothing = + { throw UnsupportedOperationException("there's no mapConverter on this column") } + } + +} diff --git a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt index efc2e5d..b1bbe4c 100644 --- a/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt +++ b/mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Table.kt @@ -95,12 +95,17 @@ abstract class Table(val name: String, val config: TableConfigurati id: Boolean = false, version: Boolean = false, notNull: Boolean = !property.returnType.isMarkedNullable, - default: R = default(property.returnType), + default: R = default(property.returnType), converter: Converter = converter(property.returnType), name: String? = null, - selectByDefault: Boolean = true): DelegatedColumn { + selectByDefault: Boolean = true, + mapConverter: (String) -> R = Column.NoMapConverter): DelegatedColumn { + + val column = Column( + { property.get(it) }, default, converter, + name ?: "", id, version, selectByDefault, !notNull, + mapConverter) - val column = Column({ property.get(it) }, default, converter, name ?: "", id, version, selectByDefault, !notNull) return DelegatedColumn(column) } @@ -112,11 +117,15 @@ abstract class Table(val name: String, val config: TableConfigurati default: R = default(property.returnType), converter: Converter = converter(property.returnType), name: String? = null, - selectByDefault: Boolean = true - + selectByDefault: Boolean = true, + mapConverter: (String) -> R = Column.NoMapConverter ): DelegatedColumn { - val column = Column({ property.get(path(it)) }, default, converter, name ?: "", id, version, selectByDefault, !notNull) + val column = Column( + { property.get(path(it)) }, default, converter, + name ?: "", id, version, selectByDefault, !notNull, + mapConverter) + return DelegatedColumn(column) } @@ -130,11 +139,15 @@ abstract class Table(val name: String, val config: TableConfigurati version: Boolean = false, converter: Converter = converter(property.returnType), name: String? = null, - selectByDefault: Boolean = true - + selectByDefault: Boolean = true, + mapConverter: (String) -> R = Column.NoMapConverter ): DelegatedColumn { - val column = Column({ path(it)?.let { property.get(it) } }, null, optional(converter), name ?: "", id, version, selectByDefault, true) + val column = Column( + { path(it)?.let { property.get(it) } }, null, optional(converter), + name ?: "", id, version, selectByDefault, true, + mapConverter) + return DelegatedColumn(column) } @@ -199,13 +212,32 @@ abstract class Table(val name: String, val config: TableConfigurati return map } - fun rowMapper(columns: Set> = defaultColumns, nf: (Column) -> String = columnName): (Row) -> T { + fun rowMapper( + columns: Set> = defaultColumns, nf: (Column) -> String = columnName + ): (Row) -> T { return { row -> + create(object : Value { + override fun of(column: Column): R = + if (column in columns) column.converter.from(row, nf(column)) else column.defaultValue + }) + } + } + + fun mapMapper( + columns: Set> = defaultColumns, nf: (Column) -> String = columnName + ): (Map) -> T { + return { map -> create(object : Value { override fun of(column: Column): R { - return if (columns.contains(column)) column.converter.from(row, nf(column)) else column.defaultValue + val fieldName = nf(column) + + return if (column in columns && fieldName in map) + column.mapConverter(map[fieldName]!!) + else + column.defaultValue } }) } } + } \ No newline at end of file diff --git a/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/MapMapperTest.kt b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/MapMapperTest.kt new file mode 100644 index 0000000..37a32a9 --- /dev/null +++ b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/MapMapperTest.kt @@ -0,0 +1,67 @@ +package com.github.andrewoma.kwery.mappertest + +import com.github.andrewoma.kwery.mapper.Column +import com.github.andrewoma.kwery.mapper.SimpleConverter +import com.github.andrewoma.kwery.mapper.Table +import com.github.andrewoma.kwery.mapper.Value +import org.junit.Test +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.* +import kotlin.test.assertEquals + +class MapMapperTest { + + @Test + fun testMapMapping() { + val id = UUID.randomUUID() + val now = LocalDateTime.now() + val something = mapOf( + "id" to id.toString(), + "name" to "Whatever", + "lastUpdated" to now.format(DateTimeFormatter.ISO_DATE_TIME)!! + ) + + val mapped = someTable.mapMapper()(something) + + assertEquals(id, mapped.id) + assertEquals("Whatever", mapped.name) + assertEquals(now, mapped.lastUpdated) + } + + class Something( + val id: UUID, + val name: String, + val lastUpdated: LocalDateTime + ) + + object someTable : Table(" won't ever be used ") { + + val Id + by col(Something::id, name = "id", id = true, default = DefaultUuid, converter = UuidConverter, mapConverter = UUID::fromString) + + val Name + by col(Something::name, name = "name", mapConverter = { it }) + + val LastUpdated + by col(Something::lastUpdated, name = "lastUpdated", mapConverter = { LocalDateTime.parse(it, DateTimeFormatter.ISO_DATE_TIME) }) + + + override fun idColumns(id: UUID): Set, *>> = + setOf(Id of id) + + override fun create(value: Value): Something = Something( + value of Id, + value of Name, + value of LastUpdated + ) + + } + +} + +private val DefaultUuid = UUID(0, 0) + +object UuidConverter : SimpleConverter( + { row, name -> row.obj(name) as UUID } +) diff --git a/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/issues/Issue11_12.kt b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/issues/Issue11_12.kt index d117f31..ca8e488 100644 --- a/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/issues/Issue11_12.kt +++ b/mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/issues/Issue11_12.kt @@ -14,7 +14,8 @@ class Issue11_12 { val emailColumn = Column( User::email, null, optional(stringConverter), - "email", false, false, true, false + "email", false, false, true, false, + Column.NoMapConverter ) usersTable.addColumn(emailColumn)