All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.kotlin.codegen.rendering.serde.SerdeExt.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.smithy.kotlin.codegen.rendering.serde

import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.codegen.core.SymbolReference
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.core.SymbolRenderer
import software.amazon.smithy.kotlin.codegen.core.defaultName
import software.amazon.smithy.kotlin.codegen.core.mangledSuffix
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.utils.StringUtils

/**
 * Get the field descriptor name for a member shape
 */
fun MemberShape.descriptorName(childName: String = ""): String = "${this.defaultName()}${childName}_DESCRIPTOR".uppercase()

/**
 * Get the serializer class name for an operation.
 */
fun OperationShape.serializerName(): String = StringUtils.capitalize(this.id.name) + "OperationSerializer"

/**
 * Get name of the function responsible for serializing an operation's body (paylaod)
 */
fun OperationShape.bodySerializerName(): String = "serialize" + StringUtils.capitalize(this.id.name) + "OperationBody"

/**
 * Get the function responsible for serializing an operation's body (payload) as a [Symbol] and register [block]
 * which will be invoked to actually render the function (signature and implementation)
 */
fun OperationShape.bodySerializer(
    settings: KotlinSettings,
    block: SymbolRenderer,
): Symbol = buildSymbol {
    name = bodySerializerName()
    namespace = settings.pkg.serde
    // place body serializer in same file as operation serializer implementation
    definitionFile = "${serializerName()}.kt"
    renderBy = block
}

/**
 * Get the deserializer class name for an operation. Operation outputs can be deserialized from the protocol (e.g. HTTP)
 * and/or the document/payload.
 */
fun OperationShape.deserializerName(): String = StringUtils.capitalize(this.id.name) + "OperationDeserializer"

/**
 * Get name of the function responsible for deserializing an operation's body (paylaod)
 */
fun OperationShape.bodyDeserializerName(): String = "deserialize" + StringUtils.capitalize(this.id.name) + "OperationBody"

/**
 * Get the function responsible for deserializing an operation's body (payload) as a [Symbol] and register [block]
 * which will be invoked to actually render the function (signature and implementation)
 */
fun OperationShape.bodyDeserializer(
    settings: KotlinSettings,
    block: SymbolRenderer,
): Symbol = buildSymbol {
    name = bodyDeserializerName()
    namespace = settings.pkg.serde
    // place body serializer in same file as operation serializer implementation
    definitionFile = "${deserializerName()}.kt"
    renderBy = block
}

/**
 * Get the serializer class name for a shape bound to the document/payload
 */
// TODO - removed and replace with Shape.documentSerializerName(members: Collection). SerializeStructGenerator still uses this though
internal fun Symbol.documentSerializerName(): String = "serialize" + StringUtils.capitalize(this.name) + "Document"

/**
 * Get the [Symbol] responsible for serializing the current shape and [members] into the document/payload
 * and register [block] which will be invoked to actually render the function (signature and implementation)
 */
fun Shape.documentSerializer(
    settings: KotlinSettings,
    symbol: Symbol,
    members: Collection = members(),
    block: SymbolRenderer,
): Symbol {
    val base = symbol.documentSerializerName()
    val suffix = mangledSuffix(members)

    return buildSymbol {
        name = "$base$suffix"
        namespace = settings.pkg.serde
        definitionFile = "${symbol.name}DocumentSerializer.kt"
        reference(symbol, SymbolReference.ContextOption.DECLARE)
        renderBy = block
    }
}

/**
 * Get the deserializer class name for a shape bound to the document/payload
 */
// TODO - removed and replace with Shape.documentDeserializerName(members: Collection). DeserializeStructGenerator still uses this though
internal fun Symbol.documentDeserializerName(): String = "deserialize" + StringUtils.capitalize(this.name) + "Document"

/**
 * Get the [Symbol] responsible for deserializing the current shape and [members] from the document/payload
 * and register [block] which will be invoked to actually render the function (signature and implementation)
 */
fun Shape.documentDeserializer(
    settings: KotlinSettings,
    symbol: Symbol,
    members: Collection = members(),
    block: SymbolRenderer,
): Symbol {
    val base = "deserialize" + StringUtils.capitalize(symbol.name) + "Document"
    val suffix = mangledSuffix(members)

    return buildSymbol {
        name = "$base$suffix"
        namespace = settings.pkg.serde
        definitionFile = "${symbol.name}DocumentDeserializer.kt"
        reference(symbol, SymbolReference.ContextOption.DECLARE)
        renderBy = block
    }
}

/**
 * Get the deserializer name for an error shape
 */
fun Symbol.errorDeserializerName(): String = "deserialize" + StringUtils.capitalize(this.name) + "Error"

/**
 * Get the function responsible for deserializing members bound to the payload of an error shape as [Symbol] and
 * register [block] * which will be invoked to actually render the function (signature and implementation)
 */
fun Symbol.errorDeserializer(settings: KotlinSettings, block: SymbolRenderer): Symbol = buildSymbol {
    name = errorDeserializerName()
    namespace = settings.pkg.serde
    val symbol = this@errorDeserializer
    // place it in the same file as the exception deserializer, e.g. for HTTP protocols this will be in
    // same file as HttpDeserialize
    definitionFile = "${symbol.name}Deserializer.kt"
    reference(symbol, SymbolReference.ContextOption.DECLARE)
    renderBy = block
}

/**
 * Get the function responsible for deserializing the specific shape as a standalone payload
 */
fun Shape.payloadDeserializer(
    settings: KotlinSettings,
    symbol: Symbol,
    members: Collection = members(),
    block: SymbolRenderer,
): Symbol {
    val base = "deserialize" + StringUtils.capitalize(symbol.name) + "Payload"
    val suffix = mangledSuffix(members)
    return buildSymbol {
        name = "$base$suffix"
        namespace = settings.pkg.serde
        definitionFile = "${symbol.name}PayloadDeserializer.kt"
        reference(symbol, SymbolReference.ContextOption.DECLARE)
        renderBy = block
    }
}

/**
 * Get the function responsible for serializing the specific shape as a standalone payload
 */
fun Shape.payloadSerializer(
    settings: KotlinSettings,
    symbol: Symbol,
    members: Collection = members(),
    block: SymbolRenderer,
): Symbol {
    val base = "serialize" + StringUtils.capitalize(symbol.name) + "Payload"
    val suffix = mangledSuffix(members)
    return buildSymbol {
        name = "$base$suffix"
        namespace = settings.pkg.serde
        definitionFile = "${symbol.name}PayloadSerializer.kt"
        reference(symbol, SymbolReference.ContextOption.DECLARE)
        renderBy = block
    }
}

/**
 * Format an instance of `Instant` using the given [tsFmt]
 * @param paramName The name of the local identifier to format
 * @param tsFmt The timestamp format to use
 * @param forceString Force the result of the expression returned to be a [String] when generated
 */
fun formatInstant(paramName: String, tsFmt: TimestampFormatTrait.Format, forceString: Boolean = false): String = when (tsFmt) {
    TimestampFormatTrait.Format.EPOCH_SECONDS -> {
        // default to epoch seconds as a double
        if (forceString) {
            "$paramName.format(TimestampFormat.EPOCH_SECONDS)"
        } else {
            "$paramName.toEpochDouble()"
        }
    }
    TimestampFormatTrait.Format.DATE_TIME -> "$paramName.format(TimestampFormat.ISO_8601)"
    TimestampFormatTrait.Format.HTTP_DATE -> "$paramName.format(TimestampFormat.RFC_5322)"
    else -> throw CodegenException("unknown timestamp format: $tsFmt")
}

/**
 * return the conversion function `Instant.fromXYZ(paramName)` for the given format
 *
 * @param paramName The name of the local identifier to convert to an `Instant`
 * @param tsFmt The timestamp format [paramName] is expected to be converted from
 */
fun parseInstant(paramName: String, tsFmt: TimestampFormatTrait.Format): String = when (tsFmt) {
    TimestampFormatTrait.Format.EPOCH_SECONDS -> "Instant.fromEpochSeconds($paramName)"
    TimestampFormatTrait.Format.DATE_TIME -> "Instant.fromIso8601($paramName)"
    TimestampFormatTrait.Format.HTTP_DATE -> "Instant.fromRfc5322($paramName)"
    else -> throw CodegenException("unknown timestamp format: $tsFmt")
}

/**
 * Get the serde SerialKind for a shape
 */
fun Shape.serialKind(): String = when (this.type) {
    ShapeType.BOOLEAN -> "SerialKind.Boolean"
    ShapeType.BYTE -> "SerialKind.Byte"
    ShapeType.SHORT -> "SerialKind.Short"
    ShapeType.INTEGER -> "SerialKind.Integer"
    ShapeType.LONG -> "SerialKind.Long"
    ShapeType.FLOAT -> "SerialKind.Float"
    ShapeType.DOUBLE -> "SerialKind.Double"
    ShapeType.STRING -> "SerialKind.String"
    ShapeType.ENUM -> "SerialKind.Enum"
    ShapeType.INT_ENUM -> "SerialKind.IntEnum"
    ShapeType.BLOB -> "SerialKind.Blob"
    ShapeType.TIMESTAMP -> "SerialKind.Timestamp"
    ShapeType.DOCUMENT -> "SerialKind.Document"
    ShapeType.BIG_INTEGER, ShapeType.BIG_DECIMAL -> "SerialKind.BigNumber"
    ShapeType.LIST -> "SerialKind.List"
    ShapeType.SET -> "SerialKind.List"
    ShapeType.MAP -> "SerialKind.Map"
    ShapeType.STRUCTURE -> "SerialKind.Struct"
    ShapeType.UNION -> "SerialKind.Struct"
    else -> throw CodegenException("unknown SerialKind for ${this.type} ($this)")
}

/**
 * Specifies the type of value the identifier represents
 */
internal enum class NestedIdentifierType(val prefix: String) {
    KEY("k"), // Generated variable names for map keys
    VALUE("v"), // Generated variable names for map values
    ELEMENT("el"), // Generated variable name for list elements
    COLLECTION("col"), // Generated variable name for collection types (list, set)
    MAP("map"), // Generated variable name for map type
}

/**
 * Generate an identifier for a given nesting level
 * @param type intended type of value
 */
internal fun Int.variableNameFor(type: NestedIdentifierType): String = "${type.prefix}$this"

/**
 * Generate an identifier for a given nesting level
 */
internal fun Int.nestedDescriptorName(): String = "_C$this"

/**
 * Returns true if the shape can contain other shapes
 */
internal fun Shape.isContainerShape() = when (this) {
    is CollectionShape,
    is MapShape,
    -> true
    else -> false
}

/**
 * Returns [Shape] of the child member of the passed Shape is a collection type or null if not collection type.
 */
internal fun Shape.childShape(model: Model): Shape? = when (this) {
    is CollectionShape -> model.expectShape(this.member.target)
    is MapShape -> model.expectShape(this.value.target)
    else -> null
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy