Skip to content

Commit 9343a39

Browse files
committed
Improve error when service is missing protocol
resolves #2452 ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent 09ba40e commit 9343a39

File tree

2 files changed

+118
-1
lines changed
  • codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols
  • codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols

2 files changed

+118
-1
lines changed

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/protocols/ProtocolLoader.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,27 @@ import software.amazon.smithy.model.traits.Trait
1414
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
1515

1616
open class ProtocolLoader<T, C : CodegenContext>(private val supportedProtocols: ProtocolMap<T, C>) {
17+
private fun formatProtocols(): String {
18+
return supportedProtocols.keys.joinToString(
19+
prefix = "\t",
20+
separator = "\n\t"
21+
)
22+
}
23+
1724
fun protocolFor(
1825
model: Model,
1926
serviceShape: ServiceShape,
2027
): Pair<ShapeId, ProtocolGeneratorFactory<T, C>> {
2128
val protocols: MutableMap<ShapeId, Trait> = ServiceIndex.of(model).getProtocols(serviceShape)
29+
if (protocols.isEmpty()) {
30+
throw CodegenException("Service must have a protocol trait. Available protocols:\n${formatProtocols()}")
31+
}
32+
2233
val matchingProtocols =
2334
protocols.keys.mapNotNull { protocolId -> supportedProtocols[protocolId]?.let { protocolId to it } }
2435
if (matchingProtocols.isEmpty()) {
25-
throw CodegenException("No matching protocol — service offers: ${protocols.keys}. We offer: ${supportedProtocols.keys}")
36+
val specified = protocols.keys.joinToString(", ")
37+
throw CodegenException("Unable to find a matching protocol. Model specifies ${specified}, but must match an available protocol:\n${formatProtocols()}")
2638
}
2739
return matchingProtocols.first()
2840
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.rust.codegen.server.smithy.protocols
7+
8+
import io.kotest.assertions.throwables.shouldThrow
9+
import io.kotest.matchers.shouldBe
10+
import io.kotest.matchers.string.shouldContain
11+
import org.junit.jupiter.api.Test
12+
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
13+
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
14+
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
15+
import software.amazon.smithy.codegen.core.CodegenException
16+
import software.amazon.smithy.rust.codegen.core.smithy.protocols.AwsJsonVersion
17+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
18+
19+
20+
class ServerProtocolLoaderTest {
21+
private val testModel =
22+
"""
23+
${"$"}version: "2"
24+
25+
namespace test
26+
27+
use aws.api#service
28+
use aws.protocols#awsJson1_0
29+
30+
@awsJson1_0
31+
@service(
32+
sdkId: "Test",
33+
arnNamespace: "test"
34+
)
35+
service TestService {
36+
version: "2024-04-01"
37+
}
38+
""".asSmithyModel(smithyVersion = "2.0")
39+
40+
private val testModelNoProtocol =
41+
"""
42+
${"$"}version: "2"
43+
44+
namespace test
45+
46+
use aws.api#service
47+
48+
@service(
49+
sdkId: "Test",
50+
arnNamespace: "test"
51+
)
52+
service TestService {
53+
version: "2024-04-01"
54+
}
55+
""".asSmithyModel(smithyVersion = "2.0")
56+
57+
@Test
58+
fun `ensures protocols are matched`() {
59+
val loader = ServerProtocolLoader(ServerProtocolLoader.DefaultProtocols)
60+
61+
val (shape, _) = loader.protocolFor(testModel, testModel.serviceShapes.first())
62+
63+
shape.name shouldBe "awsJson1_0"
64+
}
65+
66+
@Test
67+
fun `ensures unmatched service protocol fails`() {
68+
val loader = ServerProtocolLoader(
69+
mapOf(
70+
RestJson1Trait.ID to
71+
ServerRestJsonFactory(
72+
additionalServerHttpBoundProtocolCustomizations =
73+
listOf(
74+
StreamPayloadSerializerCustomization(),
75+
),
76+
),
77+
RestXmlTrait.ID to
78+
ServerRestXmlFactory(
79+
additionalServerHttpBoundProtocolCustomizations =
80+
listOf(
81+
StreamPayloadSerializerCustomization(),
82+
),
83+
),
84+
AwsJson1_1Trait.ID to
85+
ServerAwsJsonFactory(
86+
AwsJsonVersion.Json11,
87+
additionalServerHttpBoundProtocolCustomizations = listOf(StreamPayloadSerializerCustomization()),
88+
),
89+
)
90+
)
91+
val exception = shouldThrow<CodegenException> {
92+
loader.protocolFor(testModel, testModel.serviceShapes.first())
93+
}
94+
exception.message shouldContain("Unable to find a matching protocol")
95+
}
96+
97+
@Test
98+
fun `ensures service without protocol fails`() {
99+
val loader = ServerProtocolLoader(ServerProtocolLoader.DefaultProtocols)
100+
val exception = shouldThrow<CodegenException> {
101+
loader.protocolFor(testModelNoProtocol, testModelNoProtocol.serviceShapes.first())
102+
}
103+
exception.message shouldContain("Service must have a protocol trait")
104+
}
105+
}

0 commit comments

Comments
 (0)