Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/Dyn
public abstract fun name ()Ljava/lang/String;
}

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

public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbItem : java/lang/annotation/Annotation {
public abstract fun converterName ()Ljava/lang/String;
}

public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbPartitionKey : java/lang/annotation/Annotation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ package aws.sdk.kotlin.hll.dynamodbmapper
public annotation class DynamoDbAttribute(val name: String)

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

/**
* Specifies that this property is the primary key for the item. Every top-level [DynamoDbItem] to be used in a table
Expand All @@ -31,3 +34,9 @@ public annotation class DynamoDbPartitionKey
*/
@Target(AnnotationTarget.PROPERTY)
public annotation class DynamoDbSortKey

/**
* Specifies that this property should be ignored during mapping.
*/
@Target(AnnotationTarget.PROPERTY)
public annotation class DynamoDbIgnore
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import aws.sdk.kotlin.hll.codegen.model.TypeRef
import aws.sdk.kotlin.hll.codegen.model.Types
import aws.sdk.kotlin.hll.codegen.rendering.*
import aws.sdk.kotlin.hll.codegen.util.visibility
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbSortKey
import aws.sdk.kotlin.hll.dynamodbmapper.*
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.AnnotationsProcessorOptions
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.GenerateBuilderClasses
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes
Expand Down Expand Up @@ -42,7 +40,23 @@ internal class SchemaRenderer(
private val converterName = "${className}Converter"
private val schemaName = "${className}Schema"

private val properties = classDeclaration.getAllProperties().filterNot { it.modifiers.contains(Modifier.PRIVATE) }
@OptIn(KspExperimental::class)
private val dynamoDbItemAnnotation = classDeclaration.getAnnotationsByType(DynamoDbItem::class).single()

private val itemConverter: Type = dynamoDbItemAnnotation
.converterName
.takeIf { it.isNotBlank() }
?.let {
val pkg = it.substringBeforeLast(".")
val shortName = it.removePrefix("$pkg.")
TypeRef(pkg, shortName)
} ?: TypeRef(ctx.pkg, converterName)

@OptIn(KspExperimental::class)
private val properties = classDeclaration
.getAllProperties()
.filterNot { it.modifiers.contains(Modifier.PRIVATE) || it.isAnnotationPresent(DynamoDbIgnore::class) }

private val annotatedProperties = properties.mapNotNull(AnnotatedClassProperty.Companion::from)

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

/**
* We skip rendering a class builder if:
* Skip rendering a class builder if:
* - the user has configured GenerateBuilders to WHEN_REQUIRED (default value) AND
* - the class has all mutable members AND
* - the class has a zero-arg constructor
Expand All @@ -75,8 +89,13 @@ internal class SchemaRenderer(
if (shouldRenderBuilder) {
renderBuilder()
}
renderItemConverter()

if (dynamoDbItemAnnotation.converterName.isBlank()) {
renderItemConverter()
}

renderSchema()

if (ctx.attributes[AnnotationsProcessorOptions.GenerateGetTableMethodAttribute]) {
renderGetTable()
}
Expand Down Expand Up @@ -140,7 +159,7 @@ internal class SchemaRenderer(
}

withBlock("#Lobject #L : #T {", "}", ctx.attributes.visibility, schemaName, schemaType) {
write("override val converter : #1L = #1L", converterName)
write("override val converter : #1T = #1T", itemConverter)
write("override val partitionKey: #T = #T(#S)", MapperTypes.Items.keySpec(partitionKeyProp.keySpec), partitionKeyProp.keySpecType, partitionKeyProp.name)
if (sortKeyProp != null) {
write("override val sortKey: #T = #T(#S)", MapperTypes.Items.keySpec(sortKeyProp.keySpec), sortKeyProp.keySpecType, sortKeyProp.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,51 @@ class SchemaGeneratorPluginTest {
val testResult = runner.withArguments("test").build()
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), testResult.task(":test")?.outcome)
}

@Test
fun testDynamoDbIgnore() {
createClassFile("IgnoredProperty")

val result = runner.build()
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), result.task(":build")?.outcome)

val schemaFile = File(testProjectDir, "build/generated/ksp/main/kotlin/org/example/dynamodbmapper/generatedschemas/IgnoredPropertySchema.kt")
assertTrue(schemaFile.exists())

val schemaContents = schemaFile.readText()

assertContains(schemaContents, "public class IgnoredProperty")
assertContains(schemaContents, "public var id: Int? = null")
assertContains(schemaContents, "public var givenName: String? = null")
assertContains(schemaContents, "public var surname: String? = null")
assertContains(schemaContents, "public var age: Int? = null")
assertContains(schemaContents, "public fun build(): IgnoredProperty")

// ssn is annotated with DynamoDbIgnore
assertFalse(schemaContents.contains("public var ssn: String? = null"))
}

@Test
fun testDynamoDbItemConverter() {
createClassFile("custom-item-converter/CustomUser")
createClassFile("custom-item-converter/CustomItemConverter", "src/main/kotlin/my/custom/item/converter")

val result = runner.build()
assertContains(setOf(TaskOutcome.SUCCESS, TaskOutcome.UP_TO_DATE), result.task(":build")?.outcome)

val schemaFile = File(testProjectDir, "build/generated/ksp/main/kotlin/org/example/dynamodbmapper/generatedschemas/CustomUserSchema.kt")
assertTrue(schemaFile.exists())

val schemaContents = schemaFile.readText()
assertFalse(schemaContents.contains("public object CustomUserItemConverter : ItemConverter<CustomUser> by SimpleItemConverter"))
assertContains(
schemaContents,
"""
public object CustomUserSchema : ItemSchema.PartitionKey<CustomUser, Int> {
override val converter : MyCustomUserConverter = MyCustomUserConverter
override val partitionKey: KeySpec<Number> = aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec.Number("id")
}
""".trimIndent(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.example

import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbAttribute
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbIgnore
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey

@DynamoDbItem
public data class IgnoredProperty(
@DynamoDbPartitionKey var id: Int,
@DynamoDbAttribute("fName") var givenName: String,
@DynamoDbAttribute("lName") var surname: String,
var age: Int,

@DynamoDbIgnore
var ssn: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package my.custom.item.converter

import aws.sdk.kotlin.hll.dynamodbmapper.items.AttributeDescriptor
import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemConverter
import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter
import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter
import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter
import org.example.CustomUser

public object MyCustomUserConverter : ItemConverter<CustomUser> by SimpleItemConverter(
builderFactory = { CustomUser() },
build = { this },
descriptors = arrayOf(
AttributeDescriptor(
"id",
CustomUser::id,
CustomUser::id::set,
IntConverter,
),
AttributeDescriptor(
"myCustomFirstName",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: naming here is differs from the data class property i.e. givenName and firstName

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's intentional to show what a custom item converter might do

CustomUser::givenName,
CustomUser::givenName::set,
StringConverter,
),
AttributeDescriptor(
"myCustomLastName",
CustomUser::surname,
CustomUser::surname::set,
StringConverter,
),
AttributeDescriptor(
"myCustomAge",
CustomUser::age,
CustomUser::age::set,
IntConverter,
),
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.example

import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem
import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbPartitionKey

@DynamoDbItem("my.custom.item.converter.MyCustomUserConverter")
public data class CustomUser(
@DynamoDbPartitionKey var id: Int = 1,
var givenName: String = "Johnny",
var surname: String = "Appleseed",
var age: Int = 0,
)