Skip to content

Commit 767f281

Browse files
author
Andrew O'Hara
authored
Gh9 (#10)
🐛 throw expected exception type on mapping error
1 parent b027b29 commit 767f281

File tree

3 files changed

+112
-4
lines changed

3 files changed

+112
-4
lines changed

build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
kotlin("jvm") version "1.9.0"
2+
kotlin("jvm") version "1.9.10"
33
id("jacoco")
44
id("maven-publish")
55
}
@@ -19,11 +19,12 @@ dependencies {
1919
implementation(kotlin("reflect"))
2020

2121
api("software.amazon.awssdk:dynamodb-enhanced:2.20.86+")
22+
implementation("software.amazon.awssdk:aws-crt-client:2.20.86+")
2223

2324
testImplementation(kotlin("test"))
2425
testImplementation("io.kotest:kotest-assertions-core-jvm:5.6.2")
25-
testImplementation("org.http4k:http4k-aws:5.7.2.0")
26-
testImplementation("org.http4k:http4k-connect-amazon-dynamodb-fake:5.1.5.0")
26+
testImplementation("org.http4k:http4k-aws:5.8.0.0")
27+
testImplementation("org.http4k:http4k-connect-amazon-dynamodb-fake:5.2.0.0")
2728
}
2829

2930
tasks.test {

src/main/kotlin/io/andrewohara/dynamokt/DataClassTableSchema.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.andrewohara.dynamokt
33
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType
44
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
55
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
6+
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException
7+
import java.lang.IllegalArgumentException
68
import kotlin.reflect.KClass
79
import kotlin.reflect.full.*
810

@@ -21,7 +23,11 @@ class DataClassTableSchema<Item: Any>(dataClass: KClass<Item>): TableSchema<Item
2123
.mapNotNull { attr -> attributes[attr.attributeName]?.unConvert(attributeMap) }
2224
.toMap()
2325

24-
return constructor.callBy(arguments)
26+
return try {
27+
constructor.callBy(arguments)
28+
} catch (e: Throwable) {
29+
throw IllegalArgumentException("Could not map item to ${type.rawClass().simpleName}", e)
30+
}
2531
}
2632

2733
override fun itemToMap(item: Item, ignoreNulls: Boolean): Map<String, AttributeValue> {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.andrewohara.dynamokt
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
5+
import io.kotest.matchers.collections.shouldHaveSize
6+
import io.kotest.matchers.nulls.shouldNotBeNull
7+
import io.kotest.matchers.types.shouldBeInstanceOf
8+
import org.http4k.aws.AwsSdkAsyncClient
9+
import org.http4k.connect.amazon.dynamodb.FakeDynamoDb
10+
import org.junit.jupiter.api.Test
11+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
12+
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient
13+
import software.amazon.awssdk.enhanced.dynamodb.Key
14+
import software.amazon.awssdk.regions.Region
15+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
16+
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException
17+
import java.util.concurrent.ExecutionException
18+
19+
private const val TABLE_NAME = "INSERT_NAME_OF_YOUR_TABLE_HERE"
20+
21+
class GH9Test {
22+
23+
private val enhancedClient by lazy {
24+
DynamoDbEnhancedAsyncClient.builder()
25+
.dynamoDbClient(
26+
DynamoDbAsyncClient.builder()
27+
.httpClient(AwsSdkAsyncClient(FakeDynamoDb()))
28+
.credentialsProvider { AwsBasicCredentials.create("key", "id") }
29+
.region(Region.CA_CENTRAL_1)
30+
.build()
31+
)
32+
.build()
33+
}
34+
35+
@Test
36+
fun `get item with wrong schema`() {
37+
val legacyClient = enhancedClient
38+
.table(TABLE_NAME, DataClassTableSchema(TestEntityV1::class))
39+
.also { it.createTable().get() }
40+
41+
val entity1 = TestEntityV1(id = "valid", name = "valid name")
42+
val entity2 = TestEntityV1(id = "invalid", name = null)
43+
44+
legacyClient.putItem(entity1).shouldNotBeNull().get()
45+
legacyClient.putItem(entity2).shouldNotBeNull().get()
46+
47+
val newClient = enhancedClient
48+
.table(TABLE_NAME, DataClassTableSchema(TestEntityV2::class))
49+
50+
shouldThrow<ExecutionException> {
51+
newClient.getItem(Key.builder().partitionValue("invalid").build()).get()
52+
}.cause.shouldBeInstanceOf<IllegalArgumentException>()
53+
}
54+
55+
@Test
56+
fun `get item from missing table`() {
57+
val testClient = enhancedClient
58+
.table(TABLE_NAME, DataClassTableSchema(TestEntityV1::class))
59+
60+
shouldThrow<ExecutionException> {
61+
testClient.getItem(Key.builder().partitionValue("foo").build()).get()
62+
}.cause.shouldBeInstanceOf<ResourceNotFoundException>()
63+
}
64+
65+
@Test
66+
fun `mapping error on scan`() {
67+
val legacyClient = enhancedClient
68+
.table(TABLE_NAME, DataClassTableSchema(TestEntityV1::class))
69+
.also { it.createTable().get() }
70+
71+
val entity1 = TestEntityV1(id = "valid", name = "valid name")
72+
val entity2 = TestEntityV1(id = "invalid", name = null)
73+
74+
legacyClient.putItem(entity1).shouldNotBeNull().get()
75+
legacyClient.putItem(entity2).shouldNotBeNull().get()
76+
77+
val newClient = enhancedClient
78+
.table(TABLE_NAME, DataClassTableSchema(TestEntityV2::class))
79+
80+
shouldThrow<ExecutionException> {
81+
newClient.scan().subscribe {
82+
it.items().shouldHaveSize(3)
83+
it.items().shouldContainExactlyInAnyOrder(entity1, entity2)
84+
}.get()
85+
}.cause.shouldBeInstanceOf<IllegalArgumentException>()
86+
}
87+
}
88+
89+
data class TestEntityV2(
90+
@DynamoKtPartitionKey
91+
var id: String,
92+
val name: String,
93+
val otherField: String = "abc"
94+
)
95+
96+
data class TestEntityV1(
97+
@DynamoKtPartitionKey
98+
var id: String,
99+
val name: String?,
100+
val otherField: String = "abc"
101+
)

0 commit comments

Comments
 (0)