Unverified Commit 24bfcba6 authored by Guy Margalit's avatar Guy Margalit Committed by GitHub
Browse files

[Server] AdditionalErrorsDecorator - refactored from FallibleOperationsDecorator (#1224)



* InternalServerErrorDecorator changed from FallibleOperationsDecorator

Signed-off-by: default avatarGuy Margalit <guymguym@gmail.com>

* AdditionalErrorsDecorator = FallibleOperationsDecorator + InternalServerErrorDecorator

Signed-off-by: default avatarGuy Margalit <guymguym@gmail.com>

* Rename decorators

Signed-off-by: default avatarGuy Margalit <guymguym@gmail.com>

* Fix decorator names

Signed-off-by: default avatarGuy Margalit <guymguym@gmail.com>
parent 20a1483b
Loading
Loading
Loading
Loading
+87 −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.customizations

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.model.traits.RequiredTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator

/**
 * Add at least one error to all operations in the model.
 *
 * When this decorator is applied, even operations that do not have a Smithy error attatched,
 * will return `Result<OperationOutput, OperationError>`.
 *
 * To enable this decorator write its class name to a resource file like this:
 * ```
 * C="software.amazon.smithy.rust.codegen.server.smithy.customizations.AddInternalServerErrorToInfallibleOpsDecorator"
 * F="software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator"
 * D="codegen-server/src/main/resources/META-INF/services"
 * mkdir -p "$D" && echo "$C" > "$D/$F"
 * ```
 */
class AddInternalServerErrorToInfallibleOpsDecorator : RustCodegenDecorator {
    override val name: String = "AddInternalServerErrorToInfallibleOps"
    override val order: Byte = 0

    override fun transformModel(service: ServiceShape, model: Model): Model =
        addErrorShapeToModelOps(service, model, { shape -> shape.errors.isEmpty() })
}

/**
 * Add an internal server error to all operations in the model.
 *
 * When a server for an API is subject to internal errors, for example underlying network problems,
 * and there is no native mapping of these actual errors to the API errors, servers can generate
 * the code with this decorator to add an internal error shape on-the-fly to all the operations.
 *
 * When this decorator is applied, even operations that do not have a Smithy error attatched,
 * will return `Result<OperationOutput, OperationError>`.
 *
 * To enable this decorator write its class name to a resource file like this:
 * ```
 * C="software.amazon.smithy.rust.codegen.server.smithy.customizations.AddInternalServerErrorToAllOpsDecorator"
 * F="software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator"
 * D="codegen-server/src/main/resources/META-INF/services"
 * mkdir -p "$D" && echo "$C" > "$D/$F"
 * ```
 */
class AddInternalServerErrorToAllOpsDecorator : RustCodegenDecorator {
    override val name: String = "AddInternalServerErrorToAllOps"
    override val order: Byte = 0

    override fun transformModel(service: ServiceShape, model: Model): Model =
        addErrorShapeToModelOps(service, model, { _ -> true })
}

fun addErrorShapeToModelOps(service: ServiceShape, model: Model, opSelector: (OperationShape) -> Boolean): Model {
    val errorShape = internalServerError(service.id.getNamespace())
    val modelShapes = model.toBuilder().addShapes(listOf(errorShape)).build()
    return ModelTransformer.create().mapShapes(modelShapes) { shape ->
        if (shape is OperationShape && opSelector(shape)) {
            shape.toBuilder().addError(errorShape).build()
        } else {
            shape
        }
    }
}

fun internalServerError(namespace: String): StructureShape =
    StructureShape.builder().id("$namespace#InternalServerError")
        .addTrait(ErrorTrait("server"))
        .addMember(
            MemberShape.builder()
                .id("$namespace#InternalServerError\$message")
                .target("smithy.api#String")
                .addTrait(RequiredTrait())
                .build()
        ).build()
+0 −54
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.customizations

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.model.traits.RequiredTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator

/**
 * Add at least one error to all operations in the model.
 *
 * When this decorator is applied, operations that do not have a Smithy error attatched,
 * will return `Result<OperationOutput, InternalServerError>`.
 *
 * To enable this decorator, create a file called `META-INF/services/software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator`
 * containing `codegen-server/src/main/resources/META-INF/services/software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator`.
 */
class FallibleOperationsDecorator : RustCodegenDecorator {
    override val name: String = "FallibleOperations"
    override val order: Byte = 0

    private fun internalServerError(namespace: String): StructureShape {
        return StructureShape.builder().id("$namespace#InternalServerError")
            .addTrait(ErrorTrait("server"))
            .addMember(
                MemberShape.builder()
                    .id("$namespace#InternalServerError\$message")
                    .target("smithy.api#String")
                    .addTrait(RequiredTrait())
                    .build()
            ).build()
    }

    override fun transformModel(service: ServiceShape, model: Model): Model {
        val errorShape = internalServerError(service.id.getNamespace())
        val modelShapes = model.toBuilder().addShapes(listOf(errorShape)).build()
        return ModelTransformer.create().mapShapes(modelShapes) { shape ->
            if (shape is OperationShape && shape.errors.isEmpty()) {
                shape.toBuilder().addError(errorShape).build()
            } else {
                shape
            }
        }
    }
}
+15 −2
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverTestSymb
import software.amazon.smithy.rust.codegen.smithy.transformers.OperationNormalizer
import software.amazon.smithy.rust.codegen.testutil.asSmithyModel

class FallibleOperationsDecoratorTest {
class AdditionalErrorsDecoratorTest {
    private val baseModel = """
        namespace smithy.test
        use aws.protocols#restJson1
@@ -56,9 +56,22 @@ class FallibleOperationsDecoratorTest {
        val fallibleId = ShapeId.from("smithy.test#Fallible")
        model.expectShape(infallibleId, OperationShape::class.java).errors.isEmpty() shouldBe true
        model.expectShape(fallibleId, OperationShape::class.java).errors.size shouldBe 1
        val model = FallibleOperationsDecorator().transformModel(service, model)
        val model = AddInternalServerErrorToInfallibleOpsDecorator().transformModel(service, model)
        model.expectShape(infallibleId, OperationShape::class.java).errors.isEmpty() shouldBe false
        model.expectShape(infallibleId, OperationShape::class.java).errors.size shouldBe 1
        model.expectShape(fallibleId, OperationShape::class.java).errors.size shouldBe 1
    }

    @Test
    fun `add InternalServerError to all model operations`() {
        val service = ServiceShape.builder().id("smithy.test#Test").build()
        val infallibleId = ShapeId.from("smithy.test#Infallible")
        val fallibleId = ShapeId.from("smithy.test#Fallible")
        model.expectShape(infallibleId, OperationShape::class.java).errors.isEmpty() shouldBe true
        model.expectShape(fallibleId, OperationShape::class.java).errors.size shouldBe 1
        val model = AddInternalServerErrorToAllOpsDecorator().transformModel(service, model)
        model.expectShape(infallibleId, OperationShape::class.java).errors.isEmpty() shouldBe false
        model.expectShape(infallibleId, OperationShape::class.java).errors.size shouldBe 1
        model.expectShape(fallibleId, OperationShape::class.java).errors.size shouldBe 2
    }
}