From 21f1bd505c744c884feb15b1785a5ed9ccdea572 Mon Sep 17 00:00:00 2001 From: Ian Botsford <83236726+ianbotsf@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:12:05 -0700 Subject: [PATCH] add DSL overloads, paginators, and better builder integration for DDB Mapper ops codegen (#1409) --- hll/build.gradle.kts | 20 +- .../dynamodb-mapper-codegen/build.gradle.kts | 10 + .../annotations/AnnotationsProcessor.kt | 11 +- .../annotations/rendering/SchemaRenderer.kt | 2 +- .../dynamodbmapper/codegen/model/MapperPkg.kt | 30 +++ .../codegen/model/MapperTypes.kt | 162 +++++++------ .../operations/HighLevelOpsProcessor.kt | 40 ++-- .../operations/model/ItemSourceKind.kt | 5 +- .../operations/model/MemberCodegenBehavior.kt | 30 ++- .../operations/model/ModelAttributes.kt | 8 + .../codegen/operations/model/Operation.kt | 37 +-- .../codegen/operations/model/Pagination.kt | 53 +++++ .../codegen/operations/model/Structure.kt | 47 +--- .../operations/rendering/DataTypeGenerator.kt | 132 ++++++----- .../operations/rendering/HighLevelRenderer.kt | 2 +- .../operations/rendering/OperationRenderer.kt | 30 ++- .../rendering/OperationsTypeRenderer.kt | 92 ++++++-- .../operations/rendering/PaginatorRenderer.kt | 171 ++++++++++++++ ...otlin.hll.codegen.model.ModelParsingPlugin | 1 + .../dynamodb-mapper/api/dynamodb-mapper.api | 216 +++++++++++++++++- .../annotations/ManualPagination.kt | 12 + .../operations/DeleteItemTest.kt | 14 +- .../dynamodbmapper/operations/GetItemTest.kt | 11 +- .../dynamodbmapper/operations/PutItemTest.kt | 10 +- .../dynamodbmapper/operations/QueryTest.kt | 164 ++++++------- hll/hll-codegen/build.gradle.kts | 15 ++ .../kotlin/hll/codegen/core/CodeGenerator.kt | 35 +-- .../hll/codegen/core/CodeGeneratorFactory.kt | 6 +- .../hll/codegen/core/ImportDirectives.kt | 19 +- .../kotlin/hll/codegen/core/TemplateEngine.kt | 7 +- .../hll/codegen/core/TemplateProcessor.kt | 22 +- .../codegen/ksp/processors/HllKspProcessor.kt | 46 ++++ .../sdk/kotlin/hll/codegen/model/Member.kt | 21 +- .../hll/codegen/model/ModelParsingPlugin.kt | 71 ++++++ .../sdk/kotlin/hll/codegen/model/Operation.kt | 49 ++++ .../aws/sdk/kotlin/hll/codegen/model/Pkg.kt | 31 +++ .../sdk/kotlin/hll/codegen/model/Structure.kt | 53 +++++ .../aws/sdk/kotlin/hll/codegen/model/Type.kt | 57 ++++- .../aws/sdk/kotlin/hll/codegen/model/Types.kt | 94 +++++--- .../hll/codegen/rendering/BuilderRenderer.kt | 56 +++-- .../hll/codegen/rendering/RenderContext.kt | 14 +- .../hll/codegen/rendering/RenderOptions.kt | 1 - .../hll/codegen/rendering/RendererBase.kt | 6 +- .../kotlin/hll/codegen/util/AttributesExt.kt | 8 +- .../kotlin/hll/codegen/util/IterableExt.kt | 26 +++ .../hll/codegen/util/KspLoggerOutputStream.kt | 44 ++++ .../aws/sdk/kotlin/hll/codegen/util/Pkg.kt | 32 --- .../sdk/kotlin/hll/codegen/util/Strings.kt | 14 +- 48 files changed, 1464 insertions(+), 573 deletions(-) create mode 100644 hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperPkg.kt create mode 100644 hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Pagination.kt create mode 100644 hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/PaginatorRenderer.kt create mode 100644 hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/resources/META-INF/services/aws.sdk.kotlin.hll.codegen.model.ModelParsingPlugin create mode 100644 hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/annotations/ManualPagination.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/ksp/processors/HllKspProcessor.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/ModelParsingPlugin.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Operation.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Pkg.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Structure.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/IterableExt.kt create mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/KspLoggerOutputStream.kt delete mode 100644 hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Pkg.kt diff --git a/hll/build.gradle.kts b/hll/build.gradle.kts index ea37ba41e88..a2e016b1afa 100644 --- a/hll/build.gradle.kts +++ b/hll/build.gradle.kts @@ -25,8 +25,16 @@ val sdkVersion: String by project // capture locally - scope issue with custom KMP plugin val libraries = libs +val optinAnnotations = listOf( + "aws.smithy.kotlin.runtime.InternalApi", + "aws.sdk.kotlin.runtime.InternalSdkApi", + "kotlin.RequiresOptIn", +) + subprojects { - if (!needsKmpConfigured) return@subprojects + if (!needsKmpConfigured) { + return@subprojects + } group = "aws.sdk.kotlin" version = sdkVersion @@ -45,6 +53,10 @@ subprojects { sourceSets { // dependencies available for all subprojects + all { + optinAnnotations.forEach(languageSettings::optIn) + } + named("commonTest") { dependencies { implementation(libraries.kotest.assertions.core) @@ -60,12 +72,6 @@ subprojects { } } - kotlin.sourceSets.all { - // Allow subprojects to use internal APIs - // See https://kotlinlang.org/docs/reference/opt-in-requirements.html#opting-in-to-using-api - listOf("kotlin.RequiresOptIn").forEach { languageSettings.optIn(it) } - } - dependencies { dokkaPlugin(project(":dokka-aws")) } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/build.gradle.kts b/hll/dynamodb-mapper/dynamodb-mapper-codegen/build.gradle.kts index 053d6782c72..efb5056ea3c 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/build.gradle.kts +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/build.gradle.kts @@ -24,8 +24,18 @@ dependencies { testImplementation(libs.kotlin.test.junit5) } +val optinAnnotations = listOf( + "aws.smithy.kotlin.runtime.InternalApi", + "aws.sdk.kotlin.runtime.InternalSdkApi", + "kotlin.RequiresOptIn", +) + kotlin { explicitApi() + + sourceSets.all { + optinAnnotations.forEach(languageSettings::optIn) + } } tasks.test { diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/AnnotationsProcessor.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/AnnotationsProcessor.kt index d2c6a827264..ce81c7740b4 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/AnnotationsProcessor.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/AnnotationsProcessor.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.annotations import aws.sdk.kotlin.hll.codegen.core.CodeGeneratorFactory +import aws.sdk.kotlin.hll.codegen.ksp.processors.HllKspProcessor import aws.sdk.kotlin.hll.codegen.rendering.RenderOptions.VisibilityAttribute import aws.sdk.kotlin.hll.codegen.rendering.Visibility import aws.sdk.kotlin.hll.dynamodbmapper.DynamoDbItem @@ -19,17 +20,11 @@ import com.google.devtools.ksp.validate private val annotationName = DynamoDbItem::class.qualifiedName!! -public class AnnotationsProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor { - private var invoked = false +public class AnnotationsProcessor(private val environment: SymbolProcessorEnvironment) : HllKspProcessor(environment) { private val logger = environment.logger private val codeGenerator = environment.codeGenerator - override fun process(resolver: Resolver): List { - if (invoked) { - return listOf() - } - invoked = true - + override fun processImpl(resolver: Resolver): List { logger.info("Searching for symbols annotated with $annotationName") val annotated = resolver.getSymbolsWithAnnotation(annotationName) val invalid = annotated.filterNot { it.validate() }.toList() diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/rendering/SchemaRenderer.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/rendering/SchemaRenderer.kt index f0dee7310a8..d3a2a49d9c3 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/rendering/SchemaRenderer.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/annotations/rendering/SchemaRenderer.kt @@ -99,7 +99,7 @@ internal class SchemaRenderer( private fun renderBuilder() { val members = properties.map(Member.Companion::from).toSet() - BuilderRenderer(this, classType, members, ctx).render() + BuilderRenderer(this, classType, classType, members, ctx).render() } private fun renderItemConverter() { diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperPkg.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperPkg.kt new file mode 100644 index 00000000000..06061951f3b --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperPkg.kt @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.codegen.model + +import aws.sdk.kotlin.runtime.InternalSdkApi + +@InternalSdkApi +public object MapperPkg { + @InternalSdkApi + public object Hl { + public val Base: String = "aws.sdk.kotlin.hll.dynamodbmapper" + public val Annotations: String = "$Base.annotations" + public val Items: String = "$Base.items" + public val Model: String = "$Base.model" + public val Ops: String = "$Base.operations" + public val PipelineImpl: String = "$Base.pipeline.internal" + public val Values: String = "$Base.values" + public val CollectionValues: String = "$Values.collections" + public val ScalarValues: String = "$Values.scalars" + public val SmithyTypeValues: String = "$Values.smithytypes" + } + + @InternalSdkApi + public object Ll { + public val Base: String = "aws.sdk.kotlin.services.dynamodb" + public val Model: String = "$Base.model" + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperTypes.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperTypes.kt index 61ed0b6cb43..0b137560172 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperTypes.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperTypes.kt @@ -4,117 +4,129 @@ import aws.sdk.kotlin.hll.codegen.model.Type import aws.sdk.kotlin.hll.codegen.model.TypeRef import aws.sdk.kotlin.hll.codegen.model.TypeVar import aws.sdk.kotlin.hll.codegen.model.Types -import aws.sdk.kotlin.hll.codegen.util.Pkg /** * A container object for various DynamoDbMapper [Type] instances */ internal object MapperTypes { // Low-level types - val AttributeValue = TypeRef(Pkg.Ll.Model, "AttributeValue") + val AttributeValue = TypeRef(MapperPkg.Ll.Model, "AttributeValue") val AttributeMap = Types.Kotlin.map(Types.Kotlin.String, AttributeValue) // High-level types - val DynamoDbMapper = TypeRef(Pkg.Hl.Base, "DynamoDbMapper") + val DynamoDbMapper = TypeRef(MapperPkg.Hl.Base, "DynamoDbMapper") + + object Annotations { + val ManualPagination = TypeRef(MapperPkg.Hl.Annotations, "ManualPagination") + } object Items { - fun itemSchema(typeVar: String) = TypeRef(Pkg.Hl.Items, "ItemSchema", listOf(TypeVar(typeVar))) - fun itemSchemaPartitionKey(objectType: TypeRef, pkType: TypeRef) = TypeRef(Pkg.Hl.Items, "ItemSchema.PartitionKey", listOf(objectType, pkType)) - fun itemSchemaCompositeKey(objectType: TypeRef, pkType: TypeRef, skType: TypeRef) = TypeRef(Pkg.Hl.Items, "ItemSchema.CompositeKey", listOf(objectType, pkType, skType)) - fun keySpec(keyType: TypeRef) = TypeRef(Pkg.Hl.Items, "KeySpec", genericArgs = listOf(keyType)) - val KeySpecNumber = TypeRef(Pkg.Hl.Items, "KeySpec.Number") - val KeySpecString = TypeRef(Pkg.Hl.Items, "KeySpec.String") - val KeySpecByteArray = TypeRef(Pkg.Hl.Items, "KeySpec.ByteArray") - val AttributeDescriptor = TypeRef(Pkg.Hl.Items, "AttributeDescriptor") - fun itemConverter(objectType: TypeRef) = TypeRef(Pkg.Hl.Items, "ItemConverter", genericArgs = listOf(objectType)) - val SimpleItemConverter = TypeRef(Pkg.Hl.Items, "SimpleItemConverter") + fun itemSchema(typeVar: String) = + TypeRef(MapperPkg.Hl.Items, "ItemSchema", genericArgs = listOf(TypeVar(typeVar))) + + fun itemSchemaPartitionKey(objectType: TypeRef, pkType: TypeRef) = + TypeRef(MapperPkg.Hl.Items, "ItemSchema.PartitionKey", genericArgs = listOf(objectType, pkType)) + + fun itemSchemaCompositeKey(objectType: TypeRef, pkType: TypeRef, skType: TypeRef) = + TypeRef(MapperPkg.Hl.Items, "ItemSchema.CompositeKey", genericArgs = listOf(objectType, pkType, skType)) + + fun keySpec(keyType: TypeRef) = TypeRef(MapperPkg.Hl.Items, "KeySpec", genericArgs = listOf(keyType)) + val KeySpecByteArray = TypeRef(MapperPkg.Hl.Items, "KeySpec.ByteArray") + val KeySpecNumber = TypeRef(MapperPkg.Hl.Items, "KeySpec.Number") + val KeySpecString = TypeRef(MapperPkg.Hl.Items, "KeySpec.String") + val AttributeDescriptor = TypeRef(MapperPkg.Hl.Items, "AttributeDescriptor") + + fun itemConverter(objectType: TypeRef) = + TypeRef(MapperPkg.Hl.Items, "ItemConverter", genericArgs = listOf(objectType)) + + val SimpleItemConverter = TypeRef(MapperPkg.Hl.Items, "SimpleItemConverter") } object Model { fun tablePartitionKey(objectType: TypeRef, pkType: TypeRef) = TypeRef( - Pkg.Hl.Model, + MapperPkg.Hl.Model, "Table.PartitionKey", genericArgs = listOf(objectType, pkType), ) fun tableCompositeKey(objectType: TypeRef, pkType: TypeRef, skType: TypeRef) = TypeRef( - Pkg.Hl.Model, + MapperPkg.Hl.Model, "Table.CompositeKey", genericArgs = listOf(objectType, pkType, skType), ) - val toItem = TypeRef(Pkg.Hl.Model, "toItem") + val toItem = TypeRef(MapperPkg.Hl.Model, "toItem") } object Values { - fun valueConverter(value: Type) = TypeRef(Pkg.Hl.Values, "ValueConverter", genericArgs = listOf(value)) - val ItemToValueConverter = TypeRef(Pkg.Hl.Values, "ItemToValueConverter") + fun valueConverter(value: Type) = TypeRef(MapperPkg.Hl.Values, "ValueConverter", genericArgs = listOf(value)) + val ItemToValueConverter = TypeRef(MapperPkg.Hl.Values, "ItemToValueConverter") object Collections { - val ListConverter = TypeRef(Pkg.Hl.CollectionValues, "ListConverter") - val MapConverter = TypeRef(Pkg.Hl.CollectionValues, "MapConverter") - - val StringSetConverter = TypeRef(Pkg.Hl.CollectionValues, "StringSetConverter") - val CharSetConverter = TypeRef(Pkg.Hl.CollectionValues, "CharSetConverter") - val CharArraySetConverter = TypeRef(Pkg.Hl.CollectionValues, "CharArraySetConverter") - - val ByteSetConverter = TypeRef(Pkg.Hl.CollectionValues, "ByteSetConverter") - val DoubleSetConverter = TypeRef(Pkg.Hl.CollectionValues, "DoubleSetConverter") - val FloatSetConverter = TypeRef(Pkg.Hl.CollectionValues, "FloatSetConverter") - val IntSetConverter = TypeRef(Pkg.Hl.CollectionValues, "IntSetConverter") - val LongSetConverter = TypeRef(Pkg.Hl.CollectionValues, "LongSetConverter") - val ShortSetConverter = TypeRef(Pkg.Hl.CollectionValues, "ShortSetConverter") - - val UByteSetConverter = TypeRef(Pkg.Hl.CollectionValues, "UByteSetConverter") - val UIntSetConverter = TypeRef(Pkg.Hl.CollectionValues, "UIntSetConverter") - val ULongSetConverter = TypeRef(Pkg.Hl.CollectionValues, "ULongSetConverter") - val UShortSetConverter = TypeRef(Pkg.Hl.CollectionValues, "UShortSetConverter") + val ListConverter = TypeRef(MapperPkg.Hl.CollectionValues, "ListConverter") + val MapConverter = TypeRef(MapperPkg.Hl.CollectionValues, "MapConverter") + + val StringSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "StringSetConverter") + val CharSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "CharSetConverter") + val CharArraySetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "CharArraySetConverter") + + val ByteSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "ByteSetConverter") + val DoubleSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "DoubleSetConverter") + val FloatSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "FloatSetConverter") + val IntSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "IntSetConverter") + val LongSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "LongSetConverter") + val ShortSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "ShortSetConverter") + + val UByteSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "UByteSetConverter") + val UIntSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "UIntSetConverter") + val ULongSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "ULongSetConverter") + val UShortSetConverter = TypeRef(MapperPkg.Hl.CollectionValues, "UShortSetConverter") } object Scalars { - fun enumConverter(enumType: Type) = TypeRef(Pkg.Hl.ScalarValues, "EnumConverter", genericArgs = listOf(enumType)) - - val BooleanConverter = TypeRef(Pkg.Hl.ScalarValues, "BooleanConverter") - val StringConverter = TypeRef(Pkg.Hl.ScalarValues, "StringConverter") - val CharConverter = TypeRef(Pkg.Hl.ScalarValues, "CharConverter") - val CharArrayConverter = TypeRef(Pkg.Hl.ScalarValues, "CharArrayConverter") - - val ByteConverter = TypeRef(Pkg.Hl.ScalarValues, "ByteConverter") - val ByteArrayConverter = TypeRef(Pkg.Hl.ScalarValues, "ByteArrayConverter") - val DoubleConverter = TypeRef(Pkg.Hl.ScalarValues, "DoubleConverter") - val FloatConverter = TypeRef(Pkg.Hl.ScalarValues, "FloatConverter") - val IntConverter = TypeRef(Pkg.Hl.ScalarValues, "IntConverter") - val LongConverter = TypeRef(Pkg.Hl.ScalarValues, "LongConverter") - val ShortConverter = TypeRef(Pkg.Hl.ScalarValues, "ShortConverter") - val UByteConverter = TypeRef(Pkg.Hl.ScalarValues, "UByteConverter") - val UIntConverter = TypeRef(Pkg.Hl.ScalarValues, "UIntConverter") - val ULongConverter = TypeRef(Pkg.Hl.ScalarValues, "ULongConverter") - val UShortConverter = TypeRef(Pkg.Hl.ScalarValues, "UShortConverter") - - val BooleanToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "BooleanToStringConverter") - val CharArrayToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "TextConverters.CharArrayToStringConverter") - val CharToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "TextConverters.CharToStringConverter") - val StringToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "TextConverters.StringToStringConverter") - val ByteToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.ByteToStringConverter") - val DoubleToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.DoubleToStringConverter") - val FloatToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.FloatToStringConverter") - val IntToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.IntToStringConverter") - val LongToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.LongToStringConverter") - val ShortToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.ShortToStringConverter") - val UByteToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.UByteToStringConverter") - val UIntToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.UIntToStringConverter") - val ULongToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.ULongToStringConverter") - val UShortToStringConverter = TypeRef(Pkg.Hl.ScalarValues, "NumberConverters.UShortToStringConverter") + fun enumConverter(enumType: Type) = TypeRef(MapperPkg.Hl.ScalarValues, "EnumConverter", genericArgs = listOf(enumType)) + + val BooleanConverter = TypeRef(MapperPkg.Hl.ScalarValues, "BooleanConverter") + val StringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "StringConverter") + val CharConverter = TypeRef(MapperPkg.Hl.ScalarValues, "CharConverter") + val CharArrayConverter = TypeRef(MapperPkg.Hl.ScalarValues, "CharArrayConverter") + + val ByteConverter = TypeRef(MapperPkg.Hl.ScalarValues, "ByteConverter") + val ByteArrayConverter = TypeRef(MapperPkg.Hl.ScalarValues, "ByteArrayConverter") + val DoubleConverter = TypeRef(MapperPkg.Hl.ScalarValues, "DoubleConverter") + val FloatConverter = TypeRef(MapperPkg.Hl.ScalarValues, "FloatConverter") + val IntConverter = TypeRef(MapperPkg.Hl.ScalarValues, "IntConverter") + val LongConverter = TypeRef(MapperPkg.Hl.ScalarValues, "LongConverter") + val ShortConverter = TypeRef(MapperPkg.Hl.ScalarValues, "ShortConverter") + val UByteConverter = TypeRef(MapperPkg.Hl.ScalarValues, "UByteConverter") + val UIntConverter = TypeRef(MapperPkg.Hl.ScalarValues, "UIntConverter") + val ULongConverter = TypeRef(MapperPkg.Hl.ScalarValues, "ULongConverter") + val UShortConverter = TypeRef(MapperPkg.Hl.ScalarValues, "UShortConverter") + + val BooleanToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "BooleanToStringConverter") + val CharArrayToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "TextConverters.CharArrayToStringConverter") + val CharToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "TextConverters.CharToStringConverter") + val StringToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "TextConverters.StringToStringConverter") + val ByteToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.ByteToStringConverter") + val DoubleToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.DoubleToStringConverter") + val FloatToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.FloatToStringConverter") + val IntToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.IntToStringConverter") + val LongToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.LongToStringConverter") + val ShortToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.ShortToStringConverter") + val UByteToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.UByteToStringConverter") + val UIntToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.UIntToStringConverter") + val ULongToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.ULongToStringConverter") + val UShortToStringConverter = TypeRef(MapperPkg.Hl.ScalarValues, "NumberConverters.UShortToStringConverter") } object SmithyTypes { - val DefaultInstantConverter = TypeRef(Pkg.Hl.SmithyTypeValues, "InstantConverter.Default") - val UrlConverter = TypeRef(Pkg.Hl.SmithyTypeValues, "UrlConverter") - val DefaultDocumentConverter = TypeRef(Pkg.Hl.SmithyTypeValues, "DocumentConverter.Default") + val DefaultInstantConverter = TypeRef(MapperPkg.Hl.SmithyTypeValues, "InstantConverter.Default") + val UrlConverter = TypeRef(MapperPkg.Hl.SmithyTypeValues, "UrlConverter") + val DefaultDocumentConverter = TypeRef(MapperPkg.Hl.SmithyTypeValues, "DocumentConverter.Default") } } object PipelineImpl { - val HReqContextImpl = TypeRef(Pkg.Hl.PipelineImpl, "HReqContextImpl") - val MapperContextImpl = TypeRef(Pkg.Hl.PipelineImpl, "MapperContextImpl") - val Operation = TypeRef(Pkg.Hl.PipelineImpl, "Operation") + val HReqContextImpl = TypeRef(MapperPkg.Hl.PipelineImpl, "HReqContextImpl") + val MapperContextImpl = TypeRef(MapperPkg.Hl.PipelineImpl, "MapperContextImpl") + val Operation = TypeRef(MapperPkg.Hl.PipelineImpl, "Operation") } } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/HighLevelOpsProcessor.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/HighLevelOpsProcessor.kt index 861334a5b00..5e5f0d47423 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/HighLevelOpsProcessor.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/HighLevelOpsProcessor.kt @@ -5,9 +5,10 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations import aws.sdk.kotlin.hll.codegen.core.CodeGeneratorFactory +import aws.sdk.kotlin.hll.codegen.ksp.processors.HllKspProcessor +import aws.sdk.kotlin.hll.codegen.model.Operation import aws.sdk.kotlin.hll.codegen.rendering.RenderContext -import aws.sdk.kotlin.hll.codegen.util.Pkg -import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.Operation +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperPkg import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.toHighLevel import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering.HighLevelRenderer import aws.sdk.kotlin.services.dynamodb.DynamoDbClient @@ -22,33 +23,36 @@ import com.google.devtools.ksp.symbol.KSFunctionDeclaration * symbols to use as codegen input (i.e., the low-level DynamoDB operations/types), wiring up rendering context, and * starting the top-level renderer (which may in turn call other renderers). */ -internal class HighLevelOpsProcessor(environment: SymbolProcessorEnvironment) : SymbolProcessor { +internal class HighLevelOpsProcessor(environment: SymbolProcessorEnvironment) : HllKspProcessor(environment) { private val codeGenerator = environment.codeGenerator - private var invoked = false private val logger = environment.logger private val opAllowlist = environment.options["op-allowlist"]?.split(";") - private val pkg = environment.options["pkg"] ?: Pkg.Hl.Ops + private val pkg = environment.options["pkg"] ?: MapperPkg.Hl.Ops - override fun process(resolver: Resolver): List { - if (!invoked) { - invoked = true + override fun processImpl(resolver: Resolver): List { + logger.info("Scanning low-level DDB client for operations and types") + val operations = getOperations(resolver) + val codegenFactory = CodeGeneratorFactory(codeGenerator, logger) // FIXME Pass dependencies + val ctx = RenderContext(logger, codegenFactory, pkg, "dynamodb-mapper-ops-codegen") - logger.info("Scanning low-level DDB client for operations and types") - val operations = getOperations(resolver) - val codegenFactory = CodeGeneratorFactory(codeGenerator, logger) // FIXME Pass dependencies - val ctx = RenderContext(logger, codegenFactory, pkg, "dynamodb-mapper-ops-codegen") - - HighLevelRenderer(ctx, operations).render() - } + HighLevelRenderer(ctx, operations).render() return listOf() } - private fun allow(func: KSFunctionDeclaration) = - (opAllowlist?.contains(func.simpleName.getShortName()) ?: true).also { - if (!it) logger.warn("${func.simpleName.getShortName()} not in allowlist; skipping codegen") + private fun allow(func: KSFunctionDeclaration): Boolean { + val name = func.simpleName.getShortName() + val allowed = opAllowlist?.contains(name) + + when (allowed) { + false -> logger.warn("$name not in allowlist; skipping codegen") + true -> logger.info("$name in allowlist; processing...") + null -> Unit // There is no allowlist—don't log anything } + return allowed ?: true + } + private fun getOperations(resolver: Resolver): List = resolver .getClassDeclarationByName()!! .getDeclaredFunctions() diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ItemSourceKind.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ItemSourceKind.kt index bcb406c765a..58f67c9afe7 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ItemSourceKind.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ItemSourceKind.kt @@ -4,9 +4,10 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model +import aws.sdk.kotlin.hll.codegen.model.Operation import aws.sdk.kotlin.hll.codegen.model.TypeRef import aws.sdk.kotlin.hll.codegen.model.TypeVar -import aws.sdk.kotlin.hll.codegen.util.Pkg +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperPkg /** * Identifies a type in the `ItemSource` hierarchy @@ -42,7 +43,7 @@ internal enum class ItemSourceKind( * Get the [TypeRef] for the `*Spec` type for this item source kind * @param typeVar The type variable name to use for the generic type */ - fun getSpecType(typeVar: String): TypeRef = TypeRef(Pkg.Hl.Model, "${name}Spec", listOf(TypeVar(typeVar))) + fun getSpecType(typeVar: String): TypeRef = TypeRef(MapperPkg.Hl.Model, "${name}Spec", listOf(TypeVar(typeVar))) } /** diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/MemberCodegenBehavior.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/MemberCodegenBehavior.kt index 079c475f249..a07c96557ac 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/MemberCodegenBehavior.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/MemberCodegenBehavior.kt @@ -8,10 +8,11 @@ import aws.sdk.kotlin.hll.codegen.model.Member import aws.sdk.kotlin.hll.codegen.model.TypeRef import aws.sdk.kotlin.hll.codegen.model.Types import aws.sdk.kotlin.hll.codegen.model.nullable -import aws.sdk.kotlin.hll.codegen.util.Pkg +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperPkg import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes private val attrMapTypes = setOf(MapperTypes.AttributeMap, MapperTypes.AttributeMap.nullable()) +private val attrMapListTypes = Types.Kotlin.list(MapperTypes.AttributeMap).let { setOf(it, it.nullable()) } /** * Describes a behavior to apply for a given [Member] in a low-level structure when generating code for an equivalent @@ -19,19 +20,6 @@ private val attrMapTypes = setOf(MapperTypes.AttributeMap, MapperTypes.Attribute * behaviors that will be implemented by calling code. */ internal sealed interface MemberCodegenBehavior { - companion object { - /** - * Identifies a [MemberCodegenBehavior] for the given [Member] by way of various heuristics - * @param member The [Member] for which to identify a codegen behavior - */ - fun identifyFor(member: Member) = when { - member in unsupportedMembers -> Drop - member.type in attrMapTypes -> if (member.name == "key") MapKeys else MapAll - member.isTableName -> Hoist - else -> PassThrough - } - } - /** * Indicates that a member should be copied as-is from a low-level structure to a high-level equivalent (i.e., no * changes to name, type, etc. are required) @@ -47,11 +35,18 @@ internal sealed interface MemberCodegenBehavior { /** * Indicates that a member is an attribute map which contains _key_ attributes for a data type (as opposed to _all_ - * attributes) and should be replaced with a generic type (i.e., a `Map` member - * in a low-level structure should be replaced with a generic `T` member in a high-level structure) + * attributes) and should be replaced with a generic type (i.e., a `Map` member in a + * low-level structure should be replaced with a generic `T` member in a high-level structure) */ data object MapKeys : MemberCodegenBehavior + /** + * Indicates that a member is a list of attribute maps which may contain attributes for a data type and should be + * replaced with a generic list type (i.e., a `List>` member in a low-level structure + * should be replaced with a generic `List` member in a high-level structure) + */ + data object ListMapAll : MemberCodegenBehavior + /** * Indicates that a member is unsupported and should not be replicated from a low-level structure to the high-level * equivalent (e.g., a deprecated member that has been replaced with new features need not be carried forward) @@ -74,6 +69,7 @@ internal val Member.codegenBehavior: MemberCodegenBehavior get() = when { this in unsupportedMembers -> MemberCodegenBehavior.Drop type in attrMapTypes -> if (name == "key") MemberCodegenBehavior.MapKeys else MemberCodegenBehavior.MapAll + type in attrMapListTypes -> MemberCodegenBehavior.ListMapAll isTableName || isIndexName -> MemberCodegenBehavior.Hoist else -> MemberCodegenBehavior.PassThrough } @@ -84,7 +80,7 @@ private val Member.isTableName: Boolean private val Member.isIndexName: Boolean get() = name == "indexName" && type == Types.Kotlin.StringNullable -private fun llType(name: String) = TypeRef(Pkg.Ll.Model, name) +private fun llType(name: String) = TypeRef(MapperPkg.Ll.Model, name) private val unsupportedMembers = listOf( // superseded by ConditionExpression diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ModelAttributes.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ModelAttributes.kt index 5234125cfdd..6bbc651fa4e 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ModelAttributes.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/ModelAttributes.kt @@ -4,6 +4,8 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model +import aws.sdk.kotlin.hll.codegen.model.Operation +import aws.sdk.kotlin.hll.codegen.model.Structure import aws.smithy.kotlin.runtime.collections.AttributeKey /** @@ -19,4 +21,10 @@ internal object ModelAttributes { * For a given high-level [Structure], this attribute key identifies the associated low-level [Structure] */ val LowLevelStructure: AttributeKey = AttributeKey("aws.sdk.kotlin.ddbmapper#LowLevelStructure") + + /** + * For a given [Operation], this attribute key contains relevant pagination members (if applicable) in the request + * and response + */ + val PaginationInfo: AttributeKey = AttributeKey("aws.sdk.kotlin.ddbmapper#PaginationInfo") } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Operation.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Operation.kt index d2607abcd6c..84cb030683a 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Operation.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Operation.kt @@ -4,44 +4,9 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model -import aws.sdk.kotlin.hll.codegen.util.capitalizeFirstChar +import aws.sdk.kotlin.hll.codegen.model.Operation import aws.sdk.kotlin.hll.codegen.util.plus -import aws.smithy.kotlin.runtime.collections.Attributes -import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.collections.get -import com.google.devtools.ksp.symbol.KSFunctionDeclaration - -/** - * Describes a service operation (i.e., API method) - * @param methodName The name of the operation as a code-suitable method name. For example, `getItem` is a suitable - * method name in Kotlin, whereas `GetItem` is not (improperly cased) nor is `get item` (contains a space). - * @param request The [Structure] for requests/inputs to this operation - * @param response The [Structure] for responses/output from this operation - * @param attributes An [Attributes] collection for associating typed attributes with this operation - */ -internal data class Operation( - val methodName: String, - val request: Structure, - val response: Structure, - val attributes: Attributes = emptyAttributes(), -) { - /** - * The capitalized name of this operation's [methodName]. For example, if [methodName] is `getItem` then [name] - * would be `GetItem`. - */ - val name = methodName.capitalizeFirstChar // e.g., "GetItem" vs "getItem" - - companion object { - /** - * Derive an [Operation] from a [KSFunctionDeclaration] - */ - fun from(declaration: KSFunctionDeclaration) = Operation( - methodName = declaration.simpleName.getShortName(), - request = Structure.from(declaration.parameters.single().type), - response = Structure.from(declaration.returnType!!), - ) - } -} /** * Gets the low-level [Operation] equivalent for this high-level operation diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Pagination.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Pagination.kt new file mode 100644 index 00000000000..b521cf68e26 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Pagination.kt @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model + +import aws.sdk.kotlin.hll.codegen.model.Member +import aws.sdk.kotlin.hll.codegen.model.ModelParsingPlugin +import aws.sdk.kotlin.hll.codegen.model.Operation +import aws.sdk.kotlin.hll.codegen.util.plus + +/** + * Identifies the [Member] instances of an operation's request and response which control pagination + * @param inputToken The field for passing a pagination token into a request + * @param outputToken The field for receiving a pagination token from a request + * @param limit The field for limiting the number of returned results + * @param items The field for getting the low-level items from each page of results + */ +internal data class PaginationMembers( + val inputToken: Member, + val outputToken: Member, + val limit: Member, + val items: Member, +) { + internal companion object { + fun forOperationOrNull(operation: Operation): PaginationMembers? { + val inputToken = operation.request.members.find { it.name == "exclusiveStartKey" } ?: return null + val outputToken = operation.response.members.find { it.name == "lastEvaluatedKey" } ?: return null + val limit = operation.request.members.find { it.name == "limit" } ?: return null + val items = operation.response.members.find { it.name == "items" } ?: return null + + return PaginationMembers(inputToken, outputToken, limit, items) + } + } +} + +/** + * Gets the [PaginationMembers] for an operation, if applicable. If the operation does not support pagination, this + * property returns `null`. + */ +internal val Operation.paginationInfo: PaginationMembers? + get() = attributes.getOrNull(ModelAttributes.PaginationInfo) + +/** + * A codegen plugin that adds DDB-specific pagination info to operations + */ +internal class DdbPaginationPlugin : ModelParsingPlugin { + override fun postProcessOperation(operation: Operation): Operation { + val paginationMembers = PaginationMembers.forOperationOrNull(operation) ?: return operation + val newAttributes = operation.attributes + (ModelAttributes.PaginationInfo to paginationMembers) + return operation.copy(attributes = newAttributes) + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Structure.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Structure.kt index df780f5d2c1..4427a5a3424 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Structure.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Structure.kt @@ -4,42 +4,9 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model -import aws.sdk.kotlin.hll.codegen.model.Member -import aws.sdk.kotlin.hll.codegen.model.Type -import aws.sdk.kotlin.hll.codegen.model.TypeRef -import aws.sdk.kotlin.hll.codegen.model.TypeVar +import aws.sdk.kotlin.hll.codegen.model.* import aws.sdk.kotlin.hll.codegen.util.plus -import aws.smithy.kotlin.runtime.collections.Attributes -import aws.smithy.kotlin.runtime.collections.emptyAttributes import aws.smithy.kotlin.runtime.collections.get -import com.google.devtools.ksp.getDeclaredProperties -import com.google.devtools.ksp.symbol.KSClassDeclaration -import com.google.devtools.ksp.symbol.KSTypeReference - -/** - * Describes a structure (i.e., class, struct, etc.) which contains zero or more [Member] instances - * @param type The [TypeRef] for this structure, which includes its name and Kotlin package - * @param members The [Member] instances which are part of this structure - * @param attributes An [Attributes] collection for associating typed attributes with this structure - */ -internal data class Structure( - val type: TypeRef, - val members: List, - val attributes: Attributes = emptyAttributes(), -) { - companion object { - /** - * Derives a [Structure] from the given [KSTypeReference] - */ - fun from(ksTypeRef: KSTypeReference) = Structure( - type = Type.from(ksTypeRef), - members = (ksTypeRef.resolve().declaration as KSClassDeclaration) - .getDeclaredProperties() - .map(Member.Companion::from) - .toList(), - ) - } -} /** * Gets the low-level [Structure] equivalent for this high-level structure @@ -48,7 +15,7 @@ internal val Structure.lowLevel: Structure get() = attributes[ModelAttributes.LowLevelStructure] /** - * Derives a a high-level [Structure] equivalent for this low-level structure + * Derives a high-level [Structure] equivalent for this low-level structure * @param pkg The Kotlin package to use for the high-level structure */ internal fun Structure.toHighLevel(pkg: String): Structure { @@ -59,11 +26,19 @@ internal fun Structure.toHighLevel(pkg: String): Structure { val hlMembers = llStructure.members.mapNotNull { llMember -> when (llMember.codegenBehavior) { MemberCodegenBehavior.PassThrough -> llMember + MemberCodegenBehavior.MapAll, MemberCodegenBehavior.MapKeys -> llMember.copy(type = TypeVar("T", llMember.type.nullable)) + + MemberCodegenBehavior.ListMapAll -> { + val llListType = llMember.type as? TypeRef ?: error("`ListMapAll` member is required to be a TypeRef") + val hlListType = llListType.copy(genericArgs = listOf(TypeVar("T")), nullable = llListType.nullable) + llMember.copy(type = hlListType) + } + else -> null } - } + }.toSet() val hlAttributes = llStructure.attributes + (ModelAttributes.LowLevelStructure to llStructure) diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/DataTypeGenerator.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/DataTypeGenerator.kt index ca73d688aaa..8d2616e1902 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/DataTypeGenerator.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/DataTypeGenerator.kt @@ -5,48 +5,53 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering import aws.sdk.kotlin.hll.codegen.core.CodeGenerator -import aws.sdk.kotlin.hll.codegen.model.Member -import aws.sdk.kotlin.hll.codegen.model.Type -import aws.sdk.kotlin.hll.codegen.model.TypeRef -import aws.sdk.kotlin.hll.codegen.model.TypeVar +import aws.sdk.kotlin.hll.codegen.model.* +import aws.sdk.kotlin.hll.codegen.rendering.BuilderRenderer import aws.sdk.kotlin.hll.codegen.rendering.RenderContext -import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.Structure +import aws.sdk.kotlin.hll.codegen.rendering.RenderOptions +import aws.sdk.kotlin.hll.codegen.rendering.Visibility +import aws.sdk.kotlin.hll.codegen.util.plus /** - * Generates immutable data types from a [Structure] into an underlying [CodeGenerator]. These data types consist of a - * read-only `interface` (with a `val` field for each [Structure] member), a `private data class` that serves as the - * default implementation, and a `public` factory function which allows constructing new instances by passing values as - * arguments. + * Generates immutable data types from a [Structure] into an underlying [CodeGenerator]. These data types consist of: + * * a read-only `interface` (with a `val` field for each [Structure] member) + * * a `private data class` that serves as the default implementation + * * a `toBuilder` method which turns a built object into a builder + * * a `copy` method which modifies a copy of a built object and returns the result + * * a DSL factory function named after the generated interface for creating new instances * - * **FIXME**: This generator SHOULD generate a mutable builder object instead of a factory function for backwards - * compatibility. + * This generator delegates to [BuilderRenderer] to also generate builders. * * Example of generated data type: * * ```kotlin - * public interface User { + * public interface FooRequest { * public companion object { } * * public val id: Int * public val name: String - * public val active: Boolean + * public val foo: T * } * - * private data class UserImpl( + * private data class UserImpl( * override val id: Int, * override val name: String, - * override val active: Boolean, + * override val foo: T, * ) * - * public fun User( - * id: Int, - * name: String, - * active: Boolean, - * ): User = UserImpl( - * id, - * name, - * active, - * ) + * // Builder generated by BuilderRenderer + * + * public fun FooRequest.toBuilder(): FooRequestBuilder = FooRequestBuilder.apply { + * id = this@toBuilder.id + * name = this@toBuilder.name + * foo = this@toBuilder.foo + * } + * + * public fun FooRequest.copy(block: FooRequestBuilder.() -> Unit): FooRequest = + * toBuilder.apply(block).build() + * + * public fun FooRequest(block: FooRequestBuilder.() -> Unit): FooRequest = + * FooRequestBuilder().apply(block).build() * ``` * @param ctx The active rendering context * @param generator The underlying generator for the context into which the data type should be written @@ -65,51 +70,60 @@ internal class DataTypeGenerator( } blankLine() - val genericParams = structure - .members - .flatMap { it.type.generics() } - .map { it.shortName } - .requireAllDistinct() - .takeUnless { it.isEmpty() } - ?.joinToString(", ", "<", ">") - ?: "" - + val genericParams = structure.genericVars().asParamsList() + val genericParamsWithSpacer = if (genericParams.isEmpty()) "" else "$genericParams " val implName = "${structure.type.shortName}Impl" + val implType = structure.type.copy(shortName = implName) openBlock("private data class #L#L(", implName, genericParams) members { write("override val #L: #T,", name, type) } closeBlock("): #T", structure.type) blankLine() - // TODO replace function builder with Builder interface+impl - openBlock("public fun #L #L(", genericParams, structure.type.shortName) - members { write("#L: #T,", name, type) } - closeAndOpenBlock("): #T = #L(", structure.type, implName) - members { write("#L,", name) } - closeBlock(")") - } + val builderCtx = ctx.copy( + attributes = ctx.attributes + (RenderOptions.VisibilityAttribute to Visibility.PUBLIC), + ) + val builderName = BuilderRenderer.builderName(structure.type) + BuilderRenderer(this, structure.type, implType, structure.members, builderCtx).render() - private inline fun members(crossinline block: Member.() -> Unit) { - structure.members.forEach { it.block() } - } -} + blankLine() + withBlock( + "public fun #1L#2T.toBuilder(): #3L#4L = #3L#4L().apply {", + "}", + genericParamsWithSpacer, + structure.type, + builderName, + genericParams, + ) { + members { write("#1L = this@toBuilder.#1L", name) } + } -private fun Type.generics(): List = buildList { - when (val type = this@generics) { - is TypeVar -> add(type) - is TypeRef -> type.genericArgs.flatMap { it.generics() } - } -} + blankLine() + withBlock( + "public fun #1L#2T.copy(block: #3L#4L.() -> Unit): #2T =", + "", + genericParamsWithSpacer, + structure.type, + builderName, + genericParams, + ) { + write("toBuilder().apply(block).build()") + } -private fun > C.requireAllDistinct(): C { - val collection = this - val itemCounts = buildMap { - collection.forEach { element -> - compute(element) { _, existingCount -> (existingCount ?: 0) + 1 } + blankLine() + withBlock( + "public fun #L#L(block: #L#L.() -> Unit): #T =", + "", + genericParamsWithSpacer, + structure.type.shortName, + builderName, + genericParams, + structure.type, + ) { + write("#L#L().apply(block).build()", builderName, genericParams) } } - val duplicates = itemCounts.filter { (_, count) -> count > 1 }.keys - require(duplicates.isEmpty()) { "Found duplicated items: $duplicates" } - - return collection + private inline fun members(crossinline block: Member.() -> Unit) { + structure.members.forEach { it.block() } + } } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/HighLevelRenderer.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/HighLevelRenderer.kt index 878173f2bfb..d6be663cbe4 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/HighLevelRenderer.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/HighLevelRenderer.kt @@ -4,10 +4,10 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering +import aws.sdk.kotlin.hll.codegen.model.Operation import aws.sdk.kotlin.hll.codegen.model.Type import aws.sdk.kotlin.hll.codegen.rendering.RenderContext import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ItemSourceKind -import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.Operation import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.itemSourceKinds /** diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationRenderer.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationRenderer.kt index 2eb0050becf..fa34d34a225 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationRenderer.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationRenderer.kt @@ -6,14 +6,14 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering import aws.sdk.kotlin.hll.codegen.core.* import aws.sdk.kotlin.hll.codegen.model.Member +import aws.sdk.kotlin.hll.codegen.model.Operation +import aws.sdk.kotlin.hll.codegen.model.Structure import aws.sdk.kotlin.hll.codegen.rendering.RenderContext import aws.sdk.kotlin.hll.codegen.rendering.RendererBase import aws.sdk.kotlin.hll.codegen.rendering.info import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.* -// FIXME handle paginated operations differently (e.g., don't map pagination parameters, provide only Flow API) - /** * Renders a dedicated file for a high-level operation, including request/response types, converters between low/high * types, and a factory method for creating instances of the DDB mapper runtime operation @@ -24,7 +24,7 @@ internal class OperationRenderer( private val ctx: RenderContext, private val operation: Operation, ) : RendererBase(ctx, operation.name) { - val members = operation.request.lowLevel.members.groupBy { m -> + private val members = operation.request.lowLevel.members.groupBy { m -> m.codegenBehavior.also { ctx.info(" ${m.name} → $it") } } @@ -78,7 +78,6 @@ internal class OperationRenderer( } private fun renderRequest() { - ctx.info("For type ${operation.request.lowLevelName}:") DataTypeGenerator(ctx, this, operation.request).generate() blankLine() @@ -95,6 +94,9 @@ internal class OperationRenderer( members(MemberCodegenBehavior.MapAll) { write("this@convert.#1L?.let { #1L = schema.converter.convertTo(it) }", name) } + members(MemberCodegenBehavior.ListMapAll) { + write("#1L = this@convert.#1L?.map { schema.converter.toItem(it) }", name) + } members(MemberCodegenBehavior.Hoist) { write("this.#1L = #1L", name) } closeBlock("}") } @@ -111,16 +113,28 @@ internal class OperationRenderer( imports += ImportDirective(operation.response.lowLevel.type, operation.response.lowLevelName) withBlock( - "private fun #L.convert(schema: #T) = #T(", - ")", + "private fun #L.convert(schema: #T) = #T {", + "}", operation.response.lowLevelName, MapperTypes.Items.itemSchema("T"), operation.response.type, ) { - members(MemberCodegenBehavior.PassThrough) { write("#1L = this@convert.#1L,", name) } + members(MemberCodegenBehavior.PassThrough) { write("#1L = this@convert.#1L", name) } members(MemberCodegenBehavior.MapKeys, MemberCodegenBehavior.MapAll) { - write("#1L = this@convert.#1L?.#2T()?.let(schema.converter::convertFrom),", name, MapperTypes.Model.toItem) + write( + "#1L = this@convert.#1L?.#2T()?.let(schema.converter::convertFrom)", + name, + MapperTypes.Model.toItem, + ) + } + + members(MemberCodegenBehavior.ListMapAll) { + write( + "#1L = this@convert.#1L?.map { schema.converter.convertFrom(it.#2T()) }", + name, + MapperTypes.Model.toItem, + ) } } } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationsTypeRenderer.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationsTypeRenderer.kt index 0b7d5f41779..2a6834763a9 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationsTypeRenderer.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/OperationsTypeRenderer.kt @@ -4,15 +4,15 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering -import aws.sdk.kotlin.hll.codegen.model.Type -import aws.sdk.kotlin.hll.codegen.model.TypeRef -import aws.sdk.kotlin.hll.codegen.model.TypeVar +import aws.sdk.kotlin.hll.codegen.model.* +import aws.sdk.kotlin.hll.codegen.rendering.BuilderRenderer import aws.sdk.kotlin.hll.codegen.rendering.RenderContext import aws.sdk.kotlin.hll.codegen.rendering.RendererBase import aws.sdk.kotlin.hll.codegen.util.lowercaseFirstChar +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ItemSourceKind -import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.Operation import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.itemSourceKinds +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.paginationInfo /** * Renders the `*Operations` interface and `*OperationsImpl` class which contain a method for each codegenned @@ -37,13 +37,41 @@ internal class OperationsTypeRenderer( override fun generate() { renderInterface() + renderDslOps() + renderPaginators(forResponses = true) - if (!itemSourceKind.isAbstract) { + if (itemSourceKind.isAbstract) { + blankLine() + renderPaginators(forItems = true) + } else { blankLine() renderImpl() } + } + + private fun renderDslOps() = operations + .filterNot { it.appliesToAncestorKind() } + .forEach(::renderDslOp) + + private fun renderDslOp(op: Operation) { + val builderType = BuilderRenderer.builderType(op.request.type) + val generics = op.request.genericVars().asParamsList(" ") + + if (op.paginationInfo != null) renderManualPaginationAnnotation(op) else blankLine() + + withBlock( + "public suspend inline fun #L#T.#L(crossinline block: #T.() -> Unit): #T =", + "", + generics, + interfaceType, + op.methodName, + builderType, + op.response.type, + ) { + write("#L(#T().apply(block).build())", op.methodName, builderType) + } - // TODO also render DSL extension methods (e.g., table.getItem { key = ... }) + blankLine() } private fun renderImpl() { @@ -56,13 +84,17 @@ internal class OperationsTypeRenderer( itemSourceKind.getSpecType("T"), interfaceType, ) { - operations.forEach { operation -> + operations.forEach { op -> + if (op.paginationInfo != null) renderManualPaginationAnnotation(op) + write( "override suspend fun #L(request: #T) = #L(spec).execute(request)", - operation.methodName, - operation.request.type, - OperationRenderer.factoryFunctionName(operation), + op.methodName, + op.request.type, + OperationRenderer.factoryFunctionName(op), ) + + if (op.paginationInfo != null) blankLine() } } } @@ -79,19 +111,39 @@ internal class OperationsTypeRenderer( parentType?.let { writeInline(": #T ", parentType) } withBlock("{", "}") { - operations.forEach { operation -> - val overrideModifier = if (operation.appliesToAncestorKind()) " override" else "" - write( - "public#L suspend fun #L(request: #T): #T", - overrideModifier, - operation.methodName, - operation.request.type, - operation.response.type, - ) - } + operations.forEach(::renderOp) } } + private fun renderManualPaginationAnnotation(op: Operation) { + blankLine() + write( + "@#T(paginatedEquivalent = #S)", + MapperTypes.Annotations.ManualPagination, + PaginatorRenderer.paginatorName(op), + ) + } + + private fun renderOp(op: Operation) { + val overrideModifier = if (op.appliesToAncestorKind()) " override" else "" + + if (op.paginationInfo != null) renderManualPaginationAnnotation(op) + + write( + "public#L suspend fun #L(request: #T): #T", + overrideModifier, + op.methodName, + op.request.type, + op.response.type, + ) + + if (op.paginationInfo != null) blankLine() + } + + private fun renderPaginators(forResponses: Boolean = false, forItems: Boolean = false) = operations + .filterNot { it.paginationInfo == null } + .forEach { op -> PaginatorRenderer(ctx, this, op, interfaceType, forResponses, forItems).render() } + private fun Operation.appliesToAncestorKind() = itemSourceKind.parent?.let { appliesToKindOrAncestor(it) } ?: false } diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/PaginatorRenderer.kt b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/PaginatorRenderer.kt new file mode 100644 index 00000000000..e4fc8f6097a --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/PaginatorRenderer.kt @@ -0,0 +1,171 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering + +import aws.sdk.kotlin.hll.codegen.core.CodeGenerator +import aws.sdk.kotlin.hll.codegen.model.Operation +import aws.sdk.kotlin.hll.codegen.model.Type +import aws.sdk.kotlin.hll.codegen.model.TypeVar +import aws.sdk.kotlin.hll.codegen.model.Types +import aws.sdk.kotlin.hll.codegen.rendering.BuilderRenderer +import aws.sdk.kotlin.hll.codegen.rendering.RenderContext +import aws.sdk.kotlin.hll.codegen.util.capitalizeFirstChar +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes +import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.paginationInfo + +/** + * Renders paginator methods for an operation. There are two types of paginators handled by this renderer: + * * response paginators (i.e., each response is an element in a `Flow`) + * * item paginators (i.e., each item from each response is an element in a `Flow`) + * + * Rendering these paginators is enabled by setting [forResponses] or [forItems], respectively. + * + * ## Response paginators + * + * When [forResponses] is true, this paginator will render code such as the following: + * + * ```kotlin + * public fun ItemSourceOperations.queryPaginated(initialRequest: QueryRequest): Flow> = flow { + * var cursor = initialRequest.exclusiveStartKey + * var hasNextPage = true + * + * while (hasNextPage) { + * val req = initialRequest.copy { exclusiveStartKey = cursor } + * + * @OptIn(ManualPagination::class) + * val res = this@queryPaginated.query(req) + * + * cursor = res.lastEvaluatedKey + * hasNextPage = cursor != null + * emit(res) + * } + * } + * + * public inline fun ItemSourceOperations.queryPaginated(crossinline block: QueryRequestBuilder.() -> Unit): Flow> = + * queryPaginated(QueryRequestBuilder().apply(block).build()) + * ``` + * + * ## Item paginators + * + * When [forItems] is true, this paginator will render code such as the following: + * + * ```kotlin + * @JvmName("queryItems") + * public fun Flow>.items(): Flow = + * transform { page -> + * page.items?.forEach { item -> + * emit(item) + * } + * } + * ``` + * + * @param ctx The context for this renderer + * @param generator The underlying code generator to use when rendering + * @param op The operation for which paginators will be rendered + * @param extensionOf The type from which paginators will be extension methods. This type is optional—if `null` is + * passed then the rendered paginators will be simple functions instead of extension methods. This only applies to + * **response** paginators. + * @param forResponses Enables generating response paginators + * @param forItems Enables generating item paginators + */ +internal class PaginatorRenderer( + private val ctx: RenderContext, + private val generator: CodeGenerator, + private val op: Operation, + private val extensionOf: Type?, + private val forResponses: Boolean, + private val forItems: Boolean, +) : CodeGenerator by generator { + init { + require(forResponses || forItems) { "One of `forResponses` or `forItems` must be set to true" } + } + + internal companion object { + fun paginatorName(op: Operation) = "${op.methodName}Paginated" + } + + private val paginationInfo = requireNotNull(op.paginationInfo) { "Operation ${op.name} is not paginatable" } + private val name = paginatorName(op) + + private val requestType = op.request.type + private val requestBuilderType = BuilderRenderer.builderType(requestType) + private val responseType = op.response.type + + private val itemFlowType = Types.Kotlinx.Coroutines.Flow.flow(TypeVar("T")) + private val pageFlowType = Types.Kotlinx.Coroutines.Flow.flow(responseType) + + fun render() { + if (forResponses) { + blankLine() + renderPaginatorWithRequest() + blankLine() + renderPaginatorWithDsl() + } + + if (forItems) { + blankLine() + renderItemsPaginator() + } + } + + private fun renderItemsPaginator() { + val jvmName = "${op.methodName}${paginationInfo.items.name.capitalizeFirstChar}" + write("@#T(#S)", Types.Kotlin.Jvm.JvmName, jvmName) + withBlock("public fun #T.items(): #T =", "", pageFlowType, itemFlowType) { + withBlock("#T { page ->", "}", Types.Kotlinx.Coroutines.Flow.transform) { + withBlock("page.#L?.forEach { item ->", "}", paginationInfo.items.name) { + write("emit(item)") + } + } + } + } + + private fun renderPaginatorWithDsl() { + writeInline("public inline fun ") + + extensionOf?.let { writeInline("#T.", extensionOf) } + + withBlock( + "#L(crossinline block: #T.() -> Unit): #T =", + "", + name, + requestBuilderType, + pageFlowType, + ) { + write("#L(#T().apply(block).build())", name, requestBuilderType) + } + } + + private fun renderPaginatorWithRequest() { + writeInline("public fun ") + + extensionOf?.let { writeInline("#T.", extensionOf) } + + withBlock( + "#L(initialRequest: #T): #T = #T {", + "}", + name, + requestType, + pageFlowType, + Types.Kotlinx.Coroutines.Flow.flow, + ) { + write("var cursor = initialRequest.#L", paginationInfo.inputToken.name) + write("var hasNextPage = true") + blankLine() + withBlock("while (hasNextPage) {", "}") { + write("val req = initialRequest.copy { #L = cursor }", paginationInfo.inputToken.name) + + blankLine() + write("@#T(#T::class)", Types.Kotlin.OptIn, MapperTypes.Annotations.ManualPagination) + write("val res = this@#L.#L(req)", name, op.methodName) + + blankLine() + write("cursor = res.#L", paginationInfo.outputToken.name) + write("hasNextPage = cursor != null") + write("emit(res)") + } + } + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/resources/META-INF/services/aws.sdk.kotlin.hll.codegen.model.ModelParsingPlugin b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/resources/META-INF/services/aws.sdk.kotlin.hll.codegen.model.ModelParsingPlugin new file mode 100644 index 00000000000..845d4ada987 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/resources/META-INF/services/aws.sdk.kotlin.hll.codegen.model.ModelParsingPlugin @@ -0,0 +1 @@ +aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.DdbPaginationPlugin diff --git a/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api b/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api index 85f10d86873..017ba1eb67f 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api +++ b/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api @@ -32,6 +32,10 @@ public final class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbMapperKt { public static synthetic fun DynamoDbMapper$default (Laws/sdk/kotlin/services/dynamodb/DynamoDbClient;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Laws/sdk/kotlin/hll/dynamodbmapper/DynamoDbMapper; } +public abstract interface annotation class aws/sdk/kotlin/hll/dynamodbmapper/annotations/ManualPagination : java/lang/annotation/Annotation { + public abstract fun paginatedEquivalent ()Ljava/lang/String; +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/expressions/AndExpr : aws/sdk/kotlin/hll/dynamodbmapper/expressions/BooleanExpr { public abstract fun accept (Laws/sdk/kotlin/hll/dynamodbmapper/expressions/ExpressionVisitor;)Ljava/lang/Object; public abstract fun getOperands ()Ljava/util/List; @@ -677,8 +681,12 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/model/TableSpe } public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemKt { - public static final fun DeleteItemRequest (Ljava/lang/Object;Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics;Laws/sdk/kotlin/services/dynamodb/model/ReturnValue;Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest; - public static final fun DeleteItemResponse (Ljava/lang/Object;Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse; + public static final fun DeleteItemRequest (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest; + public static final fun DeleteItemResponse (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequestBuilder; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponseBuilder; } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest { @@ -693,6 +701,21 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Del public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequestBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest; + public final fun getKey ()Ljava/lang/Object; + public final fun getReturnConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity; + public final fun getReturnItemCollectionMetrics ()Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics; + public final fun getReturnValues ()Laws/sdk/kotlin/services/dynamodb/model/ReturnValue; + public final fun getReturnValuesOnConditionCheckFailure ()Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure; + public final fun setKey (Ljava/lang/Object;)V + public final fun setReturnConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)V + public final fun setReturnItemCollectionMetrics (Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics;)V + public final fun setReturnValues (Laws/sdk/kotlin/services/dynamodb/model/ReturnValue;)V + public final fun setReturnValuesOnConditionCheckFailure (Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse { public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse$Companion; public abstract fun getAttributes ()Ljava/lang/Object; @@ -703,9 +726,24 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Del public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponseBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemResponse; + public final fun getAttributes ()Ljava/lang/Object; + public final fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; + public final fun getItemCollectionMetrics ()Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics; + public final fun setAttributes (Ljava/lang/Object;)V + public final fun setConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;)V + public final fun setItemCollectionMetrics (Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics;)V +} + public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemKt { - public static final fun GetItemRequest (Ljava/lang/Boolean;Ljava/lang/Object;Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest; - public static final fun GetItemResponse (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;Ljava/lang/Object;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse; + public static final fun GetItemRequest (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest; + public static final fun GetItemResponse (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequestBuilder; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponseBuilder; } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest { @@ -718,6 +756,17 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Get public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequestBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest; + public final fun getConsistentRead ()Ljava/lang/Boolean; + public final fun getKey ()Ljava/lang/Object; + public final fun getReturnConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity; + public final fun setConsistentRead (Ljava/lang/Boolean;)V + public final fun setKey (Ljava/lang/Object;)V + public final fun setReturnConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse { public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse$Companion; public abstract fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; @@ -727,19 +776,50 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Get public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponseBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemResponse; + public final fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; + public final fun getItem ()Ljava/lang/Object; + public final fun setConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;)V + public final fun setItem (Ljava/lang/Object;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperations : aws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations { public abstract fun query (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun scan (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperationsKt { + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/IndexOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations { public abstract fun query (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun scan (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperationsKt { + public static final fun query (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun queryItems (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; + public static final fun scan (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun scanItems (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; +} + public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemKt { - public static final fun PutItemRequest (Ljava/lang/Object;Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics;Laws/sdk/kotlin/services/dynamodb/model/ReturnValue;Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest; - public static final fun PutItemResponse (Ljava/lang/Object;Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse; + public static final fun PutItemRequest (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest; + public static final fun PutItemResponse (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequestBuilder; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponseBuilder; } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest { @@ -754,6 +834,21 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Put public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequestBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemRequest; + public final fun getItem ()Ljava/lang/Object; + public final fun getReturnConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity; + public final fun getReturnItemCollectionMetrics ()Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics; + public final fun getReturnValues ()Laws/sdk/kotlin/services/dynamodb/model/ReturnValue; + public final fun getReturnValuesOnConditionCheckFailure ()Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure; + public final fun setItem (Ljava/lang/Object;)V + public final fun setReturnConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)V + public final fun setReturnItemCollectionMetrics (Laws/sdk/kotlin/services/dynamodb/model/ReturnItemCollectionMetrics;)V + public final fun setReturnValues (Laws/sdk/kotlin/services/dynamodb/model/ReturnValue;)V + public final fun setReturnValuesOnConditionCheckFailure (Laws/sdk/kotlin/services/dynamodb/model/ReturnValuesOnConditionCheckFailure;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse { public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse$Companion; public abstract fun getAttributes ()Ljava/lang/Object; @@ -764,9 +859,24 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Put public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponseBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemResponse; + public final fun getAttributes ()Ljava/lang/Object; + public final fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; + public final fun getItemCollectionMetrics ()Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics; + public final fun setAttributes (Ljava/lang/Object;)V + public final fun setConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;)V + public final fun setItemCollectionMetrics (Laws/sdk/kotlin/services/dynamodb/model/ItemCollectionMetrics;)V +} + public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryKt { - public static final fun QueryRequest (Ljava/lang/Boolean;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;Ljava/lang/Boolean;Laws/sdk/kotlin/services/dynamodb/model/Select;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest; - public static final fun QueryResponse (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;ILjava/util/List;Ljava/lang/Object;I)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse; + public static final fun QueryRequest (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest; + public static final fun QueryResponse (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequestBuilder; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponseBuilder; } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest { @@ -784,6 +894,27 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Que public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequestBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest; + public final fun getConsistentRead ()Ljava/lang/Boolean; + public final fun getExclusiveStartKey ()Ljava/lang/Object; + public final fun getFilterExpression ()Ljava/lang/String; + public final fun getKeyConditionExpression ()Ljava/lang/String; + public final fun getLimit ()Ljava/lang/Integer; + public final fun getReturnConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity; + public final fun getScanIndexForward ()Ljava/lang/Boolean; + public final fun getSelect ()Laws/sdk/kotlin/services/dynamodb/model/Select; + public final fun setConsistentRead (Ljava/lang/Boolean;)V + public final fun setExclusiveStartKey (Ljava/lang/Object;)V + public final fun setFilterExpression (Ljava/lang/String;)V + public final fun setKeyConditionExpression (Ljava/lang/String;)V + public final fun setLimit (Ljava/lang/Integer;)V + public final fun setReturnConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)V + public final fun setScanIndexForward (Ljava/lang/Boolean;)V + public final fun setSelect (Laws/sdk/kotlin/services/dynamodb/model/Select;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse { public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse$Companion; public abstract fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; @@ -796,9 +927,28 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Que public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponseBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryResponse; + public final fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; + public final fun getCount ()Ljava/lang/Integer; + public final fun getItems ()Ljava/util/List; + public final fun getLastEvaluatedKey ()Ljava/lang/Object; + public final fun getScannedCount ()Ljava/lang/Integer; + public final fun setConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;)V + public final fun setCount (Ljava/lang/Integer;)V + public final fun setItems (Ljava/util/List;)V + public final fun setLastEvaluatedKey (Ljava/lang/Object;)V + public final fun setScannedCount (Ljava/lang/Integer;)V +} + public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanKt { - public static final fun ScanRequest (Ljava/lang/Boolean;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Integer;Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;Ljava/lang/Integer;Laws/sdk/kotlin/services/dynamodb/model/Select;Ljava/lang/Integer;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest; - public static final fun ScanResponse (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;ILjava/util/List;Ljava/lang/Object;I)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse; + public static final fun ScanRequest (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest; + public static final fun ScanResponse (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest; + public static final fun copy (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequestBuilder; + public static final fun toBuilder (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse;)Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponseBuilder; } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest { @@ -816,6 +966,27 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Sca public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequestBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest; + public final fun getConsistentRead ()Ljava/lang/Boolean; + public final fun getExclusiveStartKey ()Ljava/lang/Object; + public final fun getFilterExpression ()Ljava/lang/String; + public final fun getLimit ()Ljava/lang/Integer; + public final fun getReturnConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity; + public final fun getSegment ()Ljava/lang/Integer; + public final fun getSelect ()Laws/sdk/kotlin/services/dynamodb/model/Select; + public final fun getTotalSegments ()Ljava/lang/Integer; + public final fun setConsistentRead (Ljava/lang/Boolean;)V + public final fun setExclusiveStartKey (Ljava/lang/Object;)V + public final fun setFilterExpression (Ljava/lang/String;)V + public final fun setLimit (Ljava/lang/Integer;)V + public final fun setReturnConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ReturnConsumedCapacity;)V + public final fun setSegment (Ljava/lang/Integer;)V + public final fun setSelect (Laws/sdk/kotlin/services/dynamodb/model/Select;)V + public final fun setTotalSegments (Ljava/lang/Integer;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse { public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse$Companion; public abstract fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; @@ -828,6 +999,21 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Sca public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse$Companion { } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponseBuilder { + public fun ()V + public final fun build ()Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanResponse; + public final fun getConsumedCapacity ()Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity; + public final fun getCount ()Ljava/lang/Integer; + public final fun getItems ()Ljava/util/List; + public final fun getLastEvaluatedKey ()Ljava/lang/Object; + public final fun getScannedCount ()Ljava/lang/Integer; + public final fun setConsumedCapacity (Laws/sdk/kotlin/services/dynamodb/model/ConsumedCapacity;)V + public final fun setCount (Ljava/lang/Integer;)V + public final fun setItems (Ljava/util/List;)V + public final fun setLastEvaluatedKey (Ljava/lang/Object;)V + public final fun setScannedCount (Ljava/lang/Integer;)V +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations : aws/sdk/kotlin/hll/dynamodbmapper/operations/ItemSourceOperations { public abstract fun deleteItem (Laws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getItem (Laws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -836,6 +1022,16 @@ public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/operations/Tab public abstract fun scan (Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class aws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperationsKt { + public static final fun deleteItem (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getItem (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun putItem (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/QueryRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun queryPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Laws/sdk/kotlin/hll/dynamodbmapper/operations/ScanRequest;)Lkotlinx/coroutines/flow/Flow; + public static final fun scanPaginated (Laws/sdk/kotlin/hll/dynamodbmapper/operations/TableOperations;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; +} + public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/pipeline/DeserializeInput { public abstract fun getDeserializeSchema ()Laws/sdk/kotlin/hll/dynamodbmapper/items/ItemSchema; public abstract fun getLowLevelResponse ()Ljava/lang/Object; diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/annotations/ManualPagination.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/annotations/ManualPagination.kt new file mode 100644 index 00000000000..dad2142839d --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/annotations/ManualPagination.kt @@ -0,0 +1,12 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.annotations + +/** + * Indicates that an operation invocation method (e.g., `query`) does not provide automatic pagination. While desirable + * in advanced scenarios, the typical use case should favor the paginated equivalent (e.g., `queryPaginated`). + */ +@RequiresOptIn(message = "This method does not provide automatic pagination over results and should only be used in advanced scenarios. Where possible, consider using the paginated equivalent. To explicitly opt into using this method, annotate the call site with `@OptIn(ManualPagination::class)`.") +public annotation class ManualPagination(val paginatedEquivalent: String) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemTest.kt index 28f3aa121f2..8c56a0d6eb2 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemTest.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/DeleteItemTest.kt @@ -47,15 +47,11 @@ class DeleteItemTest : DdbLocalTest() { val mapper = mapper() val table = mapper.getTable(TABLE_NAME, schema) - val resp = table.deleteItem( - DeleteItemRequest( - Item(id = "foo"), - ReturnConsumedCapacity.Indexes, - null, - ReturnValue.AllOld, - null, - ), - ) + val resp = table.deleteItem { + key = Item(id = "foo") + returnConsumedCapacity = ReturnConsumedCapacity.Indexes + returnValues = ReturnValue.AllOld + } val item = assertNotNull(resp.attributes) assertEquals("foo", item.id) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemTest.kt index fdacaa11c52..fff349cb2b9 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemTest.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemTest.kt @@ -71,13 +71,10 @@ class GetItemTest : DdbLocalTest() { action: (GetItemResponse) -> Unit, ) = runTest { keys.forEach { key -> - val response = table.getItem( - GetItemRequest( - null, - key, - returnConsumedCapacity, - ), - ) + val response = table.getItem { + this.key = key + this.returnConsumedCapacity = returnConsumedCapacity + } action(response) } diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemTest.kt index de47b760aca..5c9122f221c 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemTest.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/PutItemTest.kt @@ -41,15 +41,7 @@ class PutItemTest : DdbLocalTest() { val mapper = mapper() val table = mapper.getTable(TABLE_NAME, schema) - table.putItem( - PutItemRequest( - Item(id = "foo", value = 42), - null, - null, - null, - null, - ), - ) + table.putItem { item = Item(id = "foo", value = 42) } val resp = ddb.getItem(TABLE_NAME, "id" to "foo") diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryTest.kt index 525f9843aa1..0ffd41e81db 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryTest.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/QueryTest.kt @@ -9,18 +9,17 @@ import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema import aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter import aws.sdk.kotlin.hll.dynamodbmapper.model.itemOf -import aws.sdk.kotlin.hll.dynamodbmapper.model.toItem import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.Interceptor import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.LReqContext import aws.sdk.kotlin.hll.dynamodbmapper.testutils.DdbLocalTest import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import kotlin.test.assertContentEquals -import kotlin.test.assertNotNull import aws.sdk.kotlin.services.dynamodb.model.QueryRequest as LowLevelQueryRequest -// FIXME This whole test class is temporary because Query/Scan don't yet have pagination, object mapping, or conditions +// FIXME Much of this test class is temporary because Query/Scan don't yet have conditions class QueryTest : DdbLocalTest() { companion object { @@ -113,46 +112,35 @@ class QueryTest : DdbLocalTest() { val mapper = mapper { interceptors += ExpressionAttributeInterceptor("c" to "foo-corp") } val table = mapper.getTable(TABLE_NAME, namedEmpSchema) - val result = table.query( - QueryRequest( - null, - null, - null, - """companyId = :c""", // FIXME ugly hack until conditions are implemented - null, - null, - null, - null, + val items = table.queryPaginated { + keyConditionExpression = """companyId = :c""" // FIXME ugly hack until conditions are implemented + }.items().toList() + + val expected = listOf( + NamedEmp( + companyId = "foo-corp", + empId = "AB0123", + name = "Alice Birch", + title = "SDE", + tenureYears = 5, ), - ) - - val items = assertNotNull(result.items) - - val expected = listOf( // FIXME query/scan don't support object mapping yet - itemOf( - "companyId" to "foo-corp", - "empId" to "AB0123", - "name" to "Alice Birch", - "title" to "SDE", - "tenureYears" to 5, + NamedEmp( + companyId = "foo-corp", + empId = "AB0126", + name = "Adriana Beech", + title = "Manager", + tenureYears = 7, ), - itemOf( - "companyId" to "foo-corp", - "empId" to "AB0126", - "name" to "Adriana Beech", - "title" to "Manager", - "tenureYears" to 7, - ), - itemOf( - "companyId" to "foo-corp", - "empId" to "EF0124", - "name" to "Eddie Fraser", - "title" to "SDE", - "tenureYears" to 3, + NamedEmp( + companyId = "foo-corp", + empId = "EF0124", + name = "Eddie Fraser", + title = "SDE", + tenureYears = 3, ), ) - assertContentEquals(expected, items.map { it.toItem() }) + assertContentEquals(expected, items) } @Test @@ -161,37 +149,26 @@ class QueryTest : DdbLocalTest() { val table = mapper.getTable(TABLE_NAME, namedEmpSchema) val index = table.getIndex(TITLE_INDEX_NAME, titleSchema) - val result = index.query( - QueryRequest( - null, - null, - null, - """title = :t""", // FIXME ugly hack until conditions are implemented - null, - null, - null, - null, - ), - ) + val items = index.queryPaginated { + keyConditionExpression = """title = :t""" // FIXME ugly hack until conditions are implemented + }.items().toList() - val items = assertNotNull(result.items) - - val expected = listOf( // FIXME query/scan don't support object mapping yet - itemOf( - "companyId" to "foo-corp", - "empId" to "AB0126", - "name" to "Adriana Beech", - "title" to "Manager", + val expected = listOf( + TitleEmp( + companyId = "foo-corp", + empId = "AB0126", + name = "Adriana Beech", + title = "Manager", ), - itemOf( - "companyId" to "bar-corp", - "empId" to "157X", - "name" to "Charlie Douglas", - "title" to "Manager", + TitleEmp( + companyId = "bar-corp", + empId = "157X", + name = "Charlie Douglas", + title = "Manager", ), ) - assertContentEquals(expected, items.map { it.toItem() }) + assertContentEquals(expected, items) } @Test @@ -200,46 +177,35 @@ class QueryTest : DdbLocalTest() { val table = mapper.getTable(TABLE_NAME, namedEmpSchema) val index = table.getIndex(NAME_INDEX_NAME, empsByNameSchema) - val result = index.query( - QueryRequest( - null, - null, - null, - """companyId = :c""", // FIXME ugly hack until conditions are implemented - null, - null, - null, - null, - ), - ) - - val items = assertNotNull(result.items) - - val expected = listOf( // FIXME query/scan don't support object mapping yet - itemOf( - "companyId" to "foo-corp", - "empId" to "AB0126", - "name" to "Adriana Beech", - "title" to "Manager", - "tenureYears" to 7, + val items = index.queryPaginated { + keyConditionExpression = """companyId = :c""" // FIXME ugly hack until conditions are implemented + }.items().toList() + + val expected = listOf( + NamedEmp( + companyId = "foo-corp", + empId = "AB0126", + name = "Adriana Beech", + title = "Manager", + tenureYears = 7, ), - itemOf( - "companyId" to "foo-corp", - "empId" to "AB0123", - "name" to "Alice Birch", - "title" to "SDE", - "tenureYears" to 5, + NamedEmp( + companyId = "foo-corp", + empId = "AB0123", + name = "Alice Birch", + title = "SDE", + tenureYears = 5, ), - itemOf( - "companyId" to "foo-corp", - "empId" to "EF0124", - "name" to "Eddie Fraser", - "title" to "SDE", - "tenureYears" to 3, + NamedEmp( + companyId = "foo-corp", + empId = "EF0124", + name = "Eddie Fraser", + title = "SDE", + tenureYears = 3, ), ) - assertContentEquals(expected, items.map { it.toItem() }) + assertContentEquals(expected, items) } } diff --git a/hll/hll-codegen/build.gradle.kts b/hll/hll-codegen/build.gradle.kts index 97e17ea1d8e..4d3aee94ed8 100644 --- a/hll/hll-codegen/build.gradle.kts +++ b/hll/hll-codegen/build.gradle.kts @@ -12,7 +12,22 @@ plugins { `maven-publish` } +val optinAnnotations = listOf( + "aws.smithy.kotlin.runtime.InternalApi", + "aws.sdk.kotlin.runtime.InternalSdkApi", + "kotlin.RequiresOptIn", +) + +kotlin { + explicitApi() + + sourceSets.all { + optinAnnotations.forEach(languageSettings::optIn) + } +} + dependencies { + api(project(":aws-runtime:aws-core")) implementation(libs.ksp.api) implementation(libs.smithy.kotlin.runtime.core) diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGenerator.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGenerator.kt index 346599e6c73..b34295a00d4 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGenerator.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGenerator.kt @@ -4,6 +4,8 @@ */ package aws.sdk.kotlin.hll.codegen.core +import aws.sdk.kotlin.runtime.InternalSdkApi + private const val INDENT = " " /** @@ -25,17 +27,18 @@ private const val INDENT = " " * [dedent]. The indentation level is also adjusted by **block** methods (e.g., [openBlock], [withBlock], etc.), which * automatically indent/dedent around logical blocks of code. */ -interface CodeGenerator { +@InternalSdkApi +public interface CodeGenerator { /** * The import directives for the current context */ - val imports: ImportDirectives + public val imports: ImportDirectives /** * Append a blank line if there isn't already one in the buffer (i.e., invoking this method multiple times * sequentially will append _only one_ blank line). */ - fun blankLine() + public fun blankLine() /** * Close a manually-opened block of code by dedenting and appending some finalizing text @@ -43,7 +46,7 @@ interface CodeGenerator { * @param args The arguments to substitute into the [template] (if any). See [TemplateEngine] for more details on * string templates and argument substitution. */ - fun closeBlock(template: String, vararg args: Any) + public fun closeBlock(template: String, vararg args: Any) /** * Close a manually-opened block and open a new one by dedenting, appending some intermediate text, and then @@ -53,19 +56,19 @@ interface CodeGenerator { * @param args The arguments to substitute into the [template] (if any). See [TemplateEngine] for more details on * string templates and argument substitution. */ - fun closeAndOpenBlock(template: String, vararg args: Any) + public fun closeAndOpenBlock(template: String, vararg args: Any) /** * Decreases the active indentation level * @param levels The number of levels to decrement. Defaults to `1` if unspecified. */ - fun dedent(levels: Int = 1) + public fun dedent(levels: Int = 1) /** * Increases the active indentation level * @param levels The number of levels to increment. Defaults to `1` if unspecified. */ - fun indent(levels: Int = 1) + public fun indent(levels: Int = 1) /** * Open a block of code manually by appending some starting text and then indenting @@ -73,12 +76,12 @@ interface CodeGenerator { * @param args The arguments to substitute into the [template] (if any). See [TemplateEngine] for more details on * string templates and argument substitution. */ - fun openBlock(template: String, vararg args: Any) + public fun openBlock(template: String, vararg args: Any) /** * Sends the accumulated text of this generator to the backing buffer (e.g., writes it to a file) */ - fun persist() + public fun persist() /** * Writes a logical block of text by appending some starting text, indenting, executing the [block] function which @@ -92,7 +95,7 @@ interface CodeGenerator { * This function typically writes more code, which will inherit the active indentation level which will have been * incremented by `1` by this method. */ - fun withBlock(preTemplate: String, postText: String, vararg args: Any, block: () -> Unit) + public fun withBlock(preTemplate: String, postText: String, vararg args: Any, block: () -> Unit) /** * Writes a block of documentation by automatically prepending KDoc-style comment tokens as prefixes. This method @@ -103,14 +106,14 @@ interface CodeGenerator { * token. This function typically writes more code, which will inherit the active indentation prefix and be rendered * as a KDoc-style comment. */ - fun withDocs(block: () -> Unit) + public fun withDocs(block: () -> Unit) /** * Writes a single line of documentation, wrapping it with KDoc-style comment tokens. * @param template The templated string of documentation to write * @param args The arguments to the templated string, if any */ - fun docs(template: String, vararg args: Any) + public fun docs(template: String, vararg args: Any) /** * Writes a line of text, including a terminating newline (i.e., `\n`) @@ -118,7 +121,7 @@ interface CodeGenerator { * @param args The arguments to substitute into the [template] (if any). See [TemplateEngine] for more details on * string templates and argument substitution. */ - fun write(template: String, vararg args: Any) + public fun write(template: String, vararg args: Any) /** * Writes a string of text, _not_ including a terminating newline. Invoking this method repeatedly in sequence @@ -129,7 +132,7 @@ interface CodeGenerator { * @param args The arguments to substitute into the [template] (if any). See [TemplateEngine] for more details on * string templates and argument substitution. */ - fun writeInline(template: String, vararg args: Any) + public fun writeInline(template: String, vararg args: Any) } /** @@ -153,7 +156,7 @@ interface CodeGenerator { * @param persistCallback A callback method to invoke when the [persist] method is called on this class * @param imports The import directives for the generator. These may be appended by more lines being written. */ -class CodeGeneratorImpl( +internal class CodeGeneratorImpl( private val pkg: String, private val engine: TemplateEngine, private val persistCallback: (String) -> Unit, @@ -168,6 +171,8 @@ class CodeGeneratorImpl( } override fun closeBlock(template: String, vararg args: Any) { + if (builder.endsWith("\n\n")) builder.deleteAt(builder.length - 1) + dedent() write(template, *args) } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGeneratorFactory.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGeneratorFactory.kt index 545f02ecce0..3f16deea368 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGeneratorFactory.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/CodeGeneratorFactory.kt @@ -4,6 +4,7 @@ */ package aws.sdk.kotlin.hll.codegen.core +import aws.sdk.kotlin.runtime.InternalSdkApi import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.CodeGenerator as KSCodeGenerator @@ -13,7 +14,8 @@ import com.google.devtools.ksp.processing.CodeGenerator as KSCodeGenerator * @param ksCodeGenerator The underlying KSP [KSCodeGenerator] to use for low-level file access and dependency tracking * @param logger A logger instance to use for message */ -class CodeGeneratorFactory( +@InternalSdkApi +public class CodeGeneratorFactory( private val ksCodeGenerator: KSCodeGenerator, private val logger: KSPLogger, private val dependencies: Dependencies = Dependencies.ALL_FILES, @@ -26,7 +28,7 @@ class CodeGeneratorFactory( * @param pkg The Kotlin package for the generated code (e.g., `aws.sdk.kotlin.hll.dynamodbmapper.operations`) * @param codeGeneratorName The name of this [CodeGenerator] */ - fun generator(fileName: String, pkg: String, codeGeneratorName: String): CodeGenerator { + public fun generator(fileName: String, pkg: String, codeGeneratorName: String): CodeGenerator { val imports = ImportDirectives() val processors = listOf( TemplateProcessor.Literal, diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/ImportDirectives.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/ImportDirectives.kt index cfc0e96b76b..645ac37d78e 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/ImportDirectives.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/ImportDirectives.kt @@ -5,12 +5,14 @@ package aws.sdk.kotlin.hll.codegen.core import aws.sdk.kotlin.hll.codegen.model.TypeRef +import aws.sdk.kotlin.runtime.InternalSdkApi /** * A mutable collection of [ImportDirectives] for eventually writing to a code generator */ -class ImportDirectives : MutableSet by mutableSetOf() { - operator fun get(shortName: String) = firstOrNull { it.shortName == shortName } +@InternalSdkApi +public class ImportDirectives : MutableSet by mutableSetOf() { + public operator fun get(shortName: String): ImportDirective? = firstOrNull { it.shortName == shortName } /** * Returns a formatted code string with each import on a dedicated line. Imports will be sorted with the following @@ -19,7 +21,7 @@ class ImportDirectives : MutableSet by mutableSetOf() { * 2. The special package prefixes `java`, `javax`, `kotlin` after all other imports * 3. Lexicographically sorted */ - val formatted: String + public val formatted: String get() = buildString { sortedWith(importComparator).forEach { appendLine(it.formatted) } } @@ -41,19 +43,22 @@ private val importComparator = compareBy { it.alias != null } / * @param alias An optional alias for the import (e.g., `JavaSocket`). If present, a formatted code string for this * directive will include an `as` clause (e.g., `import java.net.Socket as JavaSocket`). */ -data class ImportDirective(val fullName: String, val alias: String? = null) { +@InternalSdkApi +public data class ImportDirective(val fullName: String, val alias: String? = null) { /** * The unaliased "short name" of an import directive—namely, everything after the last `.` separator. For example, * for the full name `java.net.Socket` the short name is `Socket`. */ - val shortName = fullName.split(".").last() + public val shortName: String = fullName.split(".").last() private val aliasFormatted = alias?.let { " as $it" } ?: "" /** * The formatted `import` code string for this directive */ - val formatted = "import $fullName$aliasFormatted" + public val formatted: String = "import $fullName$aliasFormatted" } -fun ImportDirective(type: TypeRef, alias: String? = null) = ImportDirective(type.fullName, alias) +@InternalSdkApi +public fun ImportDirective(type: TypeRef, alias: String? = null): ImportDirective = + ImportDirective(type.fullName, alias) diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateEngine.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateEngine.kt index 383f6a8486b..a9f8873d8a2 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateEngine.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateEngine.kt @@ -4,6 +4,8 @@ */ package aws.sdk.kotlin.hll.codegen.core +import aws.sdk.kotlin.runtime.InternalSdkApi + /** * A string processing engine that replaces template parameters with passed arguments to form a final string. String * templates take the form of string literals containing zero or more **parameters**, which are special sequences @@ -44,7 +46,8 @@ package aws.sdk.kotlin.hll.codegen.core * template, it attempts to match the parameter **key** (an uppercase letter `[A-Z]`) to the key of a processor. If no * referenced processor has a matching key, an exception will be thrown. */ -class TemplateEngine(processors: List) { +@InternalSdkApi +public class TemplateEngine(processors: List) { private val processors = processors.associate { it.key to it.handler } private val parameterRegex = """(?) { * @param args A collection of arguments to use in parameter substitution * @return A string with all parameters replaced */ - fun process(template: String, args: List): String { + public fun process(template: String, args: List): String { var allIndexed: Boolean? = null return parameterRegex.replaceIndexed(template) { pos, result -> diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateProcessor.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateProcessor.kt index c02c39ebdcb..d91df6df871 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateProcessor.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/core/TemplateProcessor.kt @@ -7,6 +7,7 @@ package aws.sdk.kotlin.hll.codegen.core import aws.sdk.kotlin.hll.codegen.model.Type import aws.sdk.kotlin.hll.codegen.model.TypeRef import aws.sdk.kotlin.hll.codegen.util.quote +import aws.sdk.kotlin.runtime.InternalSdkApi /** * Defines a template processor which maps an argument value of any type to a string value @@ -14,8 +15,10 @@ import aws.sdk.kotlin.hll.codegen.util.quote * this processor * @param handler A function that accepts an input argument (as an [Any]) and returns a formatted string */ -data class TemplateProcessor(val key: Char, val handler: (Any) -> String) { - companion object { +@InternalSdkApi +public data class TemplateProcessor(val key: Char, val handler: (Any) -> String) { + @InternalSdkApi + public companion object { /** * Instantiate a new typed template processor which only receives arguments of a specific type [T] * @param T The type of argument values this processor will accept @@ -23,22 +26,23 @@ data class TemplateProcessor(val key: Char, val handler: (Any) -> String) { * with this processor * @param handler A function that accepts an input argument of type [T] and returns a formatted string */ - inline fun typed(key: Char, crossinline handler: (T) -> String) = TemplateProcessor(key) { value -> - require(value is T) { "Expected argument of type ${T::class} but found $value" } - handler(value) - } + public inline fun typed(key: Char, crossinline handler: (T) -> String): TemplateProcessor = + TemplateProcessor(key) { value -> + require(value is T) { "Expected argument of type ${T::class} but found $value" } + handler(value) + } /** * A literal template processor. This processor substitutes parameters in the form of `#L` with the [toString] * representation of the corresponding argument. */ - val Literal = TemplateProcessor('L') { it.toString() } + public val Literal: TemplateProcessor = TemplateProcessor('L') { it.toString() } /** * A quoted string template processor. This processor substitutes parameters in the form of `#S` with the * quoted/escaped form of a string argument. See [quote] for more details. */ - val QuotedString = typed('S') { it.quote() } + public val QuotedString: TemplateProcessor = typed('S') { it.quote() } /** * Creates a template processor for [Type] values. This processor substitutes parameters in the form of `#T` @@ -47,7 +51,7 @@ data class TemplateProcessor(val key: Char, val handler: (Any) -> String) { * a passed argument has the same package as this processor. * @param imports An [ImportDirectives] collection to which new imports will be appended */ - fun forType(pkg: String, imports: ImportDirectives): TemplateProcessor { + public fun forType(pkg: String, imports: ImportDirectives): TemplateProcessor { val processor = ImportingTypeProcessor(pkg, imports) return typed('T', processor::format) } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/ksp/processors/HllKspProcessor.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/ksp/processors/HllKspProcessor.kt new file mode 100644 index 00000000000..a9ac3acc51c --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/ksp/processors/HllKspProcessor.kt @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.ksp.processors + +import aws.sdk.kotlin.hll.codegen.util.KspLoggerOutputStream +import aws.sdk.kotlin.hll.codegen.util.asPrintStream +import aws.sdk.kotlin.runtime.InternalSdkApi +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated +import java.io.PrintStream + +/** + * An abstract implementation for all KSP symbol processors used in high-level libraries + */ +@InternalSdkApi +public abstract class HllKspProcessor(environment: SymbolProcessorEnvironment) : SymbolProcessor { + private var invoked = false + private val logger = environment.logger + private var originalStdOut: PrintStream? = null + + final override fun process(resolver: Resolver): List { + if (invoked) { + logger.info("${this::class.simpleName} has already run once; skipping subsequent processing rounds") + return listOf() + } else { + invoked = true + } + + if (originalStdOut == null) { + val loggerOutStream = KspLoggerOutputStream(logger) + System.setOut(loggerOutStream.asPrintStream()) + } + + return processImpl(resolver) + } + + protected abstract fun processImpl(resolver: Resolver): List + + final override fun finish() { + originalStdOut?.let(System::setOut) + } +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Member.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Member.kt index eba226b8f61..38e824e786c 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Member.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Member.kt @@ -4,6 +4,7 @@ */ package aws.sdk.kotlin.hll.codegen.model +import aws.sdk.kotlin.runtime.InternalSdkApi import com.google.devtools.ksp.symbol.KSPropertyDeclaration /** @@ -11,15 +12,21 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration * @param name The name of the member inside its parent [Structure] * @param type The [Type] of the member */ -data class Member(val name: String, val type: Type, val mutable: Boolean = false) { - companion object { +@InternalSdkApi +public data class Member(val name: String, val type: Type, val mutable: Boolean = false) { + @InternalSdkApi + public companion object { /** * Derive a [Member] from a [KSPropertyDeclaration] */ - fun from(prop: KSPropertyDeclaration) = Member( - name = prop.simpleName.getShortName(), - type = Type.from(prop.type), - mutable = prop.isMutable, - ) + public fun from(prop: KSPropertyDeclaration): Member { + val member = Member( + name = prop.simpleName.getShortName(), + type = Type.from(prop.type), + mutable = prop.isMutable, + ) + + return ModelParsingPlugin.transform(member, ModelParsingPlugin::postProcessMember) + } } } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/ModelParsingPlugin.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/ModelParsingPlugin.kt new file mode 100644 index 00000000000..2ccaf77633b --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/ModelParsingPlugin.kt @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.model + +import aws.sdk.kotlin.runtime.InternalSdkApi +import java.util.ServiceLoader + +/** + * Represents a plugin to the model parsing phase of code generation. Plugins have the opportunity to customize how + * models are parsed or customized. Implementations of this interface are loaded by + * [SPI](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html). + */ +@InternalSdkApi +public interface ModelParsingPlugin { + @InternalSdkApi + public companion object { + /** + * Gets the list of plugins available on the classpath. Note that this list is loaded only once and doesn't + * refresh. + */ + public val instances: List by lazy { + val classLoader = ModelParsingPlugin::class.java.classLoader + ServiceLoader.load(ModelParsingPlugin::class.java, classLoader).toList() + } + + /** + * Executes a series of transformations from all loaded plugin instances by accepting an input [shape] and + * folding over the [processor] reference from each plugin. Plugin processors are executed in the order they + * were loaded, as visible in [instances]. + * @param S The type of shape this operation will transform + * @param shape The initial input shape + * @param processor A function which accepts a [ModelParsingPlugin] and an [S] shape which will be run for each + * plugin + */ + @InternalSdkApi + public fun transform(shape: S, processor: (ModelParsingPlugin, S) -> S): S = + instances.fold(shape) { prev, plugin -> processor(plugin, prev) } + } + + /** + * Perform some processing of an [Operation] after it's been parsed by the base codegen layer. Implementors should + * return a modified version of [operation] if changes are required or the exact same [operation] if no changes are + * necessary. + * @param operation The [Operation] to potentially modify + * @return A modified version of [operation] if changes are required or the exact same [operation] if no changes are + * necessary. + */ + public fun postProcessOperation(operation: Operation): Operation = operation + + /** + * Perform some processing of a [Structure] after it's been parsed by the base codegen layer. Implementors should + * return a modified version of [struct] if changes are required or the exact same [struct] if no changes are + * necessary. + * @param struct The [Structure] to potentially modify + * @return A modified version of [struct] if changes are required or the exact same [struct] if no changes are + * necessary. + */ + public fun postProcessStructure(struct: Structure): Structure = struct + + /** + * Perform some processing of a [Member] after it's been parsed by the base codegen layer. Implementors should + * return a modified version of [member] if changes are required or the exact same [member] if no changes are + * necessary. + * @param member The [Member] to potentially modify + * @return A modified version of [member] if changes are required or the exact same [member] if no changes are + * necessary. + */ + public fun postProcessMember(member: Member): Member = member +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Operation.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Operation.kt new file mode 100644 index 00000000000..29452620005 --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Operation.kt @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.model + +import aws.sdk.kotlin.hll.codegen.util.capitalizeFirstChar +import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes +import com.google.devtools.ksp.symbol.KSFunctionDeclaration + +/** + * Describes a service operation (i.e., API method) + * @param methodName The name of the operation as a code-suitable method name. For example, `getItem` is a suitable + * method name in Kotlin, whereas `GetItem` is not (improperly cased) nor is `get item` (contains a space). + * @param request The [Structure] for requests/inputs to this operation + * @param response The [Structure] for responses/output from this operation + * @param attributes An [Attributes] collection for associating typed attributes with this operation + */ +@InternalSdkApi +public data class Operation( + val methodName: String, + val request: Structure, + val response: Structure, + val attributes: Attributes = emptyAttributes(), +) { + /** + * The capitalized name of this operation's [methodName]. For example, if [methodName] is `getItem` then [name] + * would be `GetItem`. + */ + public val name: String = methodName.capitalizeFirstChar // e.g., "GetItem" vs "getItem" + + @InternalSdkApi + public companion object { + /** + * Derive an [Operation] from a [KSFunctionDeclaration] + */ + public fun from(declaration: KSFunctionDeclaration): Operation { + val op = Operation( + methodName = declaration.simpleName.getShortName(), + request = Structure.from(declaration.parameters.single().type), + response = Structure.from(declaration.returnType!!), + ) + + return ModelParsingPlugin.transform(op, ModelParsingPlugin::postProcessOperation) + } + } +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Pkg.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Pkg.kt new file mode 100644 index 00000000000..43895f81d00 --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Pkg.kt @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.model + +import aws.sdk.kotlin.runtime.InternalSdkApi + +/** + * Named constants for various mapper runtime packages + */ +@InternalSdkApi +public object Pkg { + @InternalSdkApi + public object Kotlin { + public val Base: String = "kotlin" + public val Collections: String = "$Base.collections" + public val Jvm: String = "$Base.jvm" + } + + @InternalSdkApi + public object Kotlinx { + public val Base: String = "kotlinx" + + @InternalSdkApi + public object Coroutines { + public val Base: String = "${Kotlinx.Base}.coroutines" + public val Flow: String = "$Base.flow" + } + } +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Structure.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Structure.kt new file mode 100644 index 00000000000..13e596d6ea0 --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Structure.kt @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.model + +import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.emptyAttributes +import com.google.devtools.ksp.getDeclaredProperties +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSTypeReference + +/** + * Describes a structure (i.e., class, struct, etc.) which contains zero or more [Member] instances + * @param type The [TypeRef] for this structure, which includes its name and Kotlin package + * @param members The [Member] instances which are part of this structure + * @param attributes An [Attributes] collection for associating typed attributes with this structure + */ +@InternalSdkApi +public data class Structure( + val type: TypeRef, + val members: Set, + val attributes: Attributes = emptyAttributes(), +) { + @InternalSdkApi + public companion object { + /** + * Derives a [Structure] from the given [KSTypeReference] + */ + public fun from(ksTypeRef: KSTypeReference): Structure { + val struct = Structure( + type = Type.from(ksTypeRef), + members = (ksTypeRef.resolve().declaration as KSClassDeclaration) + .getDeclaredProperties() + .map(Member.Companion::from) + .toSet(), + ) + + return ModelParsingPlugin.transform(struct, ModelParsingPlugin::postProcessStructure) + } + } +} + +/** + * Gets a collection of all generic variables referenced by/in a [Structure], including in the structure's [TypeRef] and + * in the [TypeRef]s of every member + */ +@InternalSdkApi +public fun Structure.genericVars(): List = buildList { + addAll(type.genericVars()) + members.flatMap { it.type.genericVars() }.let(::addAll) +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Type.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Type.kt index 3ae0086b3ef..b86839791fa 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Type.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Type.kt @@ -4,6 +4,8 @@ */ package aws.sdk.kotlin.hll.codegen.model +import aws.sdk.kotlin.hll.codegen.util.requireAllDistinct +import aws.sdk.kotlin.runtime.InternalSdkApi import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSTypeReference @@ -11,22 +13,24 @@ import com.google.devtools.ksp.symbol.KSTypeReference /** * Describes a Kotlin data type */ -sealed interface Type { - companion object { +@InternalSdkApi +public sealed interface Type { + @InternalSdkApi + public companion object { /** * Derives a [TypeRef] from a [KSClassDeclaration] */ - fun from(ksClassDeclaration: KSClassDeclaration): TypeRef = from(ksClassDeclaration.asStarProjectedType()) + public fun from(ksClassDecl: KSClassDeclaration): TypeRef = from(ksClassDecl.asStarProjectedType()) /** * Derives a [TypeRef] from a [KSTypeReference] */ - fun from(ksTypeRef: KSTypeReference): TypeRef = from(ksTypeRef.resolve()) + public fun from(ksTypeRef: KSTypeReference): TypeRef = from(ksTypeRef.resolve()) /** * Derives a [TypeRef] from a [KSType] */ - fun from(ksType: KSType): TypeRef { + public fun from(ksType: KSType): TypeRef { val name = ksType.declaration.qualifiedName!! return TypeRef( pkg = name.getQualifier(), @@ -40,12 +44,12 @@ sealed interface Type { /** * Gets the short name (i.e., not including the Kotlin package) for this type */ - val shortName: String + public val shortName: String /** * Indicates whether instances of this type allow nullable references */ - val nullable: Boolean + public val nullable: Boolean } /** @@ -55,11 +59,12 @@ sealed interface Type { * representing [kotlin.collections.List] would have a single generic argument, which may either be a concrete [TypeRef] * itself (e.g., `List`) or a generic [TypeVar] (e.g., `List`). * @param pkg The Kotlin package for this type - * @param shortName The short name (i.e., not including the kotlin package) for this type + * @param shortName The short name (i.e., not including the Kotlin package) for this type * @param genericArgs Zero or more [Type] generic arguments to this type * @param nullable Indicates whether instances of this type allow nullable references */ -data class TypeRef( +@InternalSdkApi +public data class TypeRef( val pkg: String, override val shortName: String, val genericArgs: List = listOf(), @@ -82,20 +87,48 @@ data class TypeRef( * @param shortName The name of this type variable * @param nullable Indicates whether instances of this type allow nullable references */ -data class TypeVar(override val shortName: String, override val nullable: Boolean = false) : Type +@InternalSdkApi +public data class TypeVar(override val shortName: String, override val nullable: Boolean = false) : Type /** * Derives a nullable [Type] equivalent for this type */ -fun Type.nullable() = when { +@InternalSdkApi +public fun Type.nullable(): Type = when { nullable -> this this is TypeRef -> copy(nullable = true) this is TypeVar -> copy(nullable = true) else -> error("Unknown Type ${this::class}") // Should be unreachable, only here to make compiler happy } +/** +<<<<<<< HEAD + * Gets a collection of all generic variables referenced by this [Type] + */ +@InternalSdkApi +public fun Type.genericVars(): List = buildList { + when (val type = this@genericVars) { + is TypeVar -> add(type) + is TypeRef -> type.genericArgs.flatMap { it.genericVars() } + } +} + +/** + * Formats a collection of [TypeVar] into a Kotlin generics list (e.g., ``) + * @param postfix An optional string to include at the end of the generated string. This can be useful when the intended + * destination for the string is codegen and additional spacing may be required. + */ +@InternalSdkApi +public fun List.asParamsList(postfix: String = ""): String = + takeUnless { isEmpty() } + ?.map { it.shortName } + ?.requireAllDistinct() + ?.joinToString(", ", "<", ">$postfix") + ?: "" + /** * Returns whether this [TypeRef] is generic for an [other] * For example, List.isGenericFor(List) returns true. */ -fun TypeRef.isGenericFor(other: TypeRef): Boolean = fullName == other.fullName +@InternalSdkApi +public fun TypeRef.isGenericFor(other: TypeRef): Boolean = fullName == other.fullName diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Types.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Types.kt index 918dbadc5cb..d3cae17d13d 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Types.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/model/Types.kt @@ -1,64 +1,94 @@ package aws.sdk.kotlin.hll.codegen.model -import aws.sdk.kotlin.hll.codegen.util.Pkg +import aws.sdk.kotlin.runtime.InternalSdkApi /** * A container object for various [Type] instances */ -object Types { - object Smithy { - val Instant = TypeRef("aws.smithy.kotlin.runtime.time", "Instant") - val Url = TypeRef("aws.smithy.kotlin.runtime.net.url", "Url") - val Document = TypeRef("aws.smithy.kotlin.runtime.content", "Document") +@InternalSdkApi +public object Types { + @InternalSdkApi + public object Smithy { + public val Instant: TypeRef = TypeRef("aws.smithy.kotlin.runtime.time", "Instant") + public val Url: TypeRef = TypeRef("aws.smithy.kotlin.runtime.net.url", "Url") + public val Document: TypeRef = TypeRef("aws.smithy.kotlin.runtime.content", "Document") } - object Kotlin { - val ByteArray = kotlin("ByteArray") - val Boolean = kotlin("Boolean") - val Number = kotlin("Number") - val String = kotlin("String") - val StringNullable = String.nullable() - val Char = kotlin("Char") - val CharArray = kotlin("CharArray") - val Byte = kotlin("Byte") - val Short = kotlin("Short") - val Int = kotlin("Int") - val Long = kotlin("Long") - val Float = kotlin("Float") - val Double = kotlin("Double") - val UByte = kotlin("UByte") - val UInt = kotlin("UInt") - val ULong = kotlin("ULong") - val UShort = kotlin("UShort") + @InternalSdkApi + public object Kotlin { + public val Boolean: TypeRef = kotlin("Boolean") + public val Byte: TypeRef = kotlin("Byte") + public val ByteArray: TypeRef = kotlin("ByteArray") + public val Char: TypeRef = kotlin("Char") + public val CharArray: TypeRef = kotlin("CharArray") + public val Double: TypeRef = kotlin("Double") + public val Float: TypeRef = kotlin("Float") + public val Int: TypeRef = kotlin("Int") + public val Long: TypeRef = kotlin("Long") + public val Number: TypeRef = kotlin("Number") + public val OptIn: TypeRef = kotlin("OptIn") + public val Short: TypeRef = kotlin("Short") + public val String: TypeRef = kotlin("String") + public val StringNullable: TypeRef = String.nullable() as TypeRef + public val UByte: TypeRef = kotlin("UByte") + public val UInt: TypeRef = kotlin("UInt") + public val ULong: TypeRef = kotlin("ULong") + public val UShort: TypeRef = kotlin("UShort") - object Collections { - val Set = TypeRef(Pkg.Kotlin.Collections, "Set") - val List = TypeRef(Pkg.Kotlin.Collections, "List") - val Map = TypeRef(Pkg.Kotlin.Collections, "Map") + @InternalSdkApi + public object Collections { + public val List: TypeRef = TypeRef(Pkg.Kotlin.Collections, "List") + public val Map: TypeRef = TypeRef(Pkg.Kotlin.Collections, "Map") + public val Set: TypeRef = TypeRef(Pkg.Kotlin.Collections, "Set") } /** * Creates a [TypeRef] for a generic [List] * @param element The type of elements in the list */ - fun list(element: TypeRef) = TypeRef(Pkg.Kotlin.Collections, "List", listOf(element)) + public fun list(element: Type): TypeRef = TypeRef(Pkg.Kotlin.Collections, "List", listOf(element)) /** * Creates a [TypeRef] for a named Kotlin type (e.g., `String`) */ - fun kotlin(name: String) = TypeRef(Pkg.Kotlin.Base, name) + public fun kotlin(name: String): TypeRef = TypeRef(Pkg.Kotlin.Base, name) /** * Creates a [TypeRef] for a generic [Map] * @param key The type of keys in the map * @param value The type of values in the map */ - fun map(key: TypeRef, value: TypeRef) = TypeRef(Pkg.Kotlin.Collections, "Map", listOf(key, value)) + public fun map(key: Type, value: Type): TypeRef = TypeRef(Pkg.Kotlin.Collections, "Map", listOf(key, value)) /** * Creates a [TypeRef] for a generic [Map] with [String] keys * @param value The type of values in the map */ - fun stringMap(value: TypeRef) = map(String, value) + public fun stringMap(value: Type): TypeRef = map(String, value) + + @InternalSdkApi + public object Jvm { + public val JvmName: TypeRef = TypeRef(Pkg.Kotlin.Jvm, "JvmName") + } + } + + @InternalSdkApi + public object Kotlinx { + @InternalSdkApi + public object Coroutines { + @InternalSdkApi + public object Flow { + public val flow: TypeRef = TypeRef(Pkg.Kotlinx.Coroutines.Flow, "flow") + + /** + * Creates a [TypeRef] for a generic `Flow` + * @param element The type of elements in the flow + */ + public fun flow(element: Type): TypeRef = + TypeRef(Pkg.Kotlinx.Coroutines.Flow, "Flow", listOf(element)) + + public val transform: TypeRef = TypeRef(Pkg.Kotlinx.Coroutines.Flow, "transform") + } + } } } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/BuilderRenderer.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/BuilderRenderer.kt index 1c9fb2ab680..1508a41eb3c 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/BuilderRenderer.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/BuilderRenderer.kt @@ -1,33 +1,48 @@ package aws.sdk.kotlin.hll.codegen.rendering -import aws.sdk.kotlin.hll.codegen.model.Member -import aws.sdk.kotlin.hll.codegen.model.TypeRef +import aws.sdk.kotlin.hll.codegen.core.CodeGenerator +import aws.sdk.kotlin.hll.codegen.model.* import aws.sdk.kotlin.hll.codegen.util.visibility +import aws.sdk.kotlin.runtime.InternalSdkApi /** * A DSL-style builder renderer. - * @param renderer The base renderer in which the builder will be written - * @param classType The [TypeRef] representing the class for which a builder will be generated - * @param members The [Set] of members of [classType] which will be included in the builder + * @param generator The generator in which the builder will be written + * @param builtType The [TypeRef] representing the type for which a builder will be generated. This type can be a class + * or an interface. + * @param implementationType The [TypeRef] representing the implementing type whose constructor will be called by the + * generated `build` method. This type must expose a constructor which accepts each element of [members] as parameters. + * Note that this type doesn't have to be public (merely accessible to the `build` method) and may be the same as + * [builtType] if it has an appropriate constructor. + * @param members The [Set] of members of [builtType] which will be included in the builder + * @param ctx The rendering context */ -class BuilderRenderer( - private val renderer: RendererBase, - private val classType: TypeRef, +@InternalSdkApi +public class BuilderRenderer( + private val generator: CodeGenerator, + private val builtType: TypeRef, + private val implementationType: TypeRef, private val members: Set, private val ctx: RenderContext, -) { - private val className = classType.shortName +) : CodeGenerator by generator { + @InternalSdkApi + public companion object { + public fun builderName(builtType: TypeRef): String = "${builtType.shortName}Builder" + public fun builderType(builtType: TypeRef): TypeRef = builtType.copy(shortName = builderName(builtType)) + } - fun render() = renderer.apply { - docs("A DSL-style builder for instances of [#T]", classType) + private val builderName = builderName(builtType) - withBlock("#Lclass #L {", "}", ctx.attributes.visibility, "${className}Builder") { - members.forEach { - write("#Lvar #L: #T? = null", ctx.attributes.visibility, it.name, it.type) - } + public fun render() { + docs("A DSL-style builder for instances of [#T]", builtType) + + val genericParams = members.flatMap { it.type.genericVars() }.asParamsList() + + withBlock("#Lclass #L#L {", "}", ctx.attributes.visibility, builderName, genericParams) { + members.forEach(::renderProperty) blankLine() - withBlock("#Lfun build(): #T {", "}", ctx.attributes.visibility, classType) { + withBlock("#Lfun build(): #T {", "}", ctx.attributes.visibility, builtType, genericParams) { members.forEach { if (it.type.nullable) { write("val #1L = #1L", it.name) @@ -36,7 +51,7 @@ class BuilderRenderer( } } blankLine() - withBlock("return #T(", ")", classType) { + withBlock("return #T(", ")", implementationType) { members.forEach { write("#L,", it.name) } @@ -45,4 +60,9 @@ class BuilderRenderer( } blankLine() } + + private fun renderProperty(member: Member) = generator.apply { + write("#Lvar #L: #T = null", ctx.attributes.visibility, member.name, member.type.nullable()) + // TODO add DSL methods for structure members + } } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderContext.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderContext.kt index 19eb5bd7eda..eaae066a5e1 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderContext.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderContext.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.hll.codegen.rendering import aws.sdk.kotlin.hll.codegen.core.CodeGeneratorFactory +import aws.sdk.kotlin.runtime.InternalSdkApi import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.collections.emptyAttributes import com.google.devtools.ksp.processing.KSPLogger @@ -17,7 +18,8 @@ import com.google.devtools.ksp.symbol.KSNode * @param codegenFactory A factory that creates code generator instances for specific files * @param pkg The Kotlin package for the generated code (e.g., `aws.sdk.kotlin.hll.dynamodbmapper.operations`) */ -data class RenderContext( +@InternalSdkApi +public data class RenderContext( val logger: KSPLogger, val codegenFactory: CodeGeneratorFactory, val pkg: String, @@ -25,9 +27,9 @@ data class RenderContext( val attributes: Attributes = emptyAttributes(), ) -fun RenderContext.logging(message: String, symbol: KSNode? = null) = logger.logging(message, symbol) -fun RenderContext.info(message: String, symbol: KSNode? = null) = logger.info(message, symbol) -fun RenderContext.warn(message: String, symbol: KSNode? = null) = logger.warn(message, symbol) -fun RenderContext.error(message: String, symbol: KSNode? = null) = logger.error(message, symbol) +public fun RenderContext.logging(message: String, symbol: KSNode? = null): Unit = logger.logging(message, symbol) +public fun RenderContext.info(message: String, symbol: KSNode? = null): Unit = logger.info(message, symbol) +public fun RenderContext.warn(message: String, symbol: KSNode? = null): Unit = logger.warn(message, symbol) +public fun RenderContext.error(message: String, symbol: KSNode? = null): Unit = logger.error(message, symbol) -fun RenderContext.exception(e: Throwable) = logger.exception(e) +public fun RenderContext.exception(e: Throwable): Unit = logger.exception(e) diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderOptions.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderOptions.kt index 0886c0dff88..514988feee5 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderOptions.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RenderOptions.kt @@ -1,7 +1,6 @@ package aws.sdk.kotlin.hll.codegen.rendering import aws.smithy.kotlin.runtime.collections.AttributeKey -import aws.smithy.kotlin.runtime.collections.get public object RenderOptions { diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RendererBase.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RendererBase.kt index 566f278f9c4..a33cb38fb08 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RendererBase.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/rendering/RendererBase.kt @@ -5,6 +5,7 @@ package aws.sdk.kotlin.hll.codegen.rendering import aws.sdk.kotlin.hll.codegen.core.CodeGenerator +import aws.sdk.kotlin.runtime.InternalSdkApi /** * The parent class for renderers backed by a [CodeGenerator] @@ -12,14 +13,15 @@ import aws.sdk.kotlin.hll.codegen.core.CodeGenerator * @param fileName The name of the file which should be created _without_ parent directory or extension (which is always * **.kt**) */ -abstract class RendererBase( +@InternalSdkApi +public abstract class RendererBase( ctx: RenderContext, fileName: String, ) : CodeGenerator by ctx.codegenFactory.generator(fileName, ctx.pkg, ctx.rendererName) { /** * Run this renderer by calling the `abstract` [generate] method and then [persist] */ - fun render() { + public fun render() { generate() persist() } diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/AttributesExt.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/AttributesExt.kt index d9578112ebd..d639110110e 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/AttributesExt.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/AttributesExt.kt @@ -6,19 +6,22 @@ package aws.sdk.kotlin.hll.codegen.util import aws.sdk.kotlin.hll.codegen.rendering.RenderOptions.VisibilityAttribute import aws.sdk.kotlin.hll.codegen.rendering.Visibility +import aws.sdk.kotlin.runtime.InternalSdkApi import aws.smithy.kotlin.runtime.collections.* /** * Combines this [Attributes] collection with another collection and returns the new result * @param other The other attributes to merge */ -operator fun Attributes.plus(other: Attributes): Attributes = toMutableAttributes().apply { merge(other) } +@InternalSdkApi +public operator fun Attributes.plus(other: Attributes): Attributes = toMutableAttributes().apply { merge(other) } /** * Adds another attribute to this collection and returns the new result * @param other A tuple of [AttributeKey] to a value (which may be `null`) */ -operator fun Attributes.plus(other: Pair, T?>): Attributes = +@InternalSdkApi +public operator fun Attributes.plus(other: Pair, T?>): Attributes = toMutableAttributes().apply { other.second?.let { set(other.first, it) } ?: remove(other.first) } @@ -26,6 +29,7 @@ operator fun Attributes.plus(other: Pair, T?>): Attrib /** * Convert this [Attributes]' [VisibilityAttribute] to a string */ +@InternalSdkApi public val Attributes.visibility: String get() = when (this[VisibilityAttribute]) { Visibility.PUBLIC -> "public " diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/IterableExt.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/IterableExt.kt new file mode 100644 index 00000000000..f4213b69ceb --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/IterableExt.kt @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.util + +import aws.sdk.kotlin.runtime.InternalSdkApi + +/** + * Verifies that all elements in this collection are distinct. If duplicates exist, an [IllegalArgumentException] is + * thrown that details precisely which elements are duplicates. + */ +@InternalSdkApi +public fun > C.requireAllDistinct(): C { + val collection = this + val itemCounts = buildMap { + collection.forEach { element -> + compute(element) { _, existingCount -> (existingCount ?: 0) + 1 } + } + } + + val duplicates = itemCounts.filter { (_, count) -> count > 1 }.keys + require(duplicates.isEmpty()) { "Found duplicated items: $duplicates" } + + return collection +} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/KspLoggerOutputStream.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/KspLoggerOutputStream.kt new file mode 100644 index 00000000000..6d27a200b7c --- /dev/null +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/KspLoggerOutputStream.kt @@ -0,0 +1,44 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.codegen.util + +import aws.sdk.kotlin.runtime.InternalSdkApi +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.symbol.KSNode +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.io.PrintStream + +private const val NEWLINE = 0xa // \n + +/** + * Wraps a [KSPLogger] as an [OutputStream], enabling JVM-standard stream operations on the logger. All data written to + * this stream will be buffered until a newline is encountered. Once a newline is encountered, the buffered data is + * emitted to the logger (_without_ the newline) and the buffer is cleared. + * @param logger The logger instance to which data should be written + * @param logMethod The specific logger method to use on the logger. The default value is [KSPLogger.info]. + */ +@InternalSdkApi +public class KspLoggerOutputStream( + private val logger: KSPLogger, + private val logMethod: KSPLogger.(message: String, symbol: KSNode?) -> Unit = KSPLogger::info, +) : OutputStream() { + private val currentLine = ByteArrayOutputStream() + + override fun write(b: Int) { + if (b == NEWLINE) flush() else currentLine.write(b) + } + + override fun flush() { + logger.info(currentLine.toByteArray().decodeToString()) + currentLine.reset() + } +} + +/** + * Wraps a [KspLoggerOutputStream] in a [PrintStream] + */ +@InternalSdkApi +public fun KspLoggerOutputStream.asPrintStream(): PrintStream = PrintStream(this) diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Pkg.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Pkg.kt deleted file mode 100644 index 6d76bbc1885..00000000000 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Pkg.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.hll.codegen.util - -/** - * Named constants for various mapper runtime packages - */ -object Pkg { - object Hl { - val Base = "aws.sdk.kotlin.hll.dynamodbmapper" - val Items = "$Base.items" - val Model = "$Base.model" - val Ops = "$Base.operations" - val PipelineImpl = "$Base.pipeline.internal" - val Values = "$Base.values" - val ScalarValues = "$Values.scalars" - val CollectionValues = "$Values.collections" - val SmithyTypeValues = "$Values.smithytypes" - } - - object Kotlin { - val Base = "kotlin" - val Collections = "$Base.collections" - } - - object Ll { - val Base = "aws.sdk.kotlin.services.dynamodb" - val Model = "$Base.model" - } -} diff --git a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Strings.kt b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Strings.kt index 4382e390df7..4c1ee94e10c 100644 --- a/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Strings.kt +++ b/hll/hll-codegen/src/main/kotlin/aws/sdk/kotlin/hll/codegen/util/Strings.kt @@ -4,6 +4,8 @@ */ package aws.sdk.kotlin.hll.codegen.util +import aws.sdk.kotlin.runtime.InternalSdkApi + private val codepointMap = buildMap { (0..255).filter(Character::isISOControl).forEach { code -> put(code, unicodeEscape(code)) } put(8, "\\b") @@ -24,23 +26,27 @@ private fun unicodeEscape(code: Int): String { * Escape a string to one that is safe for use as a string literal by replacing various reserved characters with escape * sequences */ -fun String.escape(): String = buildString { +@InternalSdkApi +public fun String.escape(): String = buildString { this@escape.codePoints().forEach { append(codepointMap.getValue(it)) } } /** * Returns a string with the first letter capitalized (if applicable) */ -val String.capitalizeFirstChar: String +@InternalSdkApi +public val String.capitalizeFirstChar: String get() = replaceFirstChar { it.uppercaseChar() } /** * Returns a string with the first letter lowercased (if applicable) */ -val String.lowercaseFirstChar: String +@InternalSdkApi +public val String.lowercaseFirstChar: String get() = replaceFirstChar { it.lowercaseChar() } /** * Escapes and quotes a string such that it could be used in codegen */ -fun String.quote(): String = "\"${escape()}\"" +@InternalSdkApi +public fun String.quote(): String = "\"${escape()}\""