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

software.amazon.smithy.kotlin.codegen.rendering.ShapeValueGenerator.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

import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.model.SymbolProperty
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.node.*
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.model.traits.StreamingTrait

/**
 * Generates a shape type declaration based on the parameters provided.
 */
class ShapeValueGenerator(
    internal val model: Model,
    internal val symbolProvider: SymbolProvider
) {

    /**
     * Writes generation of a shape value type declaration for the given the parameters.
     *
     * @param writer writer to write generated code with.
     * @param shape the shape that will be declared.
     * @param params parameters to fill the generated shape declaration.
     */
    fun writeShapeValueInline(writer: KotlinWriter, shape: Shape, params: Node) {
        val nodeVisitor = ShapeValueNodeVisitor(writer, this, shape)
        when (shape.type) {
            ShapeType.STRUCTURE -> classDeclaration(writer, shape.asStructureShape().get()) {
                params.accept(nodeVisitor)
            }
            ShapeType.MAP -> mapDeclaration(writer, shape.asMapShape().get()) {
                params.accept(nodeVisitor)
            }
            ShapeType.LIST, ShapeType.SET -> collectionDeclaration(writer, shape as CollectionShape) {
                params.accept(nodeVisitor)
            }
            else -> primitiveDeclaration(writer, shape) {
                params.accept(nodeVisitor)
            }
        }
    }

    private fun classDeclaration(writer: KotlinWriter, shape: StructureShape, block: () -> Unit) {
        val symbol = symbolProvider.toSymbol(shape)
        // invoke the generated DSL builder for the class
        writer.writeInline("#L {\n", symbol.name)
            .indent()
            .call { block() }
            .dedent()
            .write("")
            .write("}")
    }

    private fun mapDeclaration(writer: KotlinWriter, shape: MapShape, block: () -> Unit) {
        writer.pushState()
        writer.trimTrailingSpaces(false)

        val collectionGeneratorFunction = symbolProvider.toSymbol(shape).expectProperty(SymbolProperty.IMMUTABLE_COLLECTION_FUNCTION)

        writer.writeInline("$collectionGeneratorFunction(\n")
            .indent()
            .call { block() }
            .dedent()
            .write("")
            .write(")")

        writer.popState()
    }

    private fun collectionDeclaration(writer: KotlinWriter, shape: CollectionShape, block: () -> Unit) {
        writer.pushState()
        writer.trimTrailingSpaces(false)

        val collectionGeneratorFunction = symbolProvider.toSymbol(shape).expectProperty(SymbolProperty.IMMUTABLE_COLLECTION_FUNCTION)

        writer.writeInline("$collectionGeneratorFunction(\n")
            .indent()
            .call { block() }
            .dedent()
            .write("")
            .write(")")

        writer.popState()
    }

    private fun primitiveDeclaration(writer: KotlinWriter, shape: Shape, block: () -> Unit) {
        val blobHandlingSymbols = listOf(
            RuntimeTypes.Core.Content.ByteArrayContent,
            RuntimeTypes.Core.Content.ByteStream,
            RuntimeTypes.Core.Content.StringContent,
            RuntimeTypes.Core.Content.toByteArray
        )
        val suffix = when (shape.type) {
            ShapeType.STRING -> {
                if (shape.hasTrait()) {
                    val symbol = symbolProvider.toSymbol(shape)
                    writer.writeInline("#L.fromValue(", symbol.name)
                    ")"
                } else {
                    ""
                }
            }
            ShapeType.BLOB -> {
                if (shape.hasTrait()) {
                    writer.addImport(blobHandlingSymbols)
                    writer.writeInline("StringContent(")
                    ")"
                } else {
                    // blob params are spit out as strings
                    ".encodeAsByteArray()"
                }
            }
            else -> ""
        }

        block()

        if (suffix.isNotBlank()) {
            writer.writeInline(suffix)
        }
    }

    /**
     * NodeVisitor to walk shape value declarations with node values.
     */
    private class ShapeValueNodeVisitor(
        val writer: KotlinWriter,
        val generator: ShapeValueGenerator,
        val currShape: Shape
    ) : NodeVisitor {

        override fun objectNode(node: ObjectNode) {
            var i = 0
            node.members.forEach { (keyNode, valueNode) ->
                val memberShape: Shape
                when (currShape) {
                    is StructureShape -> {
                        val member = currShape.getMember(keyNode.value).orElseThrow {
                            CodegenException("unknown member ${currShape.id}.${keyNode.value}")
                        }
                        memberShape = generator.model.expectShape(member.target)
                        val memberName = generator.symbolProvider.toMemberName(member)
                        writer.writeInline("#L = ", memberName)
                        generator.writeShapeValueInline(writer, memberShape, valueNode)
                        if (i < node.members.size - 1) {
                            writer.write("")
                        }
                    }
                    is MapShape -> {
                        memberShape = generator.model.expectShape(currShape.value.target)
                        writer.writeInline("#S to ", keyNode.value)

                        if (valueNode is NullNode) {
                            writer.write("null")
                        } else {
                            generator.writeShapeValueInline(writer, memberShape, valueNode)
                            if (i < node.members.size - 1) {
                                writer.writeInline(",\n")
                            }
                        }
                    }
                    is DocumentShape -> {
                        // TODO - deal with document shapes
                    }
                    is UnionShape -> {
                        val member = currShape.getMember(keyNode.value).orElseThrow {
                            CodegenException("unknown member ${currShape.id}.${keyNode.value}")
                        }
                        memberShape = generator.model.expectShape(member.target)
                        val currSymbol = generator.symbolProvider.toSymbol(currShape)
                        val memberName = generator.symbolProvider.toMemberName(member)
                        val variantName = memberName.replaceFirstChar { c -> c.uppercaseChar() }
                        writer.writeInline("${currSymbol.name}.$variantName(")
                        generator.writeShapeValueInline(writer, memberShape, valueNode)
                        writer.write(")")
                    }
                    else -> throw CodegenException("unexpected shape type " + currShape.type)
                }
                i++
            }
        }

        override fun stringNode(node: StringNode) {
            when (currShape.type) {
                ShapeType.DOUBLE,
                ShapeType.FLOAT -> {
                    val symbolName = generator.symbolProvider.toSymbol(currShape).name
                    val symbolMember = when (node.value) {
                        "Infinity" -> "POSITIVE_INFINITY"
                        "-Infinity" -> "NEGATIVE_INFINITY"
                        "NaN" -> "NaN"
                        else -> throw CodegenException("""Cannot interpret $symbolName value "${node.value}".""")
                    }
                    writer.writeInline("#L", "$symbolName.$symbolMember")
                }

                else -> writer.writeInline("#S", node.value)
            }
        }

        override fun nullNode(node: NullNode) {
            writer.writeInline("null")
        }

        override fun arrayNode(node: ArrayNode) {
            val memberShape = generator.model.expectShape((currShape as CollectionShape).member.target)
            var i = 0
            node.elements.forEach { element ->
                generator.writeShapeValueInline(writer, memberShape, element)
                if (i < node.elements.size - 1) {
                    writer.writeInline(",\n")
                }
                i++
            }
        }

        override fun numberNode(node: NumberNode) {
            when (currShape.type) {
                ShapeType.TIMESTAMP -> {
                    writer.addImport("${KotlinDependency.CORE.namespace}.time", "Instant")
                    writer.writeInline("Instant.fromEpochSeconds(#L, 0)", node.value)
                }

                ShapeType.BYTE, ShapeType.SHORT, ShapeType.INTEGER,
                ShapeType.LONG -> writer.writeInline("#L", node.value)

                // ensure float/doubles that are represented as integers in the params get converted
                // since Kotlin doesn't support implicit conversions (e.g. '1' cannot be implicitly converted
                // to a Kotlin float/double)
                ShapeType.FLOAT -> writer.writeInline("#L.toFloat()", node.value)
                ShapeType.DOUBLE -> writer.writeInline("#L.toDouble()", node.value)

                ShapeType.BIG_INTEGER, ShapeType.BIG_DECIMAL -> {
                    // TODO - We need to decide non-JVM only symbols to generate for these before we know how to assign values to them
                }

                ShapeType.DOCUMENT -> {
                    // TODO - deal with document shapes
                }

                else -> throw CodegenException("unexpected shape type $currShape for numberNode")
            }
        }

        override fun booleanNode(node: BooleanNode) {
            when (currShape.type) {
                ShapeType.DOCUMENT -> {
                    // TODO - deal with document shapes
                }
                ShapeType.BOOLEAN -> writer.writeInline("#L", if (node.value) "true" else "false")
                else -> throw CodegenException("unexpected shape type $currShape for boolean value")
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy