diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e04ba630b25..c3d294b091e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,8 +29,9 @@ slf4j-version = "2.0.9" [libraries] aws-kotlin-repo-tools-build-support = { module="aws.sdk.kotlin.gradle:build-support", version.ref = "aws-kotlin-repo-tools-version" } -kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-version"} -kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin-version"} +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-version" } +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin-version" } +kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin-version" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-version" } kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin-version" } dokka-core = { module = "org.jetbrains.dokka:dokka-core", version.ref = "dokka-version" } 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 e491e508341..ff89e57829a 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 @@ -79,10 +79,10 @@ class SchemaRenderer( private val AnnotatedClassProperty.valueConverter: Type get() = when (typeName.asString()) { - "aws.smithy.kotlin.runtime.time.Instant" -> MapperTypes.Values.DefaultInstantConverter - "kotlin.Boolean" -> MapperTypes.Values.BooleanConverter - "kotlin.Int" -> MapperTypes.Values.IntConverter - "kotlin.String" -> MapperTypes.Values.StringConverter + "aws.smithy.kotlin.runtime.time.Instant" -> MapperTypes.Values.SmithyTypes.DefaultInstantConverter + "kotlin.Boolean" -> MapperTypes.Values.Scalars.BooleanConverter + "kotlin.Int" -> MapperTypes.Values.Scalars.IntConverter + "kotlin.String" -> MapperTypes.Values.Scalars.StringConverter // TODO Add additional "standard" item converters else -> error("Unsupported attribute type ${typeName.asString()}") } 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 429ca345b3a..b14a864cd62 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 @@ -38,10 +38,15 @@ object MapperTypes { } object Values { - val DefaultInstantConverter = TypeRef(Pkg.Hl.Values, "InstantConverter.Default") - val BooleanConverter = TypeRef(Pkg.Hl.Values, "BooleanConverter") - val IntConverter = TypeRef(Pkg.Hl.Values, "IntConverter") - val StringConverter = TypeRef(Pkg.Hl.Values, "StringConverter") + object Scalars { + val BooleanConverter = TypeRef(Pkg.Hl.ScalarValues, "BooleanConverter") + val IntConverter = TypeRef(Pkg.Hl.ScalarValues, "IntConverter") + val StringConverter = TypeRef(Pkg.Hl.ScalarValues, "StringConverter") + } + + object SmithyTypes { + val DefaultInstantConverter = TypeRef(Pkg.Hl.SmithyTypeValues, "InstantConverters.Default") + } } object PipelineImpl { diff --git a/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api b/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api index 0795980c4f6..c344b8910f1 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api +++ b/hll/dynamodb-mapper/dynamodb-mapper/api/dynamodb-mapper.api @@ -33,14 +33,14 @@ public final class aws/sdk/kotlin/hll/dynamodbmapper/DynamoDbMapperKt { } public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/items/AttributeDescriptor { - public abstract fun getConverter ()Laws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter; + public abstract fun getConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; public abstract fun getGetter ()Lkotlin/jvm/functions/Function1; public abstract fun getName ()Ljava/lang/String; public abstract fun getSetter ()Lkotlin/jvm/functions/Function2; } public final class aws/sdk/kotlin/hll/dynamodbmapper/items/AttributeDescriptorKt { - public static final fun AttributeDescriptor (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Laws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter;)Laws/sdk/kotlin/hll/dynamodbmapper/items/AttributeDescriptor; + public static final fun AttributeDescriptor (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/dynamodbmapper/items/AttributeDescriptor; } public final class aws/sdk/kotlin/hll/dynamodbmapper/items/DocumentConverter : aws/sdk/kotlin/hll/dynamodbmapper/items/ItemConverter { @@ -468,61 +468,137 @@ public final class aws/sdk/kotlin/hll/dynamodbmapper/pipeline/SerializeInputKt { public static final fun SerializeInput (Ljava/lang/Object;Laws/sdk/kotlin/hll/dynamodbmapper/items/ItemSchema;)Laws/sdk/kotlin/hll/dynamodbmapper/pipeline/SerializeInput; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/BooleanConverter : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/BooleanConverter; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Boolean; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public fun toAv (Z)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter : aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter { + public fun (Lkotlin/reflect/KClass;)V + public fun convertFrom (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Laws/sdk/kotlin/hll/mapping/core/util/Either; + public synthetic fun convertFrom (Ljava/lang/Object;)Ljava/lang/Object; + public fun convertTo (Ljava/lang/Object;)Laws/sdk/kotlin/hll/mapping/core/util/Either; + public synthetic fun convertTo (Ljava/lang/Object;)Ljava/lang/Object; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter; - public final fun getDefault ()Laws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverterKt { + public static final fun ListConverter (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getListConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$EpochMs : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$EpochMs; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Laws/smithy/kotlin/runtime/time/Instant; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public fun toAv (Laws/smithy/kotlin/runtime/time/Instant;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverterKt { + public static final fun MapConverter (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun MapConverter (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getMapConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$EpochS : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$EpochS; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Laws/smithy/kotlin/runtime/time/Instant; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public fun toAv (Laws/smithy/kotlin/runtime/time/Instant;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConverters; + public final fun getStringListToAttributeValueNumberSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getStringSetToAttributeValueNumberSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$Iso8601 : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter$Iso8601; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Laws/smithy/kotlin/runtime/time/Instant; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public fun toAv (Laws/smithy/kotlin/runtime/time/Instant;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConvertersKt { + public static final fun getByteSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getDoubleSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getFloatSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getIntSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getLongSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getShortSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUByteSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUIntSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getULongSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUShortSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/IntConverter : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/IntConverter; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Integer; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public fun toAv (I)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/collections/PrimitiveSetConvertersKt { + public static final fun getByteArraySetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getCharArraySetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getCharSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getStringListToAttributeValueStringSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getStringSetConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public final class aws/sdk/kotlin/hll/dynamodbmapper/values/StringConverter : aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/StringConverter; - public synthetic fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/String; - public synthetic fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; - public fun toAv (Ljava/lang/String;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/BooleanConverterKt { + public static final fun getBooleanConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } -public abstract interface class aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter { - public abstract fun fromAv (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Object; - public abstract fun toAv (Ljava/lang/Object;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ByteArrayConverterKt { + public static final fun getByteArrayConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/EnumConverter : aws/sdk/kotlin/hll/mapping/core/converters/Converter { + public fun (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)V + public fun convertFrom (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Ljava/lang/Enum; + public synthetic fun convertFrom (Ljava/lang/Object;)Ljava/lang/Object; + public fun convertTo (Ljava/lang/Enum;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; + public synthetic fun convertTo (Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConverters; + public final fun getAutoNumberToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getByteToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getDoubleToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getFloatToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getIntToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getLongToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getShortToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getStringToAttributeValueNumberConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getUByteToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getUIntToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getULongToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getUShortToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConvertersKt { + public static final fun getAutoNumberConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getByteConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getDoubleConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getFloatConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getIntConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getLongConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getShortConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUByteConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUIntConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getULongConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUShortConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConverters; + public final fun getCharArrayToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getCharToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConvertersKt { + public static final fun getCharArrayConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getCharConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter : aws/sdk/kotlin/hll/mapping/core/converters/Converter { + public static final field Companion Laws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter$Companion; + public fun ()V + public fun (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)V + public synthetic fun (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun convertFrom (Laws/sdk/kotlin/services/dynamodb/model/AttributeValue;)Laws/smithy/kotlin/runtime/content/Document; + public synthetic fun convertFrom (Ljava/lang/Object;)Ljava/lang/Object; + public fun convertTo (Laws/smithy/kotlin/runtime/content/Document;)Laws/sdk/kotlin/services/dynamodb/model/AttributeValue; + public synthetic fun convertTo (Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter$Companion { + public final fun getDefault ()Laws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConverters; + public final fun getDefault ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getEpochMs ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getEpochS ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun getIso8601 ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverterKt { + public static final fun getUrlConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun getUrlToStringConverter ()Laws/sdk/kotlin/hll/mapping/core/converters/Converter; } diff --git a/hll/dynamodb-mapper/dynamodb-mapper/build.gradle.kts b/hll/dynamodb-mapper/dynamodb-mapper/build.gradle.kts index ecb3b10eb7a..0719fb632f6 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/build.gradle.kts +++ b/hll/dynamodb-mapper/dynamodb-mapper/build.gradle.kts @@ -27,12 +27,14 @@ kotlin { commonMain { dependencies { api(project(":services:dynamodb")) + api(project(":hll:hll-mapping-core")) api(libs.kotlinx.coroutines.core) } } commonTest { dependencies { + implementation(libs.kotlin.reflect) implementation(libs.kotlinx.coroutines.test) implementation(libs.kotest.assertions.core) implementation(libs.kotest.runner.junit5) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/DocumentConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/DocumentConverter.kt index 0ac9039e7d8..d1d67b66928 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/DocumentConverter.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/DocumentConverter.kt @@ -11,9 +11,10 @@ import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.content.Document import aws.smithy.kotlin.runtime.util.toNumber +// FIXME Combine with DocumentValueConverter or refactor to commonize as much code as possible public object DocumentConverter : ItemConverter { override fun fromItem(item: Item): Document = item - .mapValues { (_, attr) -> fromAv(attr) } + .mapValues { (_, attr) -> fromAttributeValue(attr) } .let(Document::Map) override fun toItem(obj: Document, onlyAttributes: Set?): Item { @@ -25,26 +26,26 @@ public object DocumentConverter : ItemConverter { obj.filterKeys { it in onlyAttributes } } - return map.mapValues { (_, value) -> toAv(value) }.toItem() + return map.mapValues { (_, value) -> toAttributeValue(value) }.toItem() } } @OptIn(InternalApi::class) -private fun fromAv(attr: AttributeValue): Document? = when (attr) { +private fun fromAttributeValue(attr: AttributeValue): Document? = when (attr) { is AttributeValue.Null -> null is AttributeValue.N -> Document.Number(attr.value.toNumber()!!) // FIXME need better toNumber logic is AttributeValue.S -> Document.String(attr.value) is AttributeValue.Bool -> Document.Boolean(attr.value) - is AttributeValue.L -> Document.List(attr.value.map(::fromAv)) - is AttributeValue.M -> Document.Map(attr.value.mapValues { (_, nestedValue) -> fromAv(nestedValue) }) + is AttributeValue.L -> Document.List(attr.value.map(::fromAttributeValue)) + is AttributeValue.M -> Document.Map(attr.value.mapValues { (_, nestedValue) -> fromAttributeValue(nestedValue) }) else -> error("Documents do not support ${attr::class.qualifiedName}") } -private fun toAv(value: Document?): AttributeValue = when (value) { +private fun toAttributeValue(value: Document?): AttributeValue = when (value) { null -> AttributeValue.Null(true) is Document.Number -> AttributeValue.N(value.value.toString()) is Document.String -> AttributeValue.S(value.value) is Document.Boolean -> AttributeValue.Bool(value.value) - is Document.List -> AttributeValue.L(value.value.map(::toAv)) - is Document.Map -> AttributeValue.M(value.mapValues { (_, nestedValue) -> toAv(nestedValue) }) + is Document.List -> AttributeValue.L(value.value.map(::toAttributeValue)) + is Document.Map -> AttributeValue.M(value.mapValues { (_, nestedValue) -> toAttributeValue(nestedValue) }) } diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/ItemConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/ItemConverter.kt index 4a8307c49aa..b12e80658e6 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/ItemConverter.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/ItemConverter.kt @@ -7,7 +7,7 @@ package aws.sdk.kotlin.hll.dynamodbmapper.items import aws.sdk.kotlin.hll.dynamodbmapper.model.Item /** - * Defines the logic for converting between objects and items + * Defines the logic for converting between objects and DynamoDB items * @param T The type of objects which will be converted */ public interface ItemConverter { diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverter.kt index 15f605a604b..5e3dbdb8ae8 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverter.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverter.kt @@ -43,16 +43,16 @@ public class SimpleItemConverter( * * ```kotlin * val descriptor = descriptors[name] // AttributeDescriptor<*, T, B> - * val value = descriptor.converter.fromAv(av) // Any? + * val value = descriptor.converter.fromAttributeValue(av) // Any? * descriptor.setter(builder, value) // Type mismatch for value. Required: Nothing, Found: Any? * ``` */ - fun AttributeDescriptor.fromAv(av: AttributeValue) = - builder.setter(converter.fromAv(av)) + fun AttributeDescriptor.fromAttributeValue(attr: AttributeValue) = + builder.setter(converter.convertFrom(attr)) - item.forEach { (name, av) -> + item.forEach { (name, attr) -> // TODO make behavior for unknown attributes configurable (ignore, exception, other?) - descriptors[name]?.fromAv(av) + descriptors[name]?.fromAttributeValue(attr) } return builder.build() @@ -66,11 +66,11 @@ public class SimpleItemConverter( * ```kotlin * val descriptor = descriptors[name] // AttributeDescriptor<*, T, B> * val value = descriptor.getter(obj) // Any? - * descriptor.converter.toAv(value) // Type mismatch for value. Required: Nothing, Found: Any? + * descriptor.converter.toAttributeValue(value) // Type mismatch for value. Required: Nothing, Found: Any? * ``` */ - fun AttributeDescriptor.toAv() = - converter.toAv(getter(obj)) + fun AttributeDescriptor.toAttributeValue() = + converter.convertTo(getter(obj)) val descriptors = if (onlyAttributes == null) { this.descriptors.values @@ -79,7 +79,7 @@ public class SimpleItemConverter( } return buildItem { - descriptors.forEach { desc -> put(desc.name, desc.toAv()) } + descriptors.forEach { desc -> put(desc.name, desc.toAttributeValue()) } } } } diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter.kt deleted file mode 100644 index 94a792f3011..00000000000 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/InstantConverter.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.hll.dynamodbmapper.values - -import aws.sdk.kotlin.services.dynamodb.model.AttributeValue -import aws.smithy.kotlin.runtime.time.Instant -import aws.smithy.kotlin.runtime.time.TimestampFormat -import aws.smithy.kotlin.runtime.time.epochMilliseconds -import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds - -public object InstantConverter { - public val Default: ValueConverter = EpochMs - - public object EpochMs : ValueConverter { - override fun fromAv(attr: AttributeValue): Instant = - Instant.fromEpochMilliseconds(attr.asN().toLong()) - - override fun toAv(value: Instant): AttributeValue = - AttributeValue.N(value.epochMilliseconds.toString()) - } - - public object EpochS : ValueConverter { - override fun fromAv(attr: AttributeValue): Instant = - Instant.fromEpochSeconds(attr.asN().toLong()) - - override fun toAv(value: Instant): AttributeValue = - AttributeValue.N(value.epochSeconds.toString()) - } - - public object Iso8601 : ValueConverter { - override fun fromAv(attr: AttributeValue): Instant = - Instant.fromIso8601(attr.asS()) - - override fun toAv(value: Instant): AttributeValue = - AttributeValue.S(value.format(TimestampFormat.ISO_8601)) - } -} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter.kt new file mode 100644 index 00000000000..89224861d22 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverter.kt @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values + +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.SplittingConverter +import aws.sdk.kotlin.hll.mapping.core.converters.mergeBy +import aws.sdk.kotlin.hll.mapping.core.util.Either +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.reflect.KClass + +/** + * Converts between potentially `null` values and + * [DynamoDB `NULL` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Null). + * Note that this class is a [SplittingConverter] and the logic for handling non-null values is undefined in this class. + * Thus, it is typically used in conjunction with the [NullableConverter] factory function or via [mergeBy]. + * @param V The non-nullable type + */ +public class NullableConverter(klass: KClass) : SplittingConverter { + override fun convertTo(from: V?): Either = when (from) { + null -> Either.Left(AttributeValue.Null(true)) + else -> Either.Right(from) + } + + override fun convertFrom(to: AttributeValue): Either = when (to) { + is AttributeValue.Null -> Either.Left(null) + else -> Either.Right(to) + } +} + +/** + * Initializes a new [NullableConverter] for the given reified type [V] + */ +public inline fun NullableConverter(): NullableConverter = NullableConverter(V::class) + +@Suppress("ktlint:standard:function-naming") +public inline fun NullableConverter( + delegate: Converter, +): Converter = NullableConverter().mergeBy(delegate) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ScalarConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ScalarConverters.kt deleted file mode 100644 index 4d9c12625ed..00000000000 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ScalarConverters.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.hll.dynamodbmapper.values - -import aws.sdk.kotlin.services.dynamodb.model.AttributeValue - -public object BooleanConverter : ValueConverter { - override fun fromAv(attr: AttributeValue): Boolean = attr.asBool() - override fun toAv(value: Boolean): AttributeValue = AttributeValue.Bool(value) -} - -public object IntConverter : ValueConverter { - override fun fromAv(attr: AttributeValue): Int = attr.asN().toInt() - override fun toAv(value: Int): AttributeValue = AttributeValue.N(value.toString()) -} - -public object StringConverter : ValueConverter { - override fun fromAv(attr: AttributeValue): String = attr.asS() - override fun toAv(value: String): AttributeValue = AttributeValue.S(value) -} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter.kt index 4ccddcf6643..ac955d89f5c 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConverter.kt @@ -4,10 +4,13 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.values +import aws.sdk.kotlin.hll.mapping.core.converters.Converter import aws.sdk.kotlin.services.dynamodb.model.AttributeValue -// TODO document, add unit tests -public interface ValueConverter { - public fun fromAv(attr: AttributeValue): A - public fun toAv(value: A): AttributeValue -} +/** + * Defines the logic for converting individual values between a high-level type [V] (e.g., [String], [Boolean], [Map]) + * and + * [DynamoDB data types](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes) + * @param V The type of high-level values which will be converted to low-level DynamoDB attribute values + */ +public typealias ValueConverter = Converter diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverter.kt new file mode 100644 index 00000000000..df254b87133 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverter.kt @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapFrom +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Converts between [List] and + * [DynamoDB `L` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.List). + * Note that the lists must contain already-converted [AttributeValue] elements. This converter is typically chained + * with another converter which handles converting elements to [AttributeValue] either by using the factory function + * [ListConverter] or using the [mapFrom] extension method. + * + * For example: + * + * ```kotlin + * val intListConv = ListConverter(IntConverter) // ValueConverter> + * val intListConv2 = ListConverter.mapFrom(IntConverter) // same as above + * ``` + */ +public val ListConverter: ValueConverter> = Converter(AttributeValue::L, AttributeValue::asL) + +/** + * Creates a new list converter using the given [elementConverter] as a delegate + * @param F The type of elements in the list + * @param elementConverter A converter for transforming between values of [F] and [AttributeValue] + */ +@Suppress("ktlint:standard:function-naming") +public fun ListConverter(elementConverter: Converter): ValueConverter> = + ListConverter.mapFrom(elementConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverter.kt new file mode 100644 index 00000000000..9e0c1856f16 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverter.kt @@ -0,0 +1,48 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapFrom +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapKeysFrom +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapValuesFrom +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Converts between [Map] and + * [DynamoDB `M` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.Map). + * Note that the maps must contain [String] keys and already-converted [AttributeValue] values. This converter is + * typically chained with another converter which handles converting values to [AttributeValue] either by using the + * factory function [MapConverter] or by using the [mapFrom]/[mapValuesFrom]/[mapKeysFrom] extension methods. + * + * ```kotlin + * val instantMapConv = MapConverter(InstantConverter.Default) // ValueConverter> + * val instantMapConv2 = MapConverter.mapValuesFrom(InstantConverter.Default) // same as above + * ``` + */ +public val MapConverter: ValueConverter> = Converter(AttributeValue::M, AttributeValue::asM) + +/** + * Creates a new map converter using the given [keyConverter] and [valueConverter] as delegates + * @param K The type of keys in the map + * @param V The type of values in the map + * @param keyConverter A converter for transforming between [K] keys and [String] keys + * @param valueConverter A converter for transforming between [V] values and [AttributeValue] + */ +@Suppress("ktlint:standard:function-naming") +public fun MapConverter( + keyConverter: Converter, + valueConverter: ValueConverter, +): ValueConverter> = MapConverter.mapFrom(keyConverter, valueConverter) + +/** + * Creates a new string-keyed map converter using the given [valueConverter] as a delegate + * @param V The type of values in the map + * @param valueConverter A converter for transforming between [V] values and [AttributeValue] + */ +@Suppress("ktlint:standard:function-naming") +public fun MapConverter(valueConverter: ValueConverter): ValueConverter> = + MapConverter.mapValuesFrom(valueConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConverters.kt new file mode 100644 index 00000000000..385415c35c2 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/NumberSetConverters.kt @@ -0,0 +1,107 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.* +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenFrom +import aws.sdk.kotlin.hll.mapping.core.converters.collections.CollectionTypeConverters +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapFrom +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Namespace for containing various conversion utilities dealing with numerical set conversion + */ +public object NumberSetConverters { + /** + * Converts between a [List] of [String] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ + public val StringListToAttributeValueNumberSetConverter: ValueConverter> = + Converter(AttributeValue::Ns, AttributeValue::asNs) + + /** + * Converts between a [Set] of [String] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ + public val StringSetToAttributeValueNumberSetConverter: ValueConverter> = + StringListToAttributeValueNumberSetConverter.andThenFrom(CollectionTypeConverters.SetToListConverter()) + + /** + * Creates a [ValueConverter] which converts between a [Set] of [N] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + * @param N The type of high-level values which will be converted + */ + + public fun of(numberToStringConverter: Converter): ValueConverter> = + StringSetToAttributeValueNumberSetConverter.mapFrom(numberToStringConverter) +} + +/** + * Converts between a [Set] of [Byte] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val ByteSetConverter: ValueConverter> = NumberSetConverters.of(NumberConverters.ByteToStringConverter) + +/** + * Converts between a [Set] of [Double] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val DoubleSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.DoubleToStringConverter) + +/** + * Converts between a [Set] of [Float] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val FloatSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.FloatToStringConverter) + +/** + * Converts between a [Set] of [Int] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val IntSetConverter: ValueConverter> = NumberSetConverters.of(NumberConverters.IntToStringConverter) + +/** + * Converts between a [Set] of [Long] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val LongSetConverter: ValueConverter> = NumberSetConverters.of(NumberConverters.LongToStringConverter) + +/** + * Converts between a [Set] of [Short] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val ShortSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.ShortToStringConverter) + +/** + * Converts between a [Set] of [UByte] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val UByteSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.UByteToStringConverter) + +/** + * Converts between a [Set] of [UInt] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val UIntSetConverter: ValueConverter> = NumberSetConverters.of(NumberConverters.UIntToStringConverter) + +/** + * Converts between a [Set] of [ULong] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val ULongSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.ULongToStringConverter) + +/** + * Converts between a [Set] of [UShort] elements and + * [DynamoDB `NS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val UShortSetConverter: ValueConverter> = + NumberSetConverters.of(NumberConverters.UShortToStringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/PrimitiveSetConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/PrimitiveSetConverters.kt new file mode 100644 index 00000000000..d5805c5a7f5 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/PrimitiveSetConverters.kt @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.TextConverters +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenFrom +import aws.sdk.kotlin.hll.mapping.core.converters.collections.CollectionTypeConverters +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapFrom +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Converts between a [Set] of [ByteArray] elements and + * [DynamoDB `BS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val ByteArraySetConverter: ValueConverter> = Converter( + convertTo = { from: Set -> AttributeValue.Bs(from.toList()) }, + convertFrom = { to: AttributeValue -> to.asBs().toSet() }, +) + +/** + * Converts between a [List] of [String] elements and + * [DynamoDB `SS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val StringListToAttributeValueStringSetConverter: ValueConverter> = + Converter(AttributeValue::Ss, AttributeValue::asSs) + +/** + * Converts between a [Set] of [String] elements and + * [DynamoDB `SS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val StringSetConverter: ValueConverter> = + StringListToAttributeValueStringSetConverter.andThenFrom(CollectionTypeConverters.SetToListConverter()) + +/** + * Converts between a [Set] of [CharArray] elements and + * [DynamoDB `SS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val CharArraySetConverter: ValueConverter> = + StringSetConverter.mapFrom(TextConverters.CharArrayToStringConverter) + +/** + * Converts between a [Set] of [Char] elements and + * [DynamoDB `SS` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.SetTypes) + */ +public val CharSetConverter: ValueConverter> = + StringSetConverter.mapFrom(TextConverters.CharToStringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/BooleanConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/BooleanConverter.kt new file mode 100644 index 00000000000..3ad4ed92380 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/BooleanConverter.kt @@ -0,0 +1,15 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Converts between [Boolean] and + * [DynamoDB `BOOL` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Boolean) + */ +public val BooleanConverter: ValueConverter = Converter(AttributeValue::Bool, AttributeValue::asBool) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ByteArrayConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ByteArrayConverter.kt new file mode 100644 index 00000000000..631bb203a22 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ByteArrayConverter.kt @@ -0,0 +1,15 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Converts between [ByteArray] and + * [DynamoDB `B` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Binary) + */ +public val ByteArrayConverter: ValueConverter = Converter(AttributeValue::B, AttributeValue::asB) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/EnumConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/EnumConverter.kt new file mode 100644 index 00000000000..1a2d4665619 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/EnumConverter.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.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenFrom + +/** + * Converts between [Enum] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + * @param E The [Enum] type to convert + */ +public class EnumConverter>( + private val enumToStringConverter: Converter, +) : ValueConverter by StringConverter.andThenFrom(enumToStringConverter) + +/** + * Instantiates a new [ValueConverter] for enums of type [E] + * @param E The [Enum] type for which to create a [ValueConverter] + */ +public inline fun > EnumConverter(): EnumConverter = + EnumConverter( + enumToStringConverter = Converter( + convertTo = { from: E -> from.name }, + convertFrom = { to: String -> enumValueOf(to) }, + ), + ) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConverters.kt new file mode 100644 index 00000000000..0c872e74cd7 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/NumberConverters.kt @@ -0,0 +1,179 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenTo +import aws.sdk.kotlin.hll.mapping.core.converters.validatingFrom +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Namespace for containing various conversion utilities dealing with number conversion + */ +public object NumberConverters { + /** + * Converts between [String] instances which contains numbers and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ + public val StringToAttributeValueNumberConverter: ValueConverter = + Converter(AttributeValue::N, AttributeValue::asN) + + /** + * Creates a [ValueConverter] which converts between number type [N] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ + public fun of(numberToStringConverter: Converter): ValueConverter = + numberToStringConverter.andThenTo(StringToAttributeValueNumberConverter) + + /** + * Converts between [Number] and [String] values + */ + public val AutoNumberToStringConverter: Converter = Converter( + convertTo = Number::toString, + convertFrom = { to: String -> + when { + '.' in to -> to.toDouble() + else -> when (val longNumber = to.toLong()) { + in Int.MIN_VALUE..Int.MAX_VALUE -> longNumber.toInt() + else -> longNumber + } + } + }, + ) + + /** + * Converts between [Byte] and [String] values + */ + public val ByteToStringConverter: Converter = Converter(Byte::toString, String::toByte) + + /** + * Converts between [Double] and [String] values. Because + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + * do not support them, this converter throws exceptions for non-finite numbers such as [Double.NEGATIVE_INFINITY], + * [Double.POSITIVE_INFINITY], and [Double.NaN]. + */ + public val DoubleToStringConverter: Converter = + Converter(Double::toString, String::toDouble).validatingFrom { from: Double -> + require(from.isFinite()) { "Cannot convert $from: only finite numbers are supported" } + } + + /** + * Converts between [Float] and [String] values. Because + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + * do not support them, this converter throws exceptions for non-finite numbers such as [Float.NEGATIVE_INFINITY], + * [Float.POSITIVE_INFINITY], and [Float.NaN]. + */ + public val FloatToStringConverter: Converter = + Converter(Float::toString, String::toFloat).validatingFrom { from: Float -> + require(from.isFinite()) { "Cannot convert $from: only finite numbers are supported" } + } + + /** + * Converts between [Int] and [String] values + */ + public val IntToStringConverter: Converter = Converter(Int::toString, String::toInt) + + /** + * Converts between [Long] and [String] values + */ + public val LongToStringConverter: Converter = Converter(Long::toString, String::toLong) + + /** + * Converts between [Short] and [String] values + */ + public val ShortToStringConverter: Converter = Converter(Short::toString, String::toShort) + + /** + * Converts between [UByte] and [String] values + */ + public val UByteToStringConverter: Converter = Converter(UByte::toString, String::toUByte) + + /** + * Converts between [UInt] and [String] values + */ + public val UIntToStringConverter: Converter = Converter(UInt::toString, String::toUInt) + + /** + * Converts between [ULong] and [String] values + */ + public val ULongToStringConverter: Converter = Converter(ULong::toString, String::toULong) + + /** + * Converts between [UShort] and [String] values + */ + public val UShortToStringConverter: Converter = Converter(UShort::toString, String::toUShort) +} + +/** + * Converts between [Number] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number). + * When converting attribute values into number values, the following concrete subclasses of [Number] will be returned: + * * [Double] — If the number contains any fractional component + * * [Int] — If the number is in the range of [Int.MIN_VALUE] and [Int.MAX_VALUE] (inclusive) + * * [Long] — Anything else + */ +public val AutoNumberConverter: ValueConverter = + NumberConverters.of(NumberConverters.AutoNumberToStringConverter) + +/** + * Converts between [Byte] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val ByteConverter: ValueConverter = NumberConverters.of(NumberConverters.ByteToStringConverter) + +/** + * Converts between [Double] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val DoubleConverter: ValueConverter = NumberConverters.of(NumberConverters.DoubleToStringConverter) + +/** + * Converts between [Float] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val FloatConverter: ValueConverter = NumberConverters.of(NumberConverters.FloatToStringConverter) + +/** + * Converts between [Int] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val IntConverter: ValueConverter = NumberConverters.of(NumberConverters.IntToStringConverter) + +/** + * Converts between [Long] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val LongConverter: ValueConverter = NumberConverters.of(NumberConverters.LongToStringConverter) + +/** + * Converts between [Short] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val ShortConverter: ValueConverter = NumberConverters.of(NumberConverters.ShortToStringConverter) + +/** + * Converts between [UByte] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val UByteConverter: ValueConverter = NumberConverters.of(NumberConverters.UByteToStringConverter) + +/** + * Converts between [UInt] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val UIntConverter: ValueConverter = NumberConverters.of(NumberConverters.UIntToStringConverter) + +/** + * Converts between [ULong] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val ULongConverter: ValueConverter = NumberConverters.of(NumberConverters.ULongToStringConverter) + +/** + * Converts between [UShort] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + */ +public val UShortConverter: ValueConverter = NumberConverters.of(NumberConverters.UShortToStringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConverters.kt new file mode 100644 index 00000000000..866672f7da5 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/TextConverters.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.dynamodbmapper.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenTo +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue + +/** + * Namespace for containing various conversion utilities dealing with text conversion + */ +public object TextConverters { + /** + * Converts between [CharArray] and [String] + */ + public val CharArrayToStringConverter: Converter = Converter(::String, String::toCharArray) + + /** + * Converts between [Char] and [String] + */ + public val CharToStringConverter: Converter = Converter(Char::toString, String::single) +} + +/** + * Converts between [String] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + */ +public val StringConverter: ValueConverter = Converter(AttributeValue::S, AttributeValue::asS) + +/** + * Converts between [CharArray] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + */ +public val CharArrayConverter: ValueConverter = + TextConverters.CharArrayToStringConverter.andThenTo(StringConverter) + +/** + * Converts between [Char] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + */ +public val CharConverter: ValueConverter = TextConverters.CharToStringConverter.andThenTo(StringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter.kt new file mode 100644 index 00000000000..1b4311b7d2c --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverter.kt @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.NullableConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.collections.ListConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.collections.MapConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.AutoNumberConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.BooleanConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapFrom +import aws.sdk.kotlin.hll.mapping.core.converters.collections.mapValuesFrom +import aws.sdk.kotlin.hll.mapping.core.converters.mergeBy +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import aws.smithy.kotlin.runtime.content.Document + +/** + * Converts between [Document] and various DynamoDB value types. The following conversions are performed: + * * `null` ↔ [DynamoDB `NULL` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Null) + * * [Document.Number] ↔ [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + * * [Document.String] ↔ [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + * * [Document.Boolean] ↔ [DynamoDB `BOOL` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Boolean) + * * [Document.List] ↔ [DynamoDB `L` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.List) + * * [Document.Map] ↔ [DynamoDB `M` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.Map) + */ +public class DocumentValueConverter( + private val numberConverter: ValueConverter = AutoNumberConverter, + private val stringConverter: ValueConverter = StringConverter, + private val booleanConverter: ValueConverter = BooleanConverter, + nullableConverter: NullableConverter = NullableConverter(), + listConverter: ValueConverter> = ListConverter, + mapConverter: ValueConverter> = MapConverter, +) : ValueConverter { + private val nullableConverter = nullableConverter.mergeBy(this) + private val listConverter = listConverter.mapFrom(this.nullableConverter) + private val mapConverter = mapConverter.mapValuesFrom(this.nullableConverter) + + public companion object { + /** + * The default instance of [DocumentValueConverter] + */ + public val Default: DocumentValueConverter = DocumentValueConverter() + } + + override fun convertFrom(to: AttributeValue): Document = when (to) { + is AttributeValue.N -> Document.Number(numberConverter.convertFrom(to)) + is AttributeValue.S -> Document.String(stringConverter.convertFrom(to)) + is AttributeValue.Bool -> Document.Boolean(booleanConverter.convertFrom(to)) + is AttributeValue.L -> Document.List(listConverter.convertFrom(to)) + is AttributeValue.M -> Document.Map(mapConverter.convertFrom(to)) + else -> throw IllegalArgumentException("Documents do not support ${to::class.qualifiedName} values") + } + + override fun convertTo(from: Document): AttributeValue = when (from) { + is Document.Number -> numberConverter.convertTo(from.value) + is Document.String -> stringConverter.convertTo(from.value) + is Document.Boolean -> booleanConverter.convertTo(from.value) + is Document.List -> listConverter.convertTo(from.value) + is Document.Map -> mapConverter.convertTo(from.value) + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConverters.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConverters.kt new file mode 100644 index 00000000000..7a6705e0e7f --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConverters.kt @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.LongConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenTo +import aws.smithy.kotlin.runtime.time.Instant +import aws.smithy.kotlin.runtime.time.TimestampFormat +import aws.smithy.kotlin.runtime.time.epochMilliseconds +import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds + +/** + * Provides access to [ValueConverter] types for various [Instant] representations + */ +public object InstantConverters { + /** + * Converts between [Instant] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + * containing the number of milliseconds since the Unix epoch + */ + public val EpochMs: ValueConverter = + Converter(Instant::epochMilliseconds, Instant::fromEpochMilliseconds).andThenTo(LongConverter) + + /** + * Converts between [Instant] and + * [DynamoDB `N` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number) + * containing the number of seconds since the Unix epoch + */ + public val EpochS: ValueConverter = + Converter(Instant::epochSeconds, Instant::fromEpochSeconds).andThenTo(LongConverter) + + /** + * Converts between [Instant] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + * containing a formatted ISO 8601 representation + */ + public val Iso8601: ValueConverter = + Converter( + convertTo = { from: Instant -> from.format(TimestampFormat.ISO_8601_FULL) }, + convertFrom = Instant::fromIso8601, + ).andThenTo(StringConverter) + + /** + * The default converter for [Instant] values, which is an instance of [EpochS] + */ + public val Default: ValueConverter = EpochS +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverter.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverter.kt new file mode 100644 index 00000000000..6d12de6e35b --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverter.kt @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenTo +import aws.smithy.kotlin.runtime.net.url.Url + +/** + * Converts between [Url] and [String] types + */ +public val UrlToStringConverter: Converter = Converter(Url::toString, Url::parse) + +/** + * Converts between [Url] and + * [DynamoDB `S` values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.String) + */ +public val UrlConverter: ValueConverter = UrlToStringConverter.andThenTo(StringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverterTest.kt index d024cd9c491..91b932c5831 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverterTest.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/items/SimpleItemConverterTest.kt @@ -4,9 +4,9 @@ */ package aws.sdk.kotlin.hll.dynamodbmapper.items -import aws.sdk.kotlin.hll.dynamodbmapper.values.BooleanConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.IntConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.StringConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.BooleanConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue 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 2e1d97d91ad..ac161d60697 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 @@ -9,8 +9,8 @@ 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.testutils.DdbLocalTest -import aws.sdk.kotlin.hll.dynamodbmapper.values.IntConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.StringConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter import aws.sdk.kotlin.services.dynamodb.model.ReturnConsumedCapacity import aws.sdk.kotlin.services.dynamodb.model.ReturnValue import kotlinx.coroutines.test.runTest 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 507be24e165..43e19754d43 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 @@ -10,8 +10,8 @@ import aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter import aws.sdk.kotlin.hll.dynamodbmapper.model.Table import aws.sdk.kotlin.hll.dynamodbmapper.testutils.DdbLocalTest -import aws.sdk.kotlin.hll.dynamodbmapper.values.IntConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.StringConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter import aws.sdk.kotlin.services.dynamodb.model.ReturnConsumedCapacity import kotlinx.coroutines.test.runTest import kotlin.test.assertEquals 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 086a8d09297..de47b760aca 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 @@ -10,8 +10,8 @@ import aws.sdk.kotlin.hll.dynamodbmapper.items.KeySpec import aws.sdk.kotlin.hll.dynamodbmapper.items.SimpleItemConverter import aws.sdk.kotlin.hll.dynamodbmapper.testutils.DdbLocalTest import aws.sdk.kotlin.hll.dynamodbmapper.testutils.getItem -import aws.sdk.kotlin.hll.dynamodbmapper.values.IntConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.StringConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter import kotlinx.coroutines.test.runTest import kotlin.test.assertEquals import kotlin.test.assertNotNull 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 0c63c456db6..9541cf75eff 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 @@ -12,8 +12,8 @@ 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.testutils.ddbItem -import aws.sdk.kotlin.hll.dynamodbmapper.values.IntConverter -import aws.sdk.kotlin.hll.dynamodbmapper.values.StringConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.IntConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter import kotlinx.coroutines.test.runTest import kotlin.test.assertContentEquals import kotlin.test.assertNotNull diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/AttributeValues.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/AttributeValues.kt new file mode 100644 index 00000000000..7d51bf1b64f --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/AttributeValues.kt @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.testutils + +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.reflect.KClass +import kotlin.reflect.full.isSubclassOf + +fun attr(value: Boolean) = AttributeValue.Bool(value) +fun attr(value: ByteArray) = AttributeValue.B(value) +fun attr(value: List) = AttributeValue.L(value.map(::dynamicAttr)) +fun attr(vararg value: Any?) = attr(value.toList()) +fun attr(value: Map) = AttributeValue.M(value.mapValues { (_, v) -> dynamicAttr(v) }) +fun attr(vararg values: Pair) = attr(values.toMap()) +fun attr(value: Nothing?) = AttributeValue.Null(true) +fun attr(value: Number) = AttributeValue.N(value.toString()) +fun attr(value: Set) = AttributeValue.Bs(value.toList()) +fun attr(value: Set) = AttributeValue.Ns(value.map(Number::toString)) +fun attr(value: Set) = AttributeValue.Ss(value.toList()) +fun attr(value: String) = AttributeValue.S(value) + +// The unsigned types don't implement `Number` and so have to be handled individually + +@JvmName("attrUByte") +fun attr(value: Set) = AttributeValue.Ns(value.map(UByte::toString)) + +@JvmName("attrUInt") +fun attr(value: Set) = AttributeValue.Ns(value.map(UInt::toString)) + +@JvmName("attrULong") +fun attr(value: Set) = AttributeValue.Ns(value.map(ULong::toString)) + +@JvmName("attrUShort") +fun attr(value: Set) = AttributeValue.Ns(value.map(UShort::toString)) + +/** + * Converts a map of strings to values to a map of strings to [AttributeValue] + * @param item The item to convert + */ +fun ddbItem(item: Map) = item.mapValues { (_, v) -> dynamicAttr(v) } + +/** + * Converts a collection of tuples of strings to values to a map of strings to [AttributeValue] + * @param attributes The attributes to convert + */ +fun ddbItem(vararg attributes: Pair) = ddbItem(attributes.toMap()) + +@Suppress("UNCHECKED_CAST") +fun dynamicAttr(value: Any?): AttributeValue = when (value) { + null -> attr(null) + is Boolean -> attr(value) + is ByteArray -> attr(value) + is List<*> -> attr(value) + is Map<*, *> -> attr(value as Map) + is Number -> attr(value) + is Set<*> -> { + require(value.isNotEmpty()) { "Cannot determine type of empty set at runtime" } + val types = value.groupBy { it.getClass() }.keys + val type = types.singleOrNull() ?: error("Mixed set element types: $types") + + when { + type.isSubclassOf(ByteArray::class) -> attr(value as Set) + type.isSubclassOf(Number::class) -> attr(value as Set) + type.isSubclassOf(String::class) -> attr(value as Set) + type.isSubclassOf(UByte::class) -> attr(value as Set) + type.isSubclassOf(UInt::class) -> attr(value as Set) + type.isSubclassOf(ULong::class) -> attr(value as Set) + type.isSubclassOf(UShort::class) -> attr(value as Set) + else -> error("Unsupported set element type $type") + } + } + is String -> attr(value) + else -> error("Unsupported attribute value type ${value::class}") +} + +private fun Any?.getClass(): KClass<*> = if (this == null) Nothing::class else (this::class) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/DdbClientExtensions.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/DdbClientExtensions.kt index b41267869ce..1f518f2e594 100644 --- a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/DdbClientExtensions.kt +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/testutils/DdbClientExtensions.kt @@ -101,25 +101,6 @@ suspend fun DynamoDbClient.putItems(tableName: String, items: List) = item.mapValues { (_, v) -> - when (v) { - is ByteArray -> AttributeValue.B(v) - is Number -> AttributeValue.N(v.toString()) - is String -> AttributeValue.S(v) - else -> TODO("Implement support for ${v::class} types!") - } -} - -/** - * Converts a collection of tuples of strings to values to a map of strings to [AttributeValue] - * @param attributes The attributes to convert - */ -fun ddbItem(vararg attributes: Pair) = ddbItem(attributes.toMap()) - /** * Derives the [AttributeDefinition] instances for a table, taking into account its schema and any secondary indices * @param schema The schema of the table diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverterTest.kt new file mode 100644 index 00000000000..1f2f01d470a --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/NullableConverterTest.kt @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values + +import aws.sdk.kotlin.hll.dynamodbmapper.values.scalars.StringConverter +import aws.sdk.kotlin.hll.mapping.core.converters.Converter +import aws.sdk.kotlin.hll.mapping.core.converters.andThenTo +import kotlin.test.Test + +class NullableConverterTest : ValueConvertersTest() { + @Test + fun testNullConverter() = given(NullableConverter(stringReverseConverter)) { + "foo" inDdbIs "oof" + "bar" inDdbIs "rab" + null inDdbIs theSame + "null" inDdbIs "llun" + } +} + +private val stringReverseConverter = Converter(String::reversed, String::reversed).andThenTo(StringConverter) diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConvertersTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConvertersTest.kt new file mode 100644 index 00000000000..e940632828e --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/ValueConvertersTest.kt @@ -0,0 +1,217 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values + +import aws.sdk.kotlin.hll.dynamodbmapper.testutils.attr +import aws.sdk.kotlin.hll.dynamodbmapper.testutils.dynamicAttr +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * A base class for unit testing [ValueConverter] instances, including a DSL for streamlining test setup. Tests are + * configured using the [given] method and a DSL block that creates test steps using the + * [infix function](https://kotlinlang.org/docs/functions.html#infix-notation) [TestBuilder.inDdbIs]. + * + * Example usage: + * + * ```kotlin + * fun testFooConverter() = given(fooConverterInstance) { + * // Test conversion of high-level value `aFoo` to low-level attribute value { "S": "a foo" } and back again + * aFoo inDdbIs attr("a foo") + * + * // Test conversion of high-level value to an automatically-derived identical representation and back again + * bFoo inDdbIs theSame + * + * // Test conversion resulting in an error + * cFoo inDdbIs anError + * + * // Test conversion only going one way (useful when conversion back results in a different value) + * dFoo inDdbIs attr("d foo") whenGoing Direction.TO_DDB + * } + * ``` + * @see [given] + * @see [TestBuilder.inDdbIs] + * @see [TestBuilder.theSame] + * @see [TestBuilder.anError] + * @see [TestBuilder.whenGoing] + * @see [attr] + */ +abstract class ValueConvertersTest { + /** + * Executes a series of individual tests on the given [converter] + * @param converter The [ValueConverter] instance under test + * @param block A DSL block for constructing individual tests via [TestBuilder.inDdbIs] + */ + protected fun given(converter: ValueConverter, block: TestBuilder.() -> Unit) { + val tests = mutableListOf>() + TestBuilder(tests).apply(block) + + tests.forEachIndexed { index, (steps) -> + steps.forEach { (direction, highLevel, lowLevel) -> + when (direction) { + Direction.TO_ATTRIBUTE_VALUE -> { + val result = runCatching { converter.convertTo(highLevel.requireInput()) } + lowLevel.assert(result, "Test $index failed converting to attribute value") + } + + Direction.FROM_ATTRIBUTE_VALUE -> { + val result = runCatching { converter.convertFrom(lowLevel.requireInput()) } + highLevel.assert(result, "Test $index failed converting from attribute value") + } + } + } + } + } + + /** + * Identifies a direction of conversion + */ + enum class Direction { + /** + * Specifies conversion from a high-level value to a low-level DynamoDB attribute value + */ + TO_ATTRIBUTE_VALUE, + + /** + * Specifies conversion from a low-level DynamoDB attribute value to a high-level value + */ + FROM_ATTRIBUTE_VALUE, + } + + /** + * A test case consisting of multiple test steps + */ + data class TestCase(val steps: List>) { + companion object { + fun of(highLevel: TestExpectation, lowLevel: TestExpectation): TestCase { + val toAv = highLevel.asSuccessOrNull()?.let { TestStep(Direction.TO_ATTRIBUTE_VALUE, it, lowLevel) } + val fromAv = lowLevel.asSuccessOrNull()?.let { TestStep(Direction.FROM_ATTRIBUTE_VALUE, highLevel, it) } + return TestCase(listOfNotNull(toAv, fromAv)) + } + } + } + + /** + * A test step that describes the expectations for converting a value in a single direction + */ + data class TestStep( + val direction: Direction, + val highLevel: TestExpectation, + val lowLevel: TestExpectation, + ) + + /** + * An expected value or outcome for an input or output to a conversion + */ + sealed interface TestExpectation { + fun assert(result: Result<*>, message: String) + + fun asSuccessOrNull(): Success? = this as? Success + + fun requireInput(): T { + require(this is Success) { "Cannot use $this as an input to a converter test" } + return value + } + + data class Success(val value: T) : TestExpectation { + override fun assert(result: Result<*>, message: String) { + @Suppress("UNCHECKED_CAST") + val output = result.getOrThrow() as T + assertDeepEquals(value, output, message) + } + } + + data object Failure : TestExpectation { + override fun assert(result: Result<*>, message: String) { + assertTrue(result.isFailure, "$message (actual value: ${result.getOrNull()})") + } + } + } + + class TestBuilder(private val tests: MutableList>) { + object AnError + object TheSame + + /** + * Indicates that an error is expected + */ + val anError = AnError + + /** + * Indicates that an automatically-derived identical representation is expected + */ + val theSame = TheSame + + private fun T.addTest(lowLevel: AttributeValue): TestCase = + TestCase + .of(TestExpectation.Success(this), TestExpectation.Success(lowLevel)) + .also(tests::add) + + private fun T.addTest(anError: AnError): TestCase = + TestCase + .of(TestExpectation.Success(this), TestExpectation.Failure) + .also(tests::add) + + infix fun T.inDdbIs(anError: AnError) = addTest(anError) + infix fun T.inDdbIs(theSame: TheSame) = addTest(dynamicAttr(this)) + + infix fun T.inDdbIs(attr: AttributeValue) = addTest(attr) + infix fun T.inDdbIs(value: Boolean) = addTest(attr(value)) + infix fun T.inDdbIs(value: ByteArray) = addTest(attr(value)) + infix fun T.inDdbIs(value: List) = addTest(attr(value)) + infix fun T.inDdbIs(value: Map) = addTest(attr(value)) + infix fun T.inDdbIs(value: Nothing?) = addTest(attr(null)) + infix fun T.inDdbIs(value: Number) = addTest(attr(value)) + infix fun T.inDdbIs(value: String) = addTest(attr(value)) + + @JvmName("inDdbIsSetByteArray") + infix fun T.inDdbIs(value: Set) = addTest(attr(value)) + + @JvmName("inDdbIsSetNumber") + infix fun T.inDdbIs(value: Set) = addTest(attr(value)) + + @JvmName("inDdbIsSetString") + infix fun T.inDdbIs(value: Set) = addTest(attr(value)) + + /** + * Limits a test case to only a single direction (i.e., checking one of `fromAttributeValue`/`toAttributeValue` + * but not both). Most test cases are bidirectional. + */ + infix fun TestCase.whenGoing(direction: Direction) { + val removed = steps.filter { it.direction == direction } + if (removed != steps) { + tests.remove(this) + tests.add(TestCase(removed)) + } + } + } +} + +/** + * A convenience function that allows asserting value equality for non-array types and content equality for array types + * (including primitive arrays) + */ +@OptIn(ExperimentalUnsignedTypes::class) +@Suppress("UNCHECKED_CAST") +private fun assertDeepEquals(expected: T, actual: T, message: String) = when { + expected is Array<*> && actual is Array<*> -> + assertContentEquals(expected as Array, actual as Array, message) + + expected is BooleanArray && actual is BooleanArray -> assertContentEquals(expected, actual, message) + expected is ByteArray && actual is ByteArray -> assertContentEquals(expected, actual, message) + expected is CharArray && actual is CharArray -> assertContentEquals(expected, actual, message) + expected is DoubleArray && actual is DoubleArray -> assertContentEquals(expected, actual, message) + expected is FloatArray && actual is FloatArray -> assertContentEquals(expected, actual, message) + expected is IntArray && actual is IntArray -> assertContentEquals(expected, actual, message) + expected is LongArray && actual is LongArray -> assertContentEquals(expected, actual, message) + expected is ShortArray && actual is ShortArray -> assertContentEquals(expected, actual, message) + expected is UByteArray && actual is UByteArray -> assertContentEquals(expected, actual, message) + expected is UIntArray && actual is UIntArray -> assertContentEquals(expected, actual, message) + expected is ULongArray && actual is ULongArray -> assertContentEquals(expected, actual, message) + expected is UShortArray && actual is UShortArray -> assertContentEquals(expected, actual, message) + else -> assertEquals(expected, actual, message) +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverterTest.kt new file mode 100644 index 00000000000..307821dd8c4 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/ListConverterTest.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.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.testutils.attr +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.test.Test + +class ListConverterTest : ValueConvertersTest() { + @Test + fun testListConverter() = given(ListConverter(FooConverter)) { + listOf(Foo("apple", 1), Foo("banana", 2), Foo("cherry", 3)) inDdbIs listOf( + mapOf("bar" to "apple", "baz" to 1), + mapOf("bar" to "banana", "baz" to 2), + mapOf("bar" to "cherry", "baz" to 3), + ) + + List(3) { Foo("date", 4) } inDdbIs List(3) { mapOf("bar" to "date", "baz" to 4) } + + listOf() inDdbIs theSame + } +} + +private data class Foo(val bar: String, val baz: Int) + +private object FooConverter : ValueConverter { + override fun convertFrom(to: AttributeValue): Foo { + val map = to.asM() + val bar = map.getValue("bar").asS() + val baz = map.getValue("baz").asN().toInt() + return Foo(bar, baz) + } + + override fun convertTo(from: Foo) = AttributeValue.M( + mapOf( + "bar" to attr(from.bar), + "baz" to attr(from.baz), + ), + ) +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverterTest.kt new file mode 100644 index 00000000000..0373e6e4332 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/MapConverterTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.testutils.attr +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConverter +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.test.Test + +class MapConverterTest : ValueConvertersTest() { + @Test + fun testMapConverter() = given(MapConverter(BarConverter)) { + mapOf("short" to Bar(false, "meh"), "long" to Bar(true, "m", "e", "h")) inDdbIs mapOf( + "short" to listOf(false, "meh"), + "long" to listOf(true, "m", "e", "h"), + ) + + mapOf() inDdbIs theSame + } +} + +private data class Bar(val foo: Boolean, val baz: List) { + constructor(foo: Boolean, vararg baz: String) : this(foo, baz.toList()) +} + +private object BarConverter : ValueConverter { + override fun convertFrom(to: AttributeValue): Bar { + val list = to.asL() + val foo = list.first().asBool() + val baz = list.drop(1).map { it.asS() } + return Bar(foo, baz) + } + + override fun convertTo(from: Bar) = AttributeValue.L(listOf(attr(from.foo)) + from.baz.map(::attr)) +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/SetConvertersTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/SetConvertersTest.kt new file mode 100644 index 00000000000..8b2a32b19f1 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/collections/SetConvertersTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.collections + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.test.Test + +private val emptyNumberSet = AttributeValue.Ns(listOf()) + +class SetConvertersTest : ValueConvertersTest() { + @Test + fun testByteArraySetConverter() = given(ByteArraySetConverter) { + setOf(byteArrayOf(1, 1, 2, 3), byteArrayOf(5, 8), byteArrayOf(13, 21, 34, 55, 89)) inDdbIs theSame + setOf() inDdbIs AttributeValue.Bs(listOf()) + } + + @Test + fun testByteSetConverter() = given(ByteSetConverter) { + setOf(1.toByte(), 5.toByte(), Byte.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testCharSetConverter() = given(CharSetConverter) { + setOf('a', 'b', 'A', 'B') inDdbIs setOf("a", "b", "A", "B") + setOf() inDdbIs AttributeValue.Ss(listOf()) + } + + @Test + fun testDoubleSetConverter() = given(DoubleSetConverter) { + setOf(1.0, -3.14159, Double.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testFloatSetConverter() = given(FloatSetConverter) { + setOf(1.0f, -3.14159f, Float.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testIntSetConverter() = given(IntSetConverter) { + setOf(392, -5_129_352, Int.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testLongSetConverter() = given(LongSetConverter) { + setOf(392L, -5_129_352_000_000_000L, Long.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testShortSetConverter() = given(ShortSetConverter) { + setOf(392.toShort(), (-1024).toShort(), Short.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun tesStringSetConverter() = given(StringSetConverter) { + setOf("The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dogs") inDdbIs theSame + setOf() inDdbIs AttributeValue.Ss(listOf()) + } + + @Test + fun testUByteSetConverter() = given(UByteSetConverter) { + setOf(1.toUByte(), (Byte.MAX_VALUE.toInt() + 1).toUByte(), UByte.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testUIntSetConverter() = given(UIntSetConverter) { + setOf(392u, Int.MAX_VALUE.toUInt() + 1u, UInt.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testULongSetConverter() = given(ULongSetConverter) { + setOf(392uL, Long.MAX_VALUE.toULong() + 1uL, ULong.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } + + @Test + fun testUShortSetConverter() = given(UShortSetConverter) { + setOf(392.toUShort(), (Short.MAX_VALUE.toInt() + 1).toUShort(), UShort.MAX_VALUE) inDdbIs theSame + setOf() inDdbIs emptyNumberSet + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ScalarConvertersTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ScalarConvertersTest.kt new file mode 100644 index 00000000000..286f098af00 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/scalars/ScalarConvertersTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.scalars + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.sdk.kotlin.services.dynamodb.model.AttributeValue +import kotlin.test.Test + +class ScalarConvertersTest : ValueConvertersTest() { + @Test + fun testBooleanConverter() = given(BooleanConverter) { + true inDdbIs theSame + false inDdbIs theSame + } + + @Test + fun testByteArrayConverter() = given(ByteArrayConverter) { + byteArrayOf() inDdbIs theSame + "Foo".toByteArray() inDdbIs theSame + ByteArray(1024) { it.toByte() } inDdbIs theSame + } + + @Test + fun testByteConverter() = given(ByteConverter) { + 1.toByte() inDdbIs theSame + 47.toByte() inDdbIs theSame + Byte.MIN_VALUE inDdbIs theSame + } + + @Test + fun testCharArrayConverter() = given(CharArrayConverter) { + charArrayOf() inDdbIs "" + charArrayOf('G', 'u', 'i', 'n', 'e', 'a', ' ', 'p', 'i', 'g') inDdbIs "Guinea pig" + } + + @Test + fun testCharConverter() = given(CharConverter) { + '!' inDdbIs "!" + 'X' inDdbIs "X" + '7' inDdbIs "7" + } + + @Test + fun testDoubleConverter() = given(DoubleConverter) { + (-1.41421) inDdbIs theSame + 2.71828 inDdbIs theSame + 3.14159 inDdbIs theSame + 6.62607e-34 inDdbIs theSame + Double.NEGATIVE_INFINITY inDdbIs anError + Double.NaN inDdbIs anError + } + + @Test + fun testEnumConverter() = given(EnumConverter()) { + Suit.HEARTS inDdbIs "HEARTS" + Suit.DIAMONDS inDdbIs "DIAMONDS" + Suit.CLUBS inDdbIs "CLUBS" + Suit.SPADES inDdbIs "SPADES" + } + + @Test + fun testFloatConverter() = given(FloatConverter) { + (-1.73205f) inDdbIs theSame + 1.61803f inDdbIs theSame + 2.68545f inDdbIs theSame + Float.POSITIVE_INFINITY inDdbIs anError + Float.NaN inDdbIs anError + } + + @Test + fun testIntConverter() = given(IntConverter) { + 0 inDdbIs theSame + 1_000_000 inDdbIs theSame + Int.MIN_VALUE inDdbIs theSame + } + + @Test + fun testLongConverter() = given(LongConverter) { + 31_536_000L inDdbIs theSame + 9_460_730_472_580_800L inDdbIs theSame + Long.MIN_VALUE inDdbIs theSame + } + + @Test + fun testShortConverter() = given(ShortConverter) { + 1.toShort() inDdbIs theSame + 47.toShort() inDdbIs theSame + Short.MIN_VALUE inDdbIs theSame + } + + @Test + fun testStringConverter() = given(StringConverter) { + "The quick brown fox jumped over the lazy dogs" inDdbIs theSame + "Jackdaws love my big sphinx of quartz" inDdbIs theSame + """ + Benjamín pidió una bebida de kiwi y fresa. + Noé, sin vergüenza, la más exquisita champaña del menú. + """.trimIndent() inDdbIs theSame + } + + @Test + fun testUByteConverter() = given(UByteConverter) { + 1.toUByte() inDdbIs 1 + 47.toUByte() inDdbIs 47 + UByte.MAX_VALUE inDdbIs UByte.MAX_VALUE.toInt() + } + + @Test + fun testUIntConverter() = given(UIntConverter) { + 0u inDdbIs 0 + 1_000_000u inDdbIs 1_000_000 + UInt.MAX_VALUE inDdbIs UInt.MAX_VALUE.toLong() + } + + @Test + fun testULongConverter() = given(ULongConverter) { + 31_536_000uL inDdbIs 31_536_000L + 9_460_730_472_580_800uL inDdbIs 9_460_730_472_580_800L + ULong.MAX_VALUE inDdbIs AttributeValue.N(ULong.MAX_VALUE.toString()) + } + + @Test + fun testUShortConverter() = given(UShortConverter) { + 1.toUShort() inDdbIs 1 + 47.toUShort() inDdbIs 47 + UShort.MAX_VALUE inDdbIs UShort.MAX_VALUE.toInt() + } +} + +private enum class Suit { + HEARTS, + DIAMONDS, + CLUBS, + SPADES, +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverterTest.kt new file mode 100644 index 00000000000..7b94d57d1e1 --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/DocumentValueConverterTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.smithy.kotlin.runtime.content.buildDocument +import kotlin.test.Test + +class DocumentValueConverterTest : ValueConvertersTest() { + @Test + fun testKitchenSink() = given(DocumentValueConverter.Default) { + val expectedDocument = buildDocument { + "name" to "Ian" + "pets" to buildList { + add("Arrayah") + add("Coffee") + add("Willow") + add("Bambi") + add("Ginny") + add("Tori") + add("Chewy") + add("Maisy") + add("Pineapple") + add("Gizmo") + add("Bug") + } + "inactive?" to false + "employer" to buildDocument { + "name" to "Amazon" + "founded" to 1994 + "offices" to buildList { + add("Seattle") + add("New York") + add("Boston") + } + "parentCompany" to null + } + "favoriteThings" to buildList { + add("chocolate chip cookies") + add(13) + add(true) + add(null) + } + } + + val expectedValue = mapOf( + "name" to "Ian", + "pets" to listOf( + "Arrayah", + "Coffee", + "Willow", + "Bambi", + "Ginny", + "Tori", + "Chewy", + "Maisy", + "Pineapple", + "Gizmo", + "Bug", + ), + "inactive?" to false, + "employer" to mapOf( + "name" to "Amazon", + "founded" to 1994, + "offices" to listOf( + "Seattle", + "New York", + "Boston", + ), + "parentCompany" to null, + ), + "favoriteThings" to listOf( + "chocolate chip cookies", + 13, + true, + null, + ), + ) + + expectedDocument inDdbIs expectedValue + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConvertersTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConvertersTest.kt new file mode 100644 index 00000000000..17f5b7926ef --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/InstantConvertersTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.smithy.kotlin.runtime.time.Instant +import kotlin.test.Test + +private val WHOLE_TIME = Instant.fromEpochSeconds(1234567890L) // 2009-02-13T23:31:30Z +private val MS_TIME = Instant.fromEpochSeconds(1234567890L, 123000000) // 2009-02-13T23:31:30.123Z +private val MICRO_TIME = Instant.fromEpochSeconds(1234567890L, 123456000) // 2009-02-13T23:31:30.123456Z +private val NS_TIME = Instant.fromEpochSeconds(1234567890L, 123456789) // 2009-02-13T23:31:30.123456789Z + +class InstantConvertersTest : ValueConvertersTest() { + @Test + fun testEpochS() = given(InstantConverters.EpochS) { + WHOLE_TIME inDdbIs 1234567890L + NS_TIME inDdbIs 1234567890L whenGoing Direction.TO_ATTRIBUTE_VALUE + } + + @Test + fun testEpochMs() = given(InstantConverters.EpochMs) { + WHOLE_TIME inDdbIs 1234567890000L + NS_TIME inDdbIs 1234567890123L whenGoing Direction.TO_ATTRIBUTE_VALUE + MS_TIME inDdbIs 1234567890123L whenGoing Direction.FROM_ATTRIBUTE_VALUE + } + + @Test + fun testIso8601() = given(InstantConverters.Iso8601) { + WHOLE_TIME inDdbIs "2009-02-13T23:31:30Z" + MS_TIME inDdbIs "2009-02-13T23:31:30.123Z" + MICRO_TIME inDdbIs "2009-02-13T23:31:30.123456Z" + NS_TIME inDdbIs "2009-02-13T23:31:30.123456789Z" + } +} diff --git a/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverterTest.kt b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverterTest.kt new file mode 100644 index 00000000000..6c57585379d --- /dev/null +++ b/hll/dynamodb-mapper/dynamodb-mapper/common/test/aws/sdk/kotlin/hll/dynamodbmapper/values/smithytypes/UrlConverterTest.kt @@ -0,0 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.dynamodbmapper.values.smithytypes + +import aws.sdk.kotlin.hll.dynamodbmapper.values.ValueConvertersTest +import aws.smithy.kotlin.runtime.net.url.Url +import kotlin.test.Test + +private const val URL = "https://foo.bar.baz/qux?quux=corge#grault" + +class UrlConverterTest : ValueConvertersTest() { + @Test + fun testUrlConverter() = given(UrlConverter) { + Url.parse(URL) inDdbIs URL + } +} 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 index 68da7965be9..2a706af4eae 100644 --- 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 @@ -15,6 +15,8 @@ object Pkg { val Ops = "$Base.operations" val PipelineImpl = "$Base.pipeline.internal" val Values = "$Base.values" + val ScalarValues = "$Values.scalars" + val SmithyTypeValues = "$Values.smithytypes" } object Kotlin { diff --git a/hll/hll-mapping-core/api/hll-mapping-core.api b/hll/hll-mapping-core/api/hll-mapping-core.api new file mode 100644 index 00000000000..c5289eab9dd --- /dev/null +++ b/hll/hll-mapping-core/api/hll-mapping-core.api @@ -0,0 +1,121 @@ +public abstract interface class aws/sdk/kotlin/hll/mapping/core/converters/Converter : aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom, aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo { +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/ConverterKt { + public static final fun Converter (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun andThenFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun andThenTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun validatingFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun validatingTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom { + public abstract fun convertFrom (Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFromKt { + public static final fun andThenConvertsFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public static final fun firstValidatingTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo { + public abstract fun convertTo (Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/ConvertsToKt { + public static final fun andThenConvertsTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public static final fun firstValidatingFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter : aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom, aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo { +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverterKt { + public static final fun mergeBy (Laws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/CollectionTypeConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/mapping/core/converters/collections/CollectionTypeConverters; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConverters; + public final fun mapConvertsFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun mapConvertsTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConvertersKt { + public static final fun mapFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConverters; + public final fun mapConvertsFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun mapConvertsKeysFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun mapConvertsKeysTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun mapConvertsTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun mapConvertsValuesFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun mapConvertsValuesTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun ofKeys (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun ofKeys (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun ofKeys (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun ofValues (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun ofValues (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun ofValues (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConvertersKt { + public static final fun mapFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapKeysFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapKeysTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapValuesFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapValuesTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConverters { + public static final field INSTANCE Laws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConverters; + public final fun mapConvertsFrom (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun mapConvertsTo (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom; + public final fun of (Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo;)Laws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo; +} + +public final class aws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConvertersKt { + public static final fun mapFrom (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; + public static final fun mapTo (Laws/sdk/kotlin/hll/mapping/core/converters/Converter;Laws/sdk/kotlin/hll/mapping/core/converters/Converter;)Laws/sdk/kotlin/hll/mapping/core/converters/Converter; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/util/Either { + public static final field Companion Laws/sdk/kotlin/hll/mapping/core/util/Either$Companion; +} + +public final class aws/sdk/kotlin/hll/mapping/core/util/Either$Companion { + public final fun Left (Ljava/lang/Object;)Laws/sdk/kotlin/hll/mapping/core/util/Either$Left; + public final fun Right (Ljava/lang/Object;)Laws/sdk/kotlin/hll/mapping/core/util/Either$Right; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/util/Either$Left : aws/sdk/kotlin/hll/mapping/core/util/Either { + public abstract fun getValue ()Ljava/lang/Object; +} + +public abstract interface class aws/sdk/kotlin/hll/mapping/core/util/Either$Right : aws/sdk/kotlin/hll/mapping/core/util/Either { + public abstract fun getValue ()Ljava/lang/Object; +} + +public final class aws/sdk/kotlin/hll/mapping/core/util/EitherKt { + public static final fun fold (Laws/sdk/kotlin/hll/mapping/core/util/Either;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun map (Laws/sdk/kotlin/hll/mapping/core/util/Either;Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/hll/mapping/core/util/Either; + public static final fun merge (Laws/sdk/kotlin/hll/mapping/core/util/Either;)Ljava/lang/Object; +} + diff --git a/hll/hll-mapping-core/build.gradle.kts b/hll/hll-mapping-core/build.gradle.kts new file mode 100644 index 00000000000..5490add9a6a --- /dev/null +++ b/hll/hll-mapping-core/build.gradle.kts @@ -0,0 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +description = "Common data mapping utilities used by AWS SDK for Kotlin's high level libraries" +extra["displayName"] = "AWS :: SDK :: Kotlin :: HLL :: Mapping" +extra["moduleName"] = "aws.sdk.kotlin.hll.mapping.core" diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/Converter.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/Converter.kt new file mode 100644 index 00000000000..65af4456255 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/Converter.kt @@ -0,0 +1,72 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters + +/** + * Models two-way conversion between a type [T] and a type [F] + * @param F The type being converted from + * @param T The type being converted to + */ +public interface Converter : + ConvertsTo, + ConvertsFrom + +/** + * Creates a new two-way converter from symmetrical one-way converters + * @param F The type being converted from + * @param T The type being converted to + * @param convertTo A converter instance for converting one-way from [F] to [T] + * @param convertFrom A converter instance for converting one-way from [T] to [F] + */ +public fun Converter(convertTo: ConvertsTo, convertFrom: ConvertsFrom): Converter = + object : Converter, ConvertsTo by convertTo, ConvertsFrom by convertFrom { } + +/** + * Chains this converter with another converter, yielding a new converter which performs a two-stage conversion. (Note + * that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps in their actual + * implementation.) + * @param F The source type of this converter + * @param T The target type of this converter and the source type of the given [converter] + * @param T2 The target type of the given [converter] + * @param converter The converter to chain together with this converter. Note that the source type of the given + * [converter] must be the same as the target type of this converter. + */ +public fun Converter.andThenTo(converter: Converter): Converter = + Converter(this.andThenConvertsTo(converter), converter.andThenConvertsFrom(this)) + +/** + * Chains this converter with another converter, yielding a new converter which performs a two-stage conversion. (Note + * that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps in their actual + * implementation.) + * @param F The source type of this converter and the target type of the given [converter] + * @param F2 The source type of the given [converter] + * @param T The target type of this converter + * @param converter The converter to chain together with this converter. Note that the target type of the given + * [converter] must be the same as the source type of this converter. + */ +public fun Converter.andThenFrom(converter: Converter): Converter = + Converter(converter.andThenConvertsTo(this), this.andThenConvertsFrom(converter)) + +/** + * Adds validation before conversions by running [validate] on [F] values before converting them to type [T]. Validators + * are expected to throw an exception if the expected condition is not met. + * @param F The type being converted from + * @param T The type being converted to + * @param validate A function which accepts an [F] value and throws an exception if the expected condition is not + * met + */ +public fun Converter.validatingFrom(validate: (F) -> Unit): Converter = + Converter(this.firstValidatingFrom(validate), this) + +/** + * Adds validation before conversions by running [validate] on [T] values before converting them to type [F]. Validators + * are expected to throw an exception if the expected condition is not met. + * @param F The type being converted to + * @param T The type being converted from + * @param validate A function which accepts a [T] value and throws an exception if the expected condition is not + * met + */ +public fun Converter.validatingTo(validate: (T) -> Unit): Converter = + Converter(this, this.firstValidatingTo(validate)) diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom.kt new file mode 100644 index 00000000000..b21c2495516 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsFrom.kt @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters + +/** + * Models one-way conversion from a type [T] to a type [F]. This type is similar to [ConvertsTo] but models conversion + * in the opposite direction. + * @param F The type being converted to + * @param T The type being converted from + */ +public fun interface ConvertsFrom { + /** + * Converts a single value from type [T] to type [F] + * @param to The value to convert + */ + public fun convertFrom(to: T): F +} + +/** + * Chains this converter with another converter, yielding a new converter which performs a two-stage conversion. (Note + * that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps in their actual + * implementation.) + * @param F The source type of this converter and the target type of the given [converter] + * @param F2 The source type of the given [converter] + * @param T The target type of this converter + * @param converter The converter to chain together with this converter. Note that the target type of the given + * [converter] must be the same as the source type of this converter. + */ +public fun ConvertsFrom.andThenConvertsFrom(converter: ConvertsFrom): ConvertsFrom = + ConvertsFrom { to: T -> converter.convertFrom(this.convertFrom(to)) } + +/** + * Adds validation before a conversion by running [validate] on [T] values before converting them to type [F]. + * Validators are expected to throw an exception if the expected condition is not met. + * @param F The type being converted to + * @param T The type being converted from + * @param validate A function which accepts a [T] value and throws an exception if the expected condition is not met + */ +public fun ConvertsFrom.firstValidatingTo(validate: (T) -> Unit): ConvertsFrom = + ConvertsFrom { to: T -> + validate(to) + this.convertFrom(to) + } diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo.kt new file mode 100644 index 00000000000..d3f40e0832e --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/ConvertsTo.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.mapping.core.converters + +/** + * Models one-way conversion from a type [F] to a type [T] + * @param F The type being converted from + * @param T The type being converted to + */ +public fun interface ConvertsTo { + /** + * Converts a single value from type [F] to type [T] + * @param from The value to convert + */ + public fun convertTo(from: F): T +} + +/** + * Chains this converter with another converter, yielding a new converter which performs a two-stage conversion. (Note + * that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps in their actual + * implementation.) + * @param F The source type of this converter + * @param T The target type of this converter and the source type of the given [converter] + * @param T2 The target type of the given [converter] + * @param converter The converter to chain together with this converter. Note that the source type of the given + * [converter] must be the same as the target type of this converter. + */ +public fun ConvertsTo.andThenConvertsTo(converter: ConvertsTo): ConvertsTo = + ConvertsTo { from: F -> converter.convertTo(this.convertTo(from)) } + +/** + * Adds validation before a conversion by running [validate] on [F] values before converting them to type [T]. + * Validators are expected to throw an exception if the expected condition is not met. + * @param F The type being converted from + * @param T The type being converted to + * @param validate A function which accepts an [F] value and throws an exception if the expected condition is not met + */ +public fun ConvertsTo.firstValidatingFrom(validate: (F) -> Unit): ConvertsTo = + ConvertsTo { from: F -> + validate(from) + this.convertTo(from) + } diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter.kt new file mode 100644 index 00000000000..c6469d7f430 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/SplittingConverter.kt @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters + +import aws.sdk.kotlin.hll.mapping.core.util.Either +import aws.sdk.kotlin.hll.mapping.core.util.map +import aws.sdk.kotlin.hll.mapping.core.util.merge + +/** + * Models partial, asymmetrical conversion between a type [F] and a type [T], where some condition internal to the + * converter splits the possible pathways data make take through conversion logic. One of these branches will be more + * complex and involve converting types [F] and [T] to types [F2] and [T2] (respectively). The remaining conversion + * between [F2] and [T2] will typically be delegated to another converter. + * + * Because they are partial and asymmetrical, [SplittingConverter] instances are typically not very useful on their own. + * Most often they are combined with another [Converter] via the [mergeBy] extension method, forming a complete, + * symmetrical converter between [F] and [T]. + * + * Splitting converters use the [Either] type to denote values which may follow the simple branch ([Either.Left]) or the + * complex branch ([Either.Right]). + * + * @param F The overall type being converted from + * @param F2 The intermediate type being converted from on the complex branch + * @param T2 The intermediate type being converted to on the complex branch + * @param T The overall type being converted to + */ +public interface SplittingConverter : + ConvertsTo>, + ConvertsFrom, T> + +/** + * Merges this [SplittingConverter] by delegating to a [Converter] instance that converts between types [F2] and [T2]. + * After the merge, a new [Converter] will be returned which fully converts between types [F] and [T]. + * @param F The overall type being converted from + * @param F2 The intermediate type being converted from on the complex branch, which is also the source type of + * [converter] + * @param T2 The intermediate type being converted to on the complex branch, which is also the target type of + * [converter] + * @param T The overall type being converted to + * @param converter A [Converter] between types [F2] and [T2] + */ +public fun SplittingConverter.mergeBy( + converter: Converter, +): Converter = + Converter( + convertTo = { from: F -> this@mergeBy.convertTo(from).map(converter::convertTo).merge() }, + convertFrom = { to: T -> this@mergeBy.convertFrom(to).map(converter::convertFrom).merge() }, + ) diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/CollectionTypeConverters.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/CollectionTypeConverters.kt new file mode 100644 index 00000000000..e1b4dc55f23 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/CollectionTypeConverters.kt @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters.collections + +import aws.sdk.kotlin.hll.mapping.core.converters.Converter + +/** + * Namespace for containing various conversion utilities dealing with mapping between collection types (e.g., [Set] to + * [List]) + */ +public object CollectionTypeConverters { + /** + * Creates a [Converter] which transforms between [Set] and [List] instances (both of some type [T]) + * @param T The type of elements in the [Set]/[List] + */ + @Suppress("ktlint:standard:function-naming") + public inline fun SetToListConverter(): Converter, List> = + Converter(Set::toList, List::toSet) +} diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConverters.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConverters.kt new file mode 100644 index 00000000000..3a333b29185 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/ListMappingConverters.kt @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters.collections + +import aws.sdk.kotlin.hll.mapping.core.converters.* + +/** + * Namespace for containing various conversion utilities dealing with [List] mapping + */ +public object ListMappingConverters { + /** + * Creates a one-way converter for transforming [List] with elements of type [T] to [List] with elements of type [F] + * @param F The type being converted to + * @param T The type being converted from + * @param elementConverter A one-way converter of [T] elements to [F] elements + */ + public fun of(elementConverter: ConvertsFrom): ConvertsFrom, List> = + ConvertsFrom { to: List -> to.map(elementConverter::convertFrom) } + + /** + * Chains this list converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param F The source element type of this converter and the target type of the given [elementConverter] + * @param F2 The source type of the given [elementConverter] + * @param T The target type of this converter + * @param elementConverter The element converter to chain together with this list converter. Note that the target + * type of the given [elementConverter] must be the same as the source element type of this converter. + */ + public fun ConvertsFrom, T>.mapConvertsFrom( + elementConverter: ConvertsFrom, + ): ConvertsFrom, T> = this.andThenConvertsFrom(of(elementConverter)) + + /** + * Creates a one-way converter for transforming [List] with elements of type [F] to [List] with elements of type [T] + * @param F The type being converted from + * @param T The type being converted to + * @param elementConverter A one-way converter of [F] elements to [T] elements + */ + public fun of(elementConverter: ConvertsTo): ConvertsTo, List> = + ConvertsTo { from: List -> from.map(elementConverter::convertTo) } + + /** + * Chains this list converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param F The source type of this converter + * @param T The target element type of this converter and the source type of the given [elementConverter] + * @param T2 The target type of the given [elementConverter] + * @param elementConverter The element converter to chain together with this list converter. Note that the source + * type of the given [elementConverter] must be the same as the target element type of this converter. + */ + public fun ConvertsTo>.mapConvertsTo( + elementConverter: ConvertsTo, + ): ConvertsTo> = this.andThenConvertsTo(of(elementConverter)) + + /** + * Creates a two-way converter for transforming between a [List] with elements of type [F] and a [List] with + * elements of type [T] + * @param F The type being converted from + * @param T The type being converted to + * @param elementConverter A [Converter] for transforming between elements of type [F] and [T] + */ + public fun of(elementConverter: Converter): Converter, List> = + Converter(of(elementConverter as ConvertsTo), of(elementConverter as ConvertsFrom)) +} + +/** + * Chains this list converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source element type of this converter and the target type of the given [elementConverter] + * @param F2 The source type of the given [elementConverter] + * @param T The target type of this converter + * @param elementConverter The element converter to chain together with this list converter. Note that the target type + * of the given [elementConverter] must be the same as the source element type of this converter. + */ +public fun Converter, T>.mapFrom(elementConverter: Converter): Converter, T> = + this.andThenFrom(ListMappingConverters.of(elementConverter)) + +/** + * Chains this list converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param T The target element type of this converter and the source type of the given [elementConverter] + * @param T2 The target type of the given [elementConverter] + * @param elementConverter The element converter to chain together with this list converter. Note that the source type + * of the given [elementConverter] must be the same as the target element type of this converter. + */ +public fun Converter>.mapTo(elementConverter: Converter): Converter> = + this.andThenTo(ListMappingConverters.of(elementConverter)) diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConverters.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConverters.kt new file mode 100644 index 00000000000..515a99182c2 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/MapMappingConverters.kt @@ -0,0 +1,380 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters.collections + +import aws.sdk.kotlin.hll.mapping.core.converters.* + +/** + * Namespace for containing various conversion utilities dealing with [Map] mapping + */ +public object MapMappingConverters { + /** + * Creates a one-way converter for transforming [Map] with keys of type [TK] to [Map] with keys of type [FK]. The + * values of the map are unchanged. + * @param FK The type of keys being converted to + * @param TK The type of keys being converted from + * @param V The type of values + * @param keyConverter A one-way converter of [TK] keys to [FK] keys + */ + public fun ofKeys(keyConverter: ConvertsFrom): ConvertsFrom, Map> = + ConvertsFrom { to: Map -> + to.mapKeys { e: Map.Entry -> keyConverter.convertFrom(e.key) } + } + + /** + * Chains this map converter with a key converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param FK The type of keys being converted from + * @param FK2 The type of keys being converted to + * @param V The type of values + * @param T The target type of this converter + * @param keyConverter The key converter to chain together with this map converter. Note that the target type of the + * given [keyConverter] must be the same as the source key type of this converter. + */ + public fun ConvertsFrom, T>.mapConvertsKeysFrom( + keyConverter: ConvertsFrom, + ): ConvertsFrom, T> = this.andThenConvertsFrom(ofKeys(keyConverter)) + + /** + * Creates a one-way converter for transforming [Map] with values of type [TV] to [Map] with values of type [FV]. + * The keys of the map are unchanged. + * @param K The type of keys + * @param FV The type of values being converted to + * @param TV The type of values being converted from + * @param valueConverter A one-way converter of [TV] values to [FV] values + */ + public fun ofValues( + valueConverter: ConvertsFrom, + ): ConvertsFrom, Map> = + ConvertsFrom { to: Map -> + to.mapValues { e: Map.Entry -> valueConverter.convertFrom(e.value) } + } + + /** + * Chains this map converter with a value converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param K The type of keys + * @param FV The type of values being converted to + * @param FV2 The type of values being converted from + * @param T The target type of this converter + * @param valueConverter The value converter to chain together with this map converter. Note that the target type of + * the given [valueConverter] must be the same as the source value type of this converter. + */ + public fun ConvertsFrom, T>.mapConvertsValuesFrom( + valueConverter: ConvertsFrom, + ): ConvertsFrom, T> = this.andThenConvertsFrom(ofValues(valueConverter)) + + /** + * Creates a one-way converter for transforming [Map] with keys of type [TK] and values of type [TV] to [Map] with + * keys of type [FK] and values of type [FV] + * @param FK The type of keys being converted to + * @param FV The type of values being converted to + * @param TK The type of keys being converted from + * @param TV The type of values being converted from + * @param entryConverter A one-way converter of [TK]/[TV] pairs to [FK]/[FV] pairs + */ + public fun of( + entryConverter: ConvertsFrom, Pair>, + ): ConvertsFrom, Map> = + ConvertsFrom { to: Map -> + to.entries.associate { e: Map.Entry -> entryConverter.convertFrom(e.toPair()) } + } + + /** + * Chains this map converter with an entry converter, yielding a new converter which performs a two stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param FK The type of keys being converted to + * @param FV The type of values being converted to + * @param FK2 The type of keys being converted from + * @param FV2 The type of values being converted from + * @param entryConverter The entry converter to chain together with this map converter. Note that the target types + * of the given [entryConverter] must be the same as the source types of this converter. + */ + public fun ConvertsFrom, T>.mapConvertsFrom( + entryConverter: ConvertsFrom, Pair>, + ): ConvertsFrom, T> = this.andThenConvertsFrom(of(entryConverter)) + + /** + * Creates a one-way converter for transforming [Map] with keys of type [FK] to [Map] with keys of type [TK]. The + * values of the map are unchanged. + * @param FK The type of keys being converted from + * @param TK The type of keys being converted to + * @param V The type of values + * @param keyConverter A one-way converter of [FK] keys to [TK] keys + */ + public fun ofKeys( + keyConverter: ConvertsTo, + ): ConvertsTo, Map> = + ConvertsTo { from: Map -> + from.mapKeys { e: Map.Entry -> keyConverter.convertTo(e.key) } + } + + /** + * Chains this map converter with a key converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param F The source type of this converter + * @param TK The type of keys being converted from + * @param TK2 The type of keys being converted to + * @param V The type of values + * @param keyConverter The key converter to chain together with this map converter. Note that the source type of the + * given [keyConverter] must be the same as the target key type of this converter. + */ + public fun ConvertsTo>.mapConvertsKeysTo( + keyConverter: ConvertsTo, + ): ConvertsTo> = this.andThenConvertsTo(ofKeys(keyConverter)) + + /** + * Creates a one-way converter for transforming [Map] with values of type [FV] to [Map] with values of type [TV]. + * The keys of the map are unchanged. + * @param K The type of keys + * @param FV The type of values being converted from + * @param TV The type of values being converted to + * @param valueConverter A one-way converter of [FV] values to [TV] values + */ + public fun ofValues( + valueConverter: ConvertsTo, + ): ConvertsTo, Map> = + ConvertsTo { from: Map -> + from.mapValues { e: Map.Entry -> valueConverter.convertTo(e.value) } + } + + /** + * Chains this map converter with a value converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param F The source type of this converter + * @param K The type of keys + * @param TV The type of values being converted from + * @param TV2 The type of values being converted to + * @param valueConverter The value converter to chain together with this map converter. Note that the source type of + * the given [valueConverter] must be the same as the target value type of this converter. + */ + public fun ConvertsTo>.mapConvertsValuesTo( + valueConverter: ConvertsTo, + ): ConvertsTo> = this.andThenConvertsTo(ofValues(valueConverter)) + + /** + * Creates a one-way converter for transforming [Map] with keys of type [FK] and values of type [FV] to [Map] with + * keys of type [TK] and values of type [TV] + * @param FK The type of keys being converted from + * @param FV The type of values being converted from + * @param TK The type of keys being converted to + * @param TV The type of values being converted to + * @param entryConverter A one-way converter of [FK]/[FV] pairs to [TK]/[TV] pairs + */ + public fun of( + entryConverter: ConvertsTo, Pair>, + ): ConvertsTo, Map> = + ConvertsTo { from: Map -> + from.entries.associate { e -> entryConverter.convertTo(e.toPair()) } + } + + /** + * Chains this map converter with an entry converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical + * steps in their actual implementation.) + * @param F The source type of this converter + * @param TK The type of keys being converted from + * @param TV The type of values being converted from + * @param TK2 The type of keys being converted to + * @param TV2 The type of values being converted to + * @param entryConverter The entry converter to chain together with this map converter. Note that the source types + * of the given [entryConverter] must be the same as the target types of this converter. + */ + public fun ConvertsTo>.mapConvertsTo( + entryConverter: ConvertsTo, Pair>, + ): ConvertsTo> = this.andThenConvertsTo(of(entryConverter)) + + /** + * Creates a two-way converter for transforming between [Map] with keys of type [FK] and [Map] with keys of type + * [TK]. The values of maps are unchanged. + * @param FK The type of keys being converted from + * @param TK The type of keys being converted to + * @param V The type of values + * @param keyConverter A converter for transforming between keys of type [FK] and [TK] + */ + public fun ofKeys( + keyConverter: Converter, + ): Converter, Map> = + Converter(ofKeys(keyConverter as ConvertsTo), ofKeys(keyConverter as ConvertsFrom)) + + /** + * Creates a two-way converter for transforming between [Map] with values of type [FV] and [Map] with values of type + * [TV]. The keys of the map are unchanged. + * @param K The type of keys + * @param FV The type of values being converted from + * @param TV The type of values being converted to + * @param valueConverter A converter for transforming between values of type [FV] and [TV] + */ + public fun ofValues( + valueConverter: Converter, + ): Converter, Map> = + Converter(ofValues(valueConverter as ConvertsTo), ofValues(valueConverter as ConvertsFrom)) + + /** + * Creates a two-way converter for transforming between [Map] with keys of type [FK] and values of type [FV] to + * [Map] with keys of type [TK] and values of type [TV] + * @param FK The type of keys being converted from + * @param FV The type of values being converted from + * @param TK The type of keys being converted to + * @param TV The type of values being converted to + * @param entryConverter A converter for transforming between [FK]/[FV] pairs and [TK]/[TV] pairs + */ + public fun of( + entryConverter: Converter, Pair>, + ): Converter, Map> = + Converter( + of(entryConverter as ConvertsTo, Pair>), + of(entryConverter as ConvertsFrom, Pair>), + ) +} + +/** + * Chains this map converter with a key converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param FK The type of keys being converted to + * @param FK2 The type of keys being converted from + * @param V The type of values + * @param T The target type of this converter + * @param keyConverter The key converter to chain together with this map converter. Note that the target key type of the + * given [keyConverter] must be the same as the source key type of this converter. + */ +public fun Converter, T>.mapKeysFrom( + keyConverter: Converter, +): Converter, T> = this.andThenFrom(MapMappingConverters.ofKeys(keyConverter)) + +/** + * Chains this map converter with a key converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param TK The type of keys being converted from + * @param TK2 The type of keys being converted to + * @param V The type of values + * @param keyConverter The key converter to chain together with this map converter. Note that the source key type of the + * given [keyConverter] must be the same as the target key type of this converter. + */ +public fun Converter>.mapKeysTo( + keyConverter: Converter, +): Converter> = this.andThenTo(MapMappingConverters.ofKeys(keyConverter)) + +/** + * Chains this map converter with a value converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param K The type of keys + * @param FV The type of values being converted to + * @param FV2 The type of values being converted from + * @param T The target type of this converter + * @param valueConverter The value converter to chain together with this map converter. Note that the target value type + * of the given [valueConverter] must be the same as the source value type of this converter. + */ +public fun Converter, T>.mapValuesFrom( + valueConverter: Converter, +): Converter, T> = this.andThenFrom(MapMappingConverters.ofValues(valueConverter)) + +/** + * Chains this map converter with a value converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param K The type of keys + * @param TV The type of values being converted from + * @param TV2 The type of values being converted to + * @param valueConverter The value converter to chain together with this map converter. Note that the source value type + * of the given [valueConverter] must be the same as the target value type of this converter. + */ +public fun Converter>.mapValuesTo( + valueConverter: Converter, +): Converter> = this.andThenTo(MapMappingConverters.ofValues(valueConverter)) + +/** + * Convenience function for combining independent converters into `Converter, Pair>`, suitable for + * use as a map entry converter + * @param F1 The first type of value being converted from + * @param T1 The first type of value being converted to + * @param F2 The second type of value being converted from + * @param T2 The second type of value being converted to + * @param other The converter to zip with this one + */ +private fun Converter.zip(other: Converter) = Converter( + convertTo = { from: Pair -> this.convertTo(from.first) to other.convertTo(from.second) }, + convertFrom = { to: Pair -> this.convertFrom(to.first) to other.convertFrom(to.second) }, +) + +/** + * Chains this map converter with an entry converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param FK The type of keys being converted to + * @param FV The type of values being converted to + * @param FK2 The type of keys being converted from + * @param FV2 The type of values being converted from + * @param T The target type of this converter + * @param entryConverter The entry converter to chain together with this map converter. Note that the target types of + * the given [entryConverter] must be the same as the source types of this converter. + */ +public fun Converter, T>.mapFrom( + entryConverter: Converter, Pair>, +): Converter, T> = this.andThenFrom(MapMappingConverters.of(entryConverter)) + +/** + * Chains this map converter with a key converter and a value converter, yielding a new converter which performs a + * two-stage mapping conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of + * multiple logical steps in their actual implementation.) + * @param FK The type of keys being converted to + * @param FV The type of values being converted to + * @param FK2 The type of keys being converted from + * @param FV2 The type of values being converted from + * @param T The target type of this converter + * @param keyConverter The key converter to chain together with this map converter. Note that the target key type of the + * given [keyConverter] must be the same as the source key type of this converter. + * @param valueConverter The value converter to chain together with this map converter. Note that the target value type + * of the given [valueConverter] must be the same as the source value type of this converter. + */ +public fun Converter, T>.mapFrom( + keyConverter: Converter, + valueConverter: Converter, +): Converter, T> = mapFrom(keyConverter.zip(valueConverter)) + +/** + * Chains this map converter with an entry converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param TK The type of keys being converted from + * @param TV The type of values being converted from + * @param TK2 The type of keys being converted to + * @param TV2 The type of values being converted to + * @param entryConverter The entry converter to chain together with this map converter. Note that the source types of + * the given [entryConverter] must be the same as the target types of this converter. + */ +public fun Converter>.mapTo( + entryConverter: Converter, Pair>, +): Converter> = this.andThenTo(MapMappingConverters.of(entryConverter)) + +/** + * Chains this map converter with a key converter and a value converter, yielding a new converter which performs a + * two-stage mapping conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of + * multiple logical steps in their actual implementation.) + * @param F The source type of this converter + * @param TK The type of keys being converted from + * @param TV The type of values being converted from + * @param TK2 The type of keys being converted to + * @param TV2 The type of values being converted to + * @param keyConverter The key converter to chain together with this map converter. Note that the source key type of the + * given [keyConverter] must be the same as the target key type of this converter. + * @param valueConverter The value converter to chain together with this map converter. Note that the source value type + * of the given [valueConverter] must be the same as the target value type of this converter. + */ +public fun Converter>.mapTo( + keyConverter: Converter, + valueConverter: Converter, +): Converter> = mapTo(keyConverter.zip(valueConverter)) diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConverters.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConverters.kt new file mode 100644 index 00000000000..585f8b54544 --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/converters/collections/SetMappingConverters.kt @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.converters.collections + +import aws.sdk.kotlin.hll.mapping.core.converters.* + +/** + * Namespace for containing various conversion utilities dealing with [Set] mapping + */ +public object SetMappingConverters { + /** + * Creates a one-way converter for transforming [Set] with elements of type [T] to [Set] with elements of type [F] + * @param F The type being converted to + * @param T The type being converted from + * @param elementConverter A one-way converter of [T] values to [F] values + */ + public fun of(elementConverter: ConvertsFrom): ConvertsFrom, Set> = + ConvertsFrom { to: Set -> to.map(elementConverter::convertFrom).toSet() } + + /** + * Chains this set converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source element type of this converter and the target type of the given [elementConverter] + * @param F2 The source type of the given [elementConverter] + * @param T The target type of this converter + * @param elementConverter The element converter to chain together with this set converter. Note that the target type + * of the given [elementConverter] must be the same as the source element type of this converter. + */ + public fun ConvertsFrom, T>.mapConvertsFrom( + elementConverter: ConvertsFrom, + ): ConvertsFrom, T> = this.andThenConvertsFrom(of(elementConverter)) + + /** + * Creates a one-way converter for transforming [Set] with elements of type [F] to [Set] with elements of type [T] + * @param F The type being converted from + * @param T The type being converted to + * @param elementConverter A one-way converter of [F] values to [T] values + */ + public fun of(elementConverter: ConvertsTo): ConvertsTo, Set> = + ConvertsTo { from: Set -> from.map(elementConverter::convertTo).toSet() } + + /** + * Chains this set converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param T The target element type of this converter and the source type of the given [elementConverter] + * @param T2 The target type of the given [elementConverter] + * @param elementConverter The element converter to chain together with this set converter. Note that the source type + * of the given [elementConverter] must be the same as the target element type of this converter. + */ + public fun ConvertsTo>.mapConvertsTo( + elementConverter: ConvertsTo, + ): ConvertsTo> = this.andThenConvertsTo(of(elementConverter)) + + /** + * Creates a two-way converter for transforming between a [Set] with elements of type [F] and a [Set] with elements + * of type [T] + * @param F The type being converted from + * @param T The type being converted to + * @param elementConverter A [Converter] for transforming between values of type [F] and [T] + */ + public fun of(elementConverter: Converter): Converter, Set> = + Converter(of(elementConverter as ConvertsTo), of(elementConverter as ConvertsFrom)) +} + +/** + * Chains this set converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source element type of this converter and the target type of the given [elementConverter] + * @param F2 The source type of the given [elementConverter] + * @param T The target type of this converter + * @param elementConverter The element converter to chain together with this set converter. Note that the target type + * of the given [elementConverter] must be the same as the source element type of this converter. + */ +public fun Converter, T>.mapFrom(elementConverter: Converter): Converter, T> = + this.andThenFrom(SetMappingConverters.of(elementConverter)) + +/** + * Chains this set converter with an element converter, yielding a new converter which performs a two-stage mapping + * conversion. (Note that these two "stages" are conceptual. Each of these stages may consist of multiple logical steps + * in their actual implementation.) + * @param F The source type of this converter + * @param T The target element type of this converter and the source type of the given [elementConverter] + * @param T2 The target type of the given [elementConverter] + * @param elementConverter The element converter to chain together with this set converter. Note that the source type + * of the given [elementConverter] must be the same as the target element type of this converter. + */ +public fun Converter>.mapTo(elementConverter: Converter): Converter> = + this.andThenTo(SetMappingConverters.of(elementConverter)) diff --git a/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/util/Either.kt b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/util/Either.kt new file mode 100644 index 00000000000..2d0669b282f --- /dev/null +++ b/hll/hll-mapping-core/common/src/aws/sdk/kotlin/hll/mapping/core/util/Either.kt @@ -0,0 +1,91 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.hll.mapping.core.util + +/** + * Represents a value which may be one of two possible types: [L] or [R]. An instance of this type will be either [Left] + * or [Right]. + * + * By convention [Either] is **right-biased**, meaning that [Right] values are the default values to operate on (e.g., + * via [map]) and [Left] value are typically unmodified. This lends itself to using [R]/[Right] for values which may + * require more processing and using [L]/[Left] for values which are relatively "final". + * + * @param L The type of [Left] values + * @param R The type of [Right] values + */ +public sealed interface Either { + /** + * The left side of an [Either] + * @param L The type of values held in this class + */ + public interface Left : Either { + /** + * An [L] value + */ + public val value: L + } + + /** + * The right side of an [Either] + * @param R The type of values held in this class + */ + public interface Right : Either { + /** + * An [R] value + */ + public val value: R + } + + public companion object { + /** + * Creates a new [Left] with the given [value] + * @param L The type of values held in this class + * @param value An [L] value + */ + public fun Left(value: L): Left = LeftImpl(value) + + /** + * Creates a new [Right] with the given [value] + * @param R The type of values held in this class + * @param value An [R] value + */ + public fun Right(value: R): Right = RightImpl(value) + } +} + +private data class LeftImpl(override val value: L) : Either.Left + +private data class RightImpl(override val value: R) : Either.Right + +/** + * Map the right value of this [Either] to a new value. Left values are unmodified. + * @param L The type of left value + * @param R The current type of right value + * @param R2 The new type of right value + * @param func A mapping function which turns an [R] into an [R2] + */ +public inline fun Either.map(func: (right: R) -> R2): Either = when (this) { + is Either.Left -> this + is Either.Right -> Either.Right(func(value)) +} + +/** + * Transform this [Either] into a value of type [T] via specialized mapping functions for both left and right values + * @param L The current type of left value + * @param R The current type of right value + * @param T The type of output value + * @param ifLeft A function for converting [L] values to [T] + * @param ifRight A function for converting [R] values to [T] + */ +public inline fun Either.fold(ifLeft: (left: L) -> T, ifRight: (right: R) -> T): T = when (this) { + is Either.Left -> ifLeft(value) + is Either.Right -> ifRight(value) +} + +/** + * Returns the left value or right value + * @param T The type of values in left/right + */ +public fun Either.merge(): T = fold({ it }, { it }) diff --git a/settings.gradle.kts b/settings.gradle.kts index b9e3bedf097..469f5f77901 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,6 +41,7 @@ include(":aws-runtime:aws-endpoint") include(":aws-runtime:aws-http") include(":hll") include(":hll:hll-codegen") +include(":hll:hll-mapping-core") include(":services") include(":tests") include(":tests:codegen:event-stream")