Skip to content

Commit b08e24d

Browse files
authored
feat: add @DynamoDbIgnore annotation (#1402)
1 parent 54adfcb commit b08e24d

File tree

7 files changed

+156
-9
lines changed

7 files changed

+156
-9
lines changed

hll/dynamodb-mapper/dynamodb-mapper-annotations/api/dynamodb-mapper-annotations.api

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/Dyn
22
public abstract fun name ()Ljava/lang/String;
33
}
44

5+
public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbIgnore : java/lang/annotation/Annotation {
6+
}
7+
58
public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbItem : java/lang/annotation/Annotation {
9+
public abstract fun converterName ()Ljava/lang/String;
610
}
711

812
public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbPartitionKey : java/lang/annotation/Annotation {

hll/dynamodb-mapper/dynamodb-mapper-annotations/common/src/aws/sdk/kotlin/hll/dynamodbmapper/Annotations.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ package aws.sdk.kotlin.hll.dynamodbmapper
1212
public annotation class DynamoDbAttribute(val name: String)
1313

1414
/**
15-
* Specifies that this class/interface describes an item type in a table. All properties of this type will be mapped to
15+
* Specifies that this class/interface describes an item type in a table. All public properties of this type will be mapped to
1616
* attributes unless they are explicitly ignored.
17+
* @param converterName The fully qualified name of the item converter to be used for converting this class/interface.
18+
* If not set, one will be automatically generated.
1719
*/
20+
// FIXME Update to take a KClass<ItemConverter>, which will require splitting codegen modules due to a circular dependency
1821
@Target(AnnotationTarget.CLASS)
19-
public annotation class DynamoDbItem
22+
public annotation class DynamoDbItem(val converterName: String = "")
2023

2124
/**
2225
* Specifies that this property is the primary key for the item. Every top-level [DynamoDbItem] to be used in a table
@@ -31,3 +34,9 @@ public annotation class DynamoDbPartitionKey
3134
*/
3235
@Target(AnnotationTarget.PROPERTY)
3336
public annotation class DynamoDbSortKey
37+
38+
/**
39+
* Specifies that this property should be ignored during mapping.
40+
*/
41+
@Target(AnnotationTarget.PROPERTY)
42+
public annotation class DynamoDbIgnore

hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/rendering/SchemaRenderer.kt

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import aws.sdk.kotlin.hll.codegen.model.TypeRef
1010
import aws.sdk.kotlin.hll.codegen.model.Types
1111
import aws.sdk.kotlin.hll.codegen.rendering.*
1212
import aws.sdk.kotlin.hll.codegen.util.visibility
13-
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute
14-
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey
15-
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbSortKey
13+
import aws.sdk.kotlin.hll.dynamodbmapper.*
1614
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.AnnotationsProcessorOptions
1715
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.GenerateBuilderClasses
1816
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes
@@ -42,7 +40,23 @@ internal class SchemaRenderer(
4240
private val converterName = "${className}Converter"
4341
private val schemaName = "${className}Schema"
4442

45-
private val properties = classDeclaration.getAllProperties().filterNot { it.modifiers.contains(Modifier.PRIVATE) }
43+
@OptIn(KspExperimental::class)
44+
private val dynamoDbItemAnnotation = classDeclaration.getAnnotationsByType(DynamoDbItem::class).single()
45+
46+
private val itemConverter: Type = dynamoDbItemAnnotation
47+
.converterName
48+
.takeIf { it.isNotBlank() }
49+
?.let {
50+
val pkg = it.substringBeforeLast(".")
51+
val shortName = it.removePrefix("$pkg.")
52+
TypeRef(pkg, shortName)
53+
} ?: TypeRef(ctx.pkg, converterName)
54+
55+
@OptIn(KspExperimental::class)
56+
private val properties = classDeclaration
57+
.getAllProperties()
58+
.filterNot { it.modifiers.contains(Modifier.PRIVATE) || it.isAnnotationPresent(DynamoDbIgnore::class) }
59+
4660
private val annotatedProperties = properties.mapNotNull(AnnotatedClassProperty.Companion::from)
4761

4862
init {
@@ -58,7 +72,7 @@ internal class SchemaRenderer(
5872
private val sortKeyProp = annotatedProperties.singleOrNull { it.isSk }
5973

6074
/**
61-
* We skip rendering a class builder if:
75+
* Skip rendering a class builder if:
6276
* - the user has configured GenerateBuilders to WHEN_REQUIRED (default value) AND
6377
* - the class has all mutable members AND
6478
* - the class has a zero-arg constructor
@@ -75,8 +89,13 @@ internal class SchemaRenderer(
7589
if (shouldRenderBuilder) {
7690
renderBuilder()
7791
}
78-
renderItemConverter()
92+
93+
if (dynamoDbItemAnnotation.converterName.isBlank()) {
94+
renderItemConverter()
95+
}
96+
7997
renderSchema()
98+
8099
if (ctx.attributes[AnnotationsProcessorOptions.GenerateGetTableMethodAttribute]) {
81100
renderGetTable()
82101
}
@@ -140,7 +159,7 @@ internal class SchemaRenderer(
140159
}
141160

142161
withBlock("#Lobject #L : #T {", "}", ctx.attributes.visibility, schemaName, schemaType) {
143-
write("override val converter : #1L = #1L", converterName)
162+
write("override val converter : #1T = #1T", itemConverter)
144163
write("override val partitionKey: #T = #T(#S)", MapperTypes.Items.keySpec(partitionKeyProp.keySpec), partitionKeyProp.keySpecType, partitionKeyProp.name)
145164
if (sortKeyProp != null) {
146165
write("override val sortKey: #T = #T(#S)", MapperTypes.Items.keySpec(sortKeyProp.keySpec), sortKeyProp.keySpecType, sortKeyProp.name)

hll/dynamodb-mapper/dynamodb-mapper-schema-generator-plugin/src/test/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/plugins/SchemaGeneratorPluginTest.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,4 +357,51 @@ class SchemaGeneratorPluginTest {
357357
val testResult = runner.withArguments("test").build()
358358
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), testResult.task(":test")?.outcome)
359359
}
360+
361+
@Test
362+
fun testDynamoDbIgnore() {
363+
createClassFile("IgnoredProperty")
364+
365+
val result = runner.build()
366+
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), result.task(":build")?.outcome)
367+
368+
val schemaFile = File(testProjectDir, "build/generated/ksp/main/kotlin/org/example/dynamodbmapper/generatedschemas/IgnoredPropertySchema.kt")
369+
assertTrue(schemaFile.exists())
370+
371+
val schemaContents = schemaFile.readText()
372+
373+
assertContains(schemaContents, "public class IgnoredProperty")
374+
assertContains(schemaContents, "public var id: Int? = null")
375+
assertContains(schemaContents, "public var givenName: String? = null")
376+
assertContains(schemaContents, "public var surname: String? = null")
377+
assertContains(schemaContents, "public var age: Int? = null")
378+
assertContains(schemaContents, "public fun build(): IgnoredProperty")
379+
380+
// ssn is annotated with DynamoDbIgnore
381+
assertFalse(schemaContents.contains("public var ssn: String? = null"))
382+
}
383+
384+
@Test
385+
fun testDynamoDbItemConverter() {
386+
createClassFile("custom-item-converter/CustomUser")
387+
createClassFile("custom-item-converter/CustomItemConverter", "src/main/kotlin/my/custom/item/converter")
388+
389+
val result = runner.build()
390+
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), result.task(":build")?.outcome)
391+
392+
val schemaFile = File(testProjectDir, "build/generated/ksp/main/kotlin/org/example/dynamodbmapper/generatedschemas/CustomUserSchema.kt")
393+
assertTrue(schemaFile.exists())
394+
395+
val schemaContents = schemaFile.readText()
396+
assertFalse(schemaContents.contains("public object CustomUserItemConverter : ItemConverter<CustomUser> by SimpleItemConverter"))
397+
assertContains(
398+
schemaContents,
399+
"""
400+
public object CustomUserSchema : ItemSchema.PartitionKey<CustomUser, Int> {
401+
override val converter : MyCustomUserConverter = MyCustomUserConverter
402+
override val partitionKey: KeySpec<Number> = aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec.Number("id")
403+
}
404+
""".trimIndent(),
405+
)
406+
}
360407
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.example
2+
3+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute
4+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbIgnore
5+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem
6+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey
7+
8+
@DynamoDbItem
9+
public data class IgnoredProperty(
10+
@DynamoDbPartitionKey var id: Int,
11+
@DynamoDbAttribute("fName") var givenName: String,
12+
@DynamoDbAttribute("lName") var surname: String,
13+
var age: Int,
14+
15+
@DynamoDbIgnore
16+
var ssn: String? = null,
17+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package my.custom.item.converter
2+
3+
import aws.sdk.kotlin.hll.dynamodbmapper.items.AttributeDescriptor
4+
import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemConverter
5+
import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter
6+
import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter
7+
import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter
8+
import org.example.CustomUser
9+
10+
public object MyCustomUserConverter : ItemConverter<CustomUser> by SimpleItemConverter(
11+
builderFactory = { CustomUser() },
12+
build = { this },
13+
descriptors = arrayOf(
14+
AttributeDescriptor(
15+
"id",
16+
CustomUser::id,
17+
CustomUser::id::set,
18+
IntConverter,
19+
),
20+
AttributeDescriptor(
21+
"myCustomFirstName",
22+
CustomUser::givenName,
23+
CustomUser::givenName::set,
24+
StringConverter,
25+
),
26+
AttributeDescriptor(
27+
"myCustomLastName",
28+
CustomUser::surname,
29+
CustomUser::surname::set,
30+
StringConverter,
31+
),
32+
AttributeDescriptor(
33+
"myCustomAge",
34+
CustomUser::age,
35+
CustomUser::age::set,
36+
IntConverter,
37+
),
38+
),
39+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.example
2+
3+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem
4+
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey
5+
6+
@DynamoDbItem("my.custom.item.converter.MyCustomUserConverter")
7+
public data class CustomUser(
8+
@DynamoDbPartitionKey var id: Int = 1,
9+
var givenName: String = "Johnny",
10+
var surname: String = "Appleseed",
11+
var age: Int = 0,
12+
)

0 commit comments

Comments
 (0)