-
Notifications
You must be signed in to change notification settings - Fork 55
feat: basic annotation processing #1399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
d107a6c
2c3b746
84cb888
b7b6c5b
c4a13ed
fb145a8
1d0a599
bf459be
c8830bc
3fff3df
1a2966b
524858c
22a6ffe
ed889da
e94033e
d627e30
9707eb8
4edb573
d7547d3
b876ada
00e93a5
6bc65fa
0cbd2a8
4e4e473
d232651
a4fe6bc
e5cd349
9b2d135
d6fb875
3cc951a
a2bcaec
9b9de55
8abda74
6c49b8f
8ebc112
b503c15
dc30dfb
9a58f5c
29d77c9
6ce008d
ae26243
633268a
5cad5bb
74a6157
731a679
80b8107
43dc70c
f7bdf42
bf216d6
537463d
b49ff7e
9f55f5a
329ac7b
8cc75cc
8b19546
57c5f13
a43e506
8b989b9
7481db6
f287fd5
ee115d5
b0e6b2b
976e9eb
57b7fea
39651f5
c9cdf85
fc763db
1974963
b075c92
4afaf33
fe84aa6
7f667b8
232b858
7144d0c
295aa45
f45548d
eaab8cd
10044f6
dff75e4
87f8d03
d1ff053
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations.rendering | ||
|
|
||
| import aws.smithy.kotlin.runtime.collections.AttributeKey | ||
|
|
||
| /** | ||
| * Internal schema code generation attributes | ||
| */ | ||
| internal object SchemaAttributes { | ||
| /** | ||
| * Whether a value converter should be generated for the class being processed | ||
| */ | ||
| internal val ShouldRenderValueConverterAttribute: AttributeKey<Boolean> = AttributeKey("ShouldRenderValueConverter") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,6 +89,10 @@ internal class SchemaRenderer( | |
| renderItemConverter() | ||
| } | ||
|
|
||
| if (ctx.attributes[SchemaAttributes.ShouldRenderValueConverterAttribute]) { | ||
| renderValueConverter() | ||
| } | ||
|
|
||
| renderSchema() | ||
|
|
||
| if (ctx.attributes[AnnotationsProcessorOptions.GenerateGetTableMethodAttribute]) { | ||
|
|
@@ -118,33 +122,24 @@ internal class SchemaRenderer( | |
| } | ||
| } | ||
| blankLine() | ||
| } | ||
|
|
||
| // If necessary, render a ValueConverter which is a wrapper over the ItemConverter | ||
| /** | ||
| * Render a [ValueConverter] for the current class by wrapping the generated/user-provided [ItemConverter] | ||
| * with our [ItemToValueConverter] | ||
| */ | ||
| private fun renderValueConverter() { | ||
| // TODO Offer alternate serialization options besides AttributeValue.M? | ||
| if (ctx.attributes[ShouldRenderValueConverterAttribute]) { | ||
| withBlock( | ||
| "#Lobject #L : #T {", | ||
| "}", | ||
| ctx.attributes.visibility, | ||
| "${className}ValueConverter", | ||
| MapperTypes.Values.valueConverter(classType), | ||
| ) { | ||
| write( | ||
| "override fun convertFrom(to: #T): #T = #T.fromItem(to.asM().#T())", | ||
| MapperTypes.AttributeValue, | ||
| classType, | ||
| itemConverter, | ||
| MapperTypes.Model.toItem, | ||
| ) | ||
| write( | ||
| "override fun convertTo(from: #1T): #2T = #2T.M(#3T.toItem(from))", | ||
| classType, | ||
| MapperTypes.AttributeValue, | ||
| itemConverter, | ||
| ) | ||
| } | ||
| blankLine() | ||
| } | ||
| write( | ||
| "#Lval #L : #T = #T.#T(#T)", | ||
| ctx.attributes.visibility, | ||
| "${className}ValueConverter", | ||
| MapperTypes.Values.valueConverter(classType), | ||
| itemConverter, | ||
| TypeRef("aws.sdk.kotlin.hll.mapping.core.converters", "andThenTo"), | ||
| MapperTypes.Values.ItemToValueConverter, | ||
| ) | ||
| blankLine() | ||
| } | ||
|
|
||
| private fun renderAttributeDescriptor(prop: KSPropertyDeclaration) { | ||
|
|
@@ -160,108 +155,121 @@ internal class SchemaRenderer( | |
| } | ||
|
|
||
| // converter | ||
| write("#T", prop.type.resolve().valueConverter) | ||
| prop.type.resolve().renderValueConverter(this) | ||
| write("") | ||
| } | ||
| } | ||
|
|
||
| private val KSType.valueConverter: Type | ||
| get() = when { | ||
| this.isEnum -> MapperTypes.Values.Scalars.enumConverter(Type.from(this)) | ||
|
|
||
| // Assuming other classes have also been annotated with DynamoDbItem, and they are codegenerated in the same package | ||
| this.isUserClass -> TypeRef(ctx.pkg, "${this.declaration.simpleName.asString()}ValueConverter") | ||
|
|
||
| else -> when (this.declaration.qualifiedName?.asString()) { | ||
| "aws.smithy.kotlin.runtime.time.Instant" -> MapperTypes.Values.SmithyTypes.DefaultInstantConverter | ||
| "aws.smithy.kotlin.runtime.net.url.Url" -> MapperTypes.Values.SmithyTypes.UrlConverter | ||
| "aws.smithy.kotlin.runtime.content.Document" -> MapperTypes.Values.SmithyTypes.DefaultDocumentConverter | ||
|
|
||
| "kotlin.Boolean" -> MapperTypes.Values.Scalars.BooleanConverter | ||
| "kotlin.String" -> MapperTypes.Values.Scalars.StringConverter | ||
| "kotlin.CharArray" -> MapperTypes.Values.Scalars.CharArrayConverter | ||
| "kotlin.Char" -> MapperTypes.Values.Scalars.CharConverter | ||
| "kotlin.Byte" -> MapperTypes.Values.Scalars.ByteConverter | ||
| "kotlin.ByteArray" -> MapperTypes.Values.Scalars.ByteArrayConverter | ||
| "kotlin.Short" -> MapperTypes.Values.Scalars.ShortConverter | ||
| "kotlin.Int" -> MapperTypes.Values.Scalars.IntConverter | ||
| "kotlin.Long" -> MapperTypes.Values.Scalars.LongConverter | ||
| "kotlin.Double" -> MapperTypes.Values.Scalars.DoubleConverter | ||
| "kotlin.Float" -> MapperTypes.Values.Scalars.FloatConverter | ||
| "kotlin.UByte" -> MapperTypes.Values.Scalars.UByteConverter | ||
| "kotlin.UInt" -> MapperTypes.Values.Scalars.UIntConverter | ||
| "kotlin.UShort" -> MapperTypes.Values.Scalars.UShortConverter | ||
| "kotlin.ULong" -> MapperTypes.Values.Scalars.ULongConverter | ||
|
|
||
| "kotlin.collections.Set" -> this.singleArgument().setValueConverter | ||
|
|
||
| "kotlin.collections.List" -> { | ||
| val listElementConverter = this.singleArgument().valueConverter | ||
| MapperTypes.Values.Collections.listConverter(listElementConverter) | ||
| /** | ||
| * Renders a ValueConverter for the [KSType]. | ||
| * | ||
| * Note: The ValueConverter(s) will be rendered without a newline in order to support deep recursion. | ||
| * Callers are responsible for adding a newline after the top-level invocation of this function. | ||
| */ | ||
| private fun KSType.renderValueConverter(writer: SchemaRenderer) { | ||
| writer.apply { | ||
| val ksType = this@renderValueConverter | ||
| val type = Type.from(ksType).copy(genericArgs = listOf()) | ||
|
||
|
|
||
| when { | ||
|
||
| isEnum -> writeInline("#T()", MapperTypes.Values.Scalars.enumConverter(type)) | ||
|
|
||
| // FIXME Handle multi-module codegen rather than assuming nested classes will be in the same [ctx.pkg] | ||
| isUserClass -> writeInline("#T", TypeRef(ctx.pkg, "${declaration.simpleName.asString()}ValueConverter")) | ||
|
|
||
| type == Types.Kotlin.Collections.List -> { | ||
| val listElementType = singleArgument() | ||
| writeInline("#T(", MapperTypes.Values.Collections.ListConverter) | ||
| listElementType.renderValueConverter(this) | ||
| writeInline(")") | ||
| } | ||
|
|
||
| "kotlin.collections.Map" -> { | ||
| type == Types.Kotlin.Collections.Map -> { | ||
| check(arguments.size == 2) { "Expected map type ${declaration.qualifiedName?.asString()} to have 2 arguments, got ${arguments.size}" } | ||
|
|
||
| val (keyType, valueType) = arguments | ||
| .map { | ||
| checkNotNull(it.type?.resolve()) { | ||
| "Failed to resolved argument type for $it" | ||
| } | ||
| } | ||
|
|
||
| val keyConverter = keyType.mapKeyConverter | ||
| val valueConverter = valueType.valueConverter | ||
| val (keyType, valueType) = arguments.map { | ||
| checkNotNull(it.type?.resolve()) { "Failed to resolved argument type for $it" } | ||
| } | ||
|
|
||
| MapperTypes.Values.Collections.mapConverter(keyConverter, valueConverter) | ||
| writeInline("#T(#T, ", MapperTypes.Values.Collections.MapConverter, keyType.mapKeyConverter) | ||
| valueType.renderValueConverter(this) | ||
| writeInline(")") | ||
| } | ||
|
|
||
| else -> error("Unsupported attribute type $this") | ||
| else -> writeInline( | ||
| "#T", | ||
| when (type) { | ||
| Types.Smithy.Instant -> MapperTypes.Values.SmithyTypes.DefaultInstantConverter | ||
| Types.Smithy.Url -> MapperTypes.Values.SmithyTypes.UrlConverter | ||
| Types.Smithy.Document -> MapperTypes.Values.SmithyTypes.DefaultDocumentConverter | ||
|
|
||
| Types.Kotlin.Boolean -> MapperTypes.Values.Scalars.BooleanConverter | ||
| Types.Kotlin.String -> MapperTypes.Values.Scalars.StringConverter | ||
| Types.Kotlin.CharArray -> MapperTypes.Values.Scalars.CharArrayConverter | ||
| Types.Kotlin.Char -> MapperTypes.Values.Scalars.CharConverter | ||
| Types.Kotlin.Byte -> MapperTypes.Values.Scalars.ByteConverter | ||
| Types.Kotlin.ByteArray -> MapperTypes.Values.Scalars.ByteArrayConverter | ||
| Types.Kotlin.Short -> MapperTypes.Values.Scalars.ShortConverter | ||
| Types.Kotlin.Int -> MapperTypes.Values.Scalars.IntConverter | ||
| Types.Kotlin.Long -> MapperTypes.Values.Scalars.LongConverter | ||
| Types.Kotlin.Double -> MapperTypes.Values.Scalars.DoubleConverter | ||
| Types.Kotlin.Float -> MapperTypes.Values.Scalars.FloatConverter | ||
| Types.Kotlin.UByte -> MapperTypes.Values.Scalars.UByteConverter | ||
| Types.Kotlin.UInt -> MapperTypes.Values.Scalars.UIntConverter | ||
| Types.Kotlin.UShort -> MapperTypes.Values.Scalars.UShortConverter | ||
| Types.Kotlin.ULong -> MapperTypes.Values.Scalars.ULongConverter | ||
|
|
||
| Types.Kotlin.Collections.Set -> singleArgument().setValueConverter | ||
| else -> error("Unsupported attribute type $this") | ||
| }, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private val KSType.mapKeyConverter: Type | ||
| get() = when (val name = this.declaration.qualifiedName?.asString()) { | ||
| get() = when (val type = Type.from(this)) { | ||
| // String | ||
| "kotlin.CharArray" -> MapperTypes.Values.Scalars.CharArrayToStringConverter | ||
| "kotlin.Char" -> MapperTypes.Values.Scalars.CharToStringConverter | ||
| "kotlin.String" -> MapperTypes.Values.Scalars.StringToStringConverter | ||
| Types.Kotlin.ByteArray -> MapperTypes.Values.Scalars.CharArrayToStringConverter | ||
| Types.Kotlin.Char -> MapperTypes.Values.Scalars.CharToStringConverter | ||
| Types.Kotlin.String -> MapperTypes.Values.Scalars.StringToStringConverter | ||
|
|
||
| // Number | ||
| "kotlin.Byte" -> MapperTypes.Values.Scalars.ByteToStringConverter | ||
| "kotlin.Double" -> MapperTypes.Values.Scalars.DoubleToStringConverter | ||
| "kotlin.Float" -> MapperTypes.Values.Scalars.FloatToStringConverter | ||
| "kotlin.Int" -> MapperTypes.Values.Scalars.IntToStringConverter | ||
| "kotlin.Long" -> MapperTypes.Values.Scalars.LongToStringConverter | ||
| "kotlin.Short" -> MapperTypes.Values.Scalars.ShortToStringConverter | ||
| "kotlin.UByte" -> MapperTypes.Values.Scalars.UByteToStringConverter | ||
| "kotlin.UInt" -> MapperTypes.Values.Scalars.UIntToStringConverter | ||
| "kotlin.ULong" -> MapperTypes.Values.Scalars.ULongToStringConverter | ||
| "kotlin.UShort" -> MapperTypes.Values.Scalars.UShortToStringConverter | ||
| Types.Kotlin.Byte -> MapperTypes.Values.Scalars.ByteToStringConverter | ||
| Types.Kotlin.Double -> MapperTypes.Values.Scalars.DoubleToStringConverter | ||
| Types.Kotlin.Float -> MapperTypes.Values.Scalars.FloatToStringConverter | ||
| Types.Kotlin.Int -> MapperTypes.Values.Scalars.IntToStringConverter | ||
| Types.Kotlin.Long -> MapperTypes.Values.Scalars.LongToStringConverter | ||
| Types.Kotlin.Short -> MapperTypes.Values.Scalars.ShortToStringConverter | ||
| Types.Kotlin.UByte -> MapperTypes.Values.Scalars.UByteToStringConverter | ||
| Types.Kotlin.UInt -> MapperTypes.Values.Scalars.UIntToStringConverter | ||
| Types.Kotlin.ULong -> MapperTypes.Values.Scalars.ULongToStringConverter | ||
| Types.Kotlin.UShort -> MapperTypes.Values.Scalars.UShortToStringConverter | ||
|
|
||
| // Boolean | ||
| "kotlin.Boolean" -> MapperTypes.Values.Scalars.BooleanToStringConverter | ||
| else -> error("Unsupported key type: $name") | ||
| Types.Kotlin.Boolean -> MapperTypes.Values.Scalars.BooleanToStringConverter | ||
| else -> error("Unsupported key type: $type") | ||
| } | ||
|
|
||
| private fun KSType.singleArgument(): KSType = checkNotNull(arguments.single().type?.resolve()) { | ||
| "Failed to resolve single argument type for ${this.declaration.qualifiedName?.asString()}" | ||
| } | ||
|
|
||
| private val KSType.setValueConverter: Type | ||
| get() = when (this.declaration.qualifiedName?.asString()) { | ||
| "kotlin.String" -> MapperTypes.Values.Collections.StringSetConverter | ||
| "kotlin.Char" -> MapperTypes.Values.Collections.CharSetConverter | ||
| "kotlin.CharArray" -> MapperTypes.Values.Collections.CharArraySetConverter | ||
| "kotlin.Byte" -> MapperTypes.Values.Collections.ByteSetConverter | ||
| "kotlin.Double" -> MapperTypes.Values.Collections.DoubleSetConverter | ||
| "kotlin.Float" -> MapperTypes.Values.Collections.FloatSetConverter | ||
| "kotlin.Int" -> MapperTypes.Values.Collections.IntSetConverter | ||
| "kotlin.Long" -> MapperTypes.Values.Collections.LongSetConverter | ||
| "kotlin.Short" -> MapperTypes.Values.Collections.ShortSetConverter | ||
| "kotlin.UByte" -> MapperTypes.Values.Collections.UByteSetConverter | ||
| "kotlin.UInt" -> MapperTypes.Values.Collections.UIntSetConverter | ||
| "kotlin.ULong" -> MapperTypes.Values.Collections.ULongSetConverter | ||
| "kotlin.UShort" -> MapperTypes.Values.Collections.UShortSetConverter | ||
| get() = when (Type.from(this)) { | ||
| Types.Kotlin.String -> MapperTypes.Values.Collections.StringSetConverter | ||
| Types.Kotlin.Char -> MapperTypes.Values.Collections.CharSetConverter | ||
| Types.Kotlin.CharArray -> MapperTypes.Values.Collections.CharArraySetConverter | ||
| Types.Kotlin.Byte -> MapperTypes.Values.Collections.ByteSetConverter | ||
| Types.Kotlin.Double -> MapperTypes.Values.Collections.DoubleSetConverter | ||
| Types.Kotlin.Float -> MapperTypes.Values.Collections.FloatSetConverter | ||
| Types.Kotlin.Int -> MapperTypes.Values.Collections.IntSetConverter | ||
| Types.Kotlin.Long -> MapperTypes.Values.Collections.LongSetConverter | ||
| Types.Kotlin.Short -> MapperTypes.Values.Collections.ShortSetConverter | ||
| Types.Kotlin.UByte -> MapperTypes.Values.Collections.UByteSetConverter | ||
| Types.Kotlin.UInt -> MapperTypes.Values.Collections.UIntSetConverter | ||
| Types.Kotlin.ULong -> MapperTypes.Values.Collections.ULongSetConverter | ||
| Types.Kotlin.UShort -> MapperTypes.Values.Collections.UShortSetConverter | ||
| else -> error("Unsupported set element $this") | ||
| } | ||
|
|
||
|
|
@@ -316,11 +324,12 @@ internal class SchemaRenderer( | |
| schemaName, | ||
| ) | ||
| } | ||
|
|
||
| private val KSType.isUserClass: Boolean | ||
| get() = listOf("kotlin", "java", "aws.smithy.kotlin", "aws.sdk.kotlin").none { declaration.packageName.asString().startsWith(it) } | ||
| } | ||
|
|
||
| @OptIn(KspExperimental::class) | ||
| private val KSType.isUserClass: Boolean | ||
| get() = declaration.isAnnotationPresent(DynamoDbItem::class) | ||
|
|
||
| private val KSPropertyDeclaration.typeName: String | ||
| get() = checkNotNull(getter?.returnType?.resolve()?.declaration?.qualifiedName?.asString()) { "Failed to determine type name for $this" } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Parentheses around
propType.arguments.anycall are unnecessary