Unverified Commit 2931c9e1 authored by Matteo Bigoi's avatar Matteo Bigoi Committed by GitHub
Browse files

Initial awsJson 1.0 and 1.1 server implementation (#1279)



To support these new protocols, we also changed the runtime Router definition as for both awsJson 1.0 and 1.1, every request MUST be sent to the root URL (/) using the HTTP "POST" method.

The runtime Router now supports explicitly all the available protocols.

There are still some protocol tests failing for awsJson 1.0 and 1.1. The failure are caused by the missing implementation of @endpoint trait and date parsing. Protocol tests for these 2 protocols are heavily biased towards Responses. Before announcing support for awsJson 1.0 and 1.1, we should increase the protocol tests coverage.

Signed-off-by: default avatarBigo <1781140+crisidev@users.noreply.github.com>
Co-authored-by: default avatardavid-perez <d@vidp.dev>
parent a870d2ad
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ val allCodegenTests = listOf(
    CodegenTest("com.amazonaws.simple#SimpleService", "simple"),
    CodegenTest("aws.protocoltests.restjson#RestJson", "rest_json"),
    CodegenTest("aws.protocoltests.restjson.validation#RestJsonValidation", "rest_json_validation"),
    CodegenTest("aws.protocoltests.json10#JsonRpc10", "json_rpc10"),
    CodegenTest("aws.protocoltests.json#JsonProtocol", "json_rpc11"),
    CodegenTest("aws.protocoltests.misc#MiscService", "misc"),
    CodegenTest("com.amazonaws.ebs#Ebs", "ebs"),
    CodegenTest("com.amazonaws.s3#AmazonS3", "s3"),
+3 −0
Original line number Diff line number Diff line
@@ -38,4 +38,7 @@ object ServerRuntimeType {

    fun ResponseRejection(runtimeConfig: RuntimeConfig) =
        RuntimeType("ResponseRejection", ServerCargoDependency.SmithyHttpServer(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server::rejection")

    fun Protocol(runtimeConfig: RuntimeConfig) =
        RuntimeType("Protocol", ServerCargoDependency.SmithyHttpServer(runtimeConfig), "${runtimeConfig.crateSrcPrefix}_http_server::protocols")
}
+41 −5
Original line number Diff line number Diff line
@@ -5,6 +5,10 @@

package software.amazon.smithy.rust.codegen.server.smithy.generators

import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.rustlang.Attribute
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
@@ -28,7 +32,9 @@ class ServerOperationRegistryGenerator(
    private val httpBindingResolver: HttpBindingResolver,
    private val operations: List<OperationShape>,
) {
    private val protocol = codegenContext.protocol
    private val symbolProvider = codegenContext.symbolProvider
    private val serviceName = codegenContext.serviceShape.toShapeId().name
    private val operationNames = operations.map { symbolProvider.toSymbol(it).name.toSnakeCase() }
    private val runtimeConfig = codegenContext.runtimeConfig
    private val codegenScope = arrayOf(
@@ -223,7 +229,7 @@ class ServerOperationRegistryGenerator(
                rustTemplate(
                    """
                    $requestSpecs
                    #{Router}::from_box_clone_service_iter($towerServices)
                    #{Router}::${runtimeRouterConstructor()}($towerServices)
                    """.trimIndent(),
                    *codegenScope
                )
@@ -241,12 +247,42 @@ class ServerOperationRegistryGenerator(
    }

    /*
     * Generate the `RequestSpec`s for an operation based on its HTTP-bound route.
     * Finds the runtime function to construct a new `Router` based on the Protocol.
     */
    private fun OperationShape.requestSpec(): String {
    private fun runtimeRouterConstructor(): String =
        when (protocol) {
            RestJson1Trait.ID -> "new_rest_json_router"
            RestXmlTrait.ID -> "new_rest_xml_router"
            AwsJson1_0Trait.ID -> "new_aws_json_10_router"
            AwsJson1_1Trait.ID -> "new_aws_json_11_router"
            else -> TODO("Protocol $protocol not supported yet")
        }

    /*
     * Returns the `RequestSpec`s for an operation based on its HTTP-bound route.
     */
    private fun OperationShape.requestSpec(): String =
        when (protocol) {
            RestJson1Trait.ID, RestXmlTrait.ID -> restRequestSpec()
            AwsJson1_0Trait.ID, AwsJson1_1Trait.ID -> awsJsonOperationName()
            else -> TODO("Protocol $protocol not supported yet")
        }

    /*
     * Returns an AwsJson specific runtime `RequestSpec`.
     */
    private fun OperationShape.awsJsonOperationName(): String {
        val operationName = symbolProvider.toSymbol(this).name
        // TODO(https://github.com/awslabs/smithy-rs/issues/950): Support the `endpoint` trait: https://awslabs.github.io/smithy/1.0/spec/core/endpoint-traits.html#endpoint-trait
        return """String::from("$serviceName.$operationName")"""
    }

    /*
     * Generates a REST (RestJson1, RestXml) specific runtime `RequestSpec`.
     */
    private fun OperationShape.restRequestSpec(): String {
        val httpTrait = httpBindingResolver.httpTrait(this)
        val namespace = ServerRuntimeType.RequestSpecModule(runtimeConfig).fullyQualifiedName()

        // TODO(https://github.com/awslabs/smithy-rs/issues/950): Support the `endpoint` trait.
        val pathSegments = httpTrait.uri.segments.map {
            "$namespace::PathSegment::" +
@@ -268,7 +304,7 @@ class ServerOperationRegistryGenerator(
                        $namespace::PathSpec::from_vector_unchecked(vec![${pathSegments.joinToString()}]),
                        $namespace::QuerySpec::from_vector_unchecked(vec![${querySegments.joinToString()}])
                    )
                )
                ),
            )
        """.trimIndent()
    }
+14 −2
Original line number Diff line number Diff line
@@ -798,6 +798,18 @@ class ServerProtocolTestGenerator(
            FailingTest("com.amazonaws.s3#AmazonS3", "S3VirtualHostAccelerateAddressing", TestType.Request),
            FailingTest("com.amazonaws.s3#AmazonS3", "S3VirtualHostDualstackAccelerateAddressing", TestType.Request),
            FailingTest("com.amazonaws.s3#AmazonS3", "S3OperationAddressingPreferred", TestType.Request),

            // AwsJson1.0 failing tests.
            FailingTest("aws.protocoltests.json10#JsonRpc10", "AwsJson10EndpointTraitWithHostLabel", TestType.Request),
            FailingTest("aws.protocoltests.json10#JsonRpc10", "AwsJson10EndpointTrait", TestType.Request),

            // AwsJson1.1 failing tests.
            FailingTest("aws.protocoltests.json#JsonProtocol", "AwsJson11EndpointTraitWithHostLabel", TestType.Request),
            FailingTest("aws.protocoltests.json#JsonProtocol", "AwsJson11EndpointTrait", TestType.Request),
            FailingTest("aws.protocoltests.json#JsonProtocol", "parses_httpdate_timestamps", TestType.Response),
            FailingTest("aws.protocoltests.json#JsonProtocol", "parses_iso8601_timestamps", TestType.Response),
            FailingTest("aws.protocoltests.json#JsonProtocol", "parses_the_request_id_from_the_response", TestType.Response),

        )
        private val RunOnly: Set<String>? = null

+97 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

package software.amazon.smithy.rust.codegen.server.smithy.protocols

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.escape
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.smithy.generators.protocol.ProtocolSupport
import software.amazon.smithy.rust.codegen.smithy.protocols.AwsJson
import software.amazon.smithy.rust.codegen.smithy.protocols.AwsJsonVersion
import software.amazon.smithy.rust.codegen.smithy.protocols.HttpBindingResolver
import software.amazon.smithy.rust.codegen.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.smithy.protocols.ProtocolGeneratorFactory
import software.amazon.smithy.rust.codegen.smithy.protocols.awsJsonFieldName
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.JsonCustomization
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.JsonSection
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.JsonSerializerGenerator
import software.amazon.smithy.rust.codegen.smithy.protocols.serialize.StructuredDataSerializerGenerator
import software.amazon.smithy.rust.codegen.util.hasTrait

/*
 * AwsJson 1.0 and 1.1 server-side protocol factory. This factory creates the [ServerHttpBoundProtocolGenerator]
 * with AwsJson specific configurations.
 */
class ServerAwsJsonFactory(private val version: AwsJsonVersion) : ProtocolGeneratorFactory<ServerHttpBoundProtocolGenerator> {
    override fun protocol(codegenContext: CodegenContext): Protocol = ServerAwsJson(codegenContext, version)

    override fun buildProtocolGenerator(codegenContext: CodegenContext): ServerHttpBoundProtocolGenerator =
        ServerHttpBoundProtocolGenerator(codegenContext, protocol(codegenContext))

    override fun transformModel(model: Model): Model = model

    override fun support(): ProtocolSupport {
        return ProtocolSupport(
            /* Client support */
            requestSerialization = false,
            requestBodySerialization = false,
            responseDeserialization = false,
            errorDeserialization = false,
            /* Server support */
            requestDeserialization = true,
            requestBodyDeserialization = true,
            responseSerialization = true,
            errorSerialization = true
        )
    }
}

/**
 * AwsJson requires errors to be serialized with an additional "__type" field. This
 * customization writes the right field depending on the version of the AwsJson protocol.
 */
class ServerAwsJsonError(private val awsJsonVersion: AwsJsonVersion) : JsonCustomization() {
    override fun section(section: JsonSection): Writable = when (section) {
        is JsonSection.ServerError -> writable {
            if (section.structureShape.hasTrait<ErrorTrait>()) {
                val typeId = when (awsJsonVersion) {
                    // AwsJson 1.0 wants the whole shape ID (namespace#Shape).
                    // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_0-protocol.html#operation-error-serialization
                    AwsJsonVersion.Json10 -> section.structureShape.id.toString()
                    // AwsJson 1.1 wants only the shape name (Shape).
                    // https://awslabs.github.io/smithy/1.0/spec/aws/aws-json-1_1-protocol.html#operation-error-serialization
                    AwsJsonVersion.Json11 -> section.structureShape.id.name.toString()
                }
                rust("""${section.jsonObject}.key("__type").string("${escape(typeId)}");""")
            }
        }
    }
}

/**
 * AwsJson requires errors to be serialized with an additional "__type" field. This class
 * customizes [JsonSerializerGenerator] to add this functionality.
 */
class ServerAwsJsonSerializerGenerator(
    private val codegenContext: CodegenContext,
    private val httpBindingResolver: HttpBindingResolver,
    private val awsJsonVersion: AwsJsonVersion,
    private val jsonSerializerGenerator: JsonSerializerGenerator =
        JsonSerializerGenerator(codegenContext, httpBindingResolver, ::awsJsonFieldName, customizations = listOf(ServerAwsJsonError(awsJsonVersion)))
) : StructuredDataSerializerGenerator by jsonSerializerGenerator

class ServerAwsJson(
    private val codegenContext: CodegenContext,
    private val awsJsonVersion: AwsJsonVersion
) : AwsJson(codegenContext, awsJsonVersion) {
    override fun structuredDataSerializer(operationShape: OperationShape): StructuredDataSerializerGenerator =
        ServerAwsJsonSerializerGenerator(codegenContext, httpBindingResolver, awsJsonVersion)
}
Loading