Unverified Commit fb721c15 authored by Aaron Todd's avatar Aaron Todd Committed by GitHub
Browse files

fix event stream signing to include resource operations (#4055)

## Motivation and Context
fixes https://github.com/smithy-lang/smithy-rs/issues/4054

## Description
`serviceShape.operations` only includes operations directly bound to the
service and not operations bound via resources. This caused a bug with
event stream signing because the operation came from a resource and
didn't trigger the codegen path to enable the required feature flag in
the runtime (see the ticket for linked code). This PR address the
original issue and adds a test. I also searched for other places this
may be happening as it's a common bug in Smithy codegen.


## Checklist
<!--- If a checkbox below is not applicable, then please DELETE it
rather than leaving it unchecked -->
- [x] For changes to the smithy-rs codegen or runtime crates, I have
created a changelog entry Markdown file in the `.changelog` directory,
specifying "client," "server," or both in the `applies_to` key.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent 0774950e
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
---
applies_to:
- client
authors: ["aajtodd"]
references:
- smithy-rs#4054
breaking: false
new_feature: false
bug_fix: true
---
Fix traversal of operations bound to resources in several places including logic to determine if an event stream exists
+1 −1
Original line number Diff line number Diff line
@@ -205,7 +205,7 @@ private class AuthServiceRuntimePluginCustomization(private val codegenContext:
                    val serviceHasEventStream =
                        codegenContext.serviceShape.hasEventStreamOperations(codegenContext.model)
                    if (serviceHasEventStream) {
                        // enable the aws-runtime `sign-eventstream` feature
                        // enable the aws-runtime `event-stream` feature
                        addDependency(
                            AwsCargoDependency.awsRuntime(runtimeConfig).withFeature("event-stream").toType()
                                .toSymbol(),
+4 −2
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package software.amazon.smithy.rust.codegen.client.smithy

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.EnumShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.Shape
@@ -156,11 +157,12 @@ class ClientModuleDocProvider(
    private fun customizeModuleDoc(): Writable =
        writable {
            val model = codegenContext.model
            val operations = TopDownIndex.of(model).getContainedOperations(codegenContext.serviceShape)
            docs("Operation customization and supporting types.\n")
            if (codegenContext.serviceShape.operations.isNotEmpty()) {
            if (operations.isNotEmpty()) {
                val opFnName =
                    FluentClientGenerator.clientOperationFnName(
                        codegenContext.serviceShape.operations.minOf { it }
                        operations.minOf { it.id }
                            .let { model.expectShape(it, OperationShape::class.java) },
                        codegenContext.symbolProvider,
                    )
+6 −7
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.client

import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.docs
@@ -59,17 +58,17 @@ object FluentClientDocs {
        writable {
            val model = codegenContext.model
            val symbolProvider = codegenContext.symbolProvider
            if (model.operationShapes.isNotEmpty()) {
            val operations = TopDownIndex.of(model).getContainedOperations(codegenContext.serviceShape)
            if (operations.isNotEmpty()) {
                // Find an operation with a simple string member shape
                val (operation, member) =
                    codegenContext.serviceShape.operations
                        .map { id ->
                            val operationShape = model.expectShape(id, OperationShape::class.java)
                    operations
                        .map { op ->
                            val member =
                                operationShape.inputShape(model)
                                op.inputShape(model)
                                    .members()
                                    .firstOrNull { model.expectShape(it.target) is StringShape }
                            operationShape to member
                            op to member
                        }
                        .sortedBy { it.first.id }
                        .firstOrNull { (_, member) -> member != null } ?: (null to null)
+9 −3
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@
package software.amazon.smithy.rust.codegen.core.smithy.customizations

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.core.rustlang.Feature
@@ -21,8 +23,12 @@ import software.amazon.smithy.rust.codegen.core.util.hasEventStreamOperations
import software.amazon.smithy.rust.codegen.core.util.hasStreamingMember

/** Returns true if the model has normal streaming operations (excluding event streams) */
private fun hasStreamingOperations(model: Model): Boolean {
    return model.operationShapes.any { operation ->
private fun hasStreamingOperations(
    model: Model,
    serviceShape: ServiceShape,
): Boolean {
    val operations = TopDownIndex.of(model).getContainedOperations(serviceShape)
    return operations.any { operation ->
        val input = model.expectShape(operation.inputShape, StructureShape::class.java)
        val output = model.expectShape(operation.outputShape, StructureShape::class.java)
        (input.hasStreamingMember(model) && !input.hasEventStreamMember(model)) ||
@@ -69,7 +75,7 @@ fun pubUseSmithyPrimitives(
                "Format" to RuntimeType.format(rc),
            )
        }
        if (hasStreamingOperations(model)) {
        if (hasStreamingOperations(model, codegenContext.serviceShape)) {
            rustCrate.mergeFeature(
                Feature(
                    "rt-tokio",
Loading