Skip to content

Commit 9b3b33a

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 9b3b33a

File tree

2 files changed

+120
-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

+120
-1
lines changed

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

Lines changed: 13 additions & 1 deletion
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,107 @@
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+
class ServerProtocolLoaderTest {
20+
private val testModel =
21+
"""
22+
${"$"}version: "2"
23+
24+
namespace test
25+
26+
use aws.api#service
27+
use aws.protocols#awsJson1_0
28+
29+
@awsJson1_0
30+
@service(
31+
sdkId: "Test",
32+
arnNamespace: "test"
33+
)
34+
service TestService {
35+
version: "2024-04-01"
36+
}
37+
""".asSmithyModel(smithyVersion = "2.0")
38+
39+
private val testModelNoProtocol =
40+
"""
41+
${"$"}version: "2"
42+
43+
namespace test
44+
45+
use aws.api#service
46+
47+
@service(
48+
sdkId: "Test",
49+
arnNamespace: "test"
50+
)
51+
service TestService {
52+
version: "2024-04-01"
53+
}
54+
""".asSmithyModel(smithyVersion = "2.0")
55+
56+
@Test
57+
fun `ensures protocols are matched`() {
58+
val loader = ServerProtocolLoader(ServerProtocolLoader.DefaultProtocols)
59+
60+
val (shape, _) = loader.protocolFor(testModel, testModel.serviceShapes.first())
61+
62+
shape.name shouldBe "awsJson1_0"
63+
}
64+
65+
@Test
66+
fun `ensures unmatched service protocol fails`() {
67+
val loader =
68+
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 =
92+
shouldThrow<CodegenException> {
93+
loader.protocolFor(testModel, testModel.serviceShapes.first())
94+
}
95+
exception.message shouldContain("Unable to find a matching protocol")
96+
}
97+
98+
@Test
99+
fun `ensures service without protocol fails`() {
100+
val loader = ServerProtocolLoader(ServerProtocolLoader.DefaultProtocols)
101+
val exception =
102+
shouldThrow<CodegenException> {
103+
loader.protocolFor(testModelNoProtocol, testModelNoProtocol.serviceShapes.first())
104+
}
105+
exception.message shouldContain("Service must have a protocol trait")
106+
}
107+
}

0 commit comments

Comments
 (0)