Skip to content

Commit c63e792

Browse files
authored
Add server RPC v2 CBOR support (#2544)
RPC v2 CBOR is a new protocol that ~is being added~ has [recently been added](https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html) to the Smithy specification. _(I'll add more details here as the patchset evolves)_ Credit goes to @jjant for initial implementation of the router, which I built on top of from his [`jjant/smithy-rpc-v2-exploration`](https://github.com/awslabs/smithy-rs/tree/jjant/smithy-rpc-v2-exploration) branch. Tracking issue: #3573. ## Caveats `TODO`s are currently exhaustively sprinkled throughout the patch documenting what remains to be done. Most of these need to be addressed before this can be merged in; some can be punted on to not make this PR bigger. However, I'd like to call out the major caveats and blockers here. I'll keep updating this list as the patchset evolves. - [x] RPC v2 has still not been added to the Smithy specification. It is currently being worked on over in the [`smithy-rpc-v2`](https://github.com/awslabs/smithy/tree/smithy-rpc-v2) branch. The following are prerrequisites for this PR to be merged; **until they are done CI on this PR will fail**: - [x] Smithy merges in RPC v2 support. - [x] Smithy releases a new version incorporating RPC v2 support. - Released in [Smithy v1.47](https://github.com/smithy-lang/smithy/releases/tag/1.47.0) - [x] smithy-rs updates to the new version. - Updated in #3552 - [x] ~Protocol tests for the protocol do not currently exist in Smithy. Until those get written~, this PR resorts to Rust unit tests and integration tests that use `serde` to round-trip messages and compare `serde`'s encoders and decoders with ours for correctness. - Protocol tests are under the [`smithy-protocol-tests`](https://github.com/smithy-lang/smithy/tree/main/smithy-protocol-tests/model/rpcv2Cbor) directory in Smithy. - We're keeping the `serde_cbor` round-trip tests for defense in depth. - [ ] #3709 - Currently only server-side support has been implemented, because that's what I'm most familiar. However, we're almost all the way there to add client-side support. - ~[ ] [Smithy `document` shapes](https://smithy.io/2.0/spec/simple-types.html#document) are not supported. RPC v2's specification currently doesn't indicate how to implement them.~ - [The spec](https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#shape-serialization) ended up leaving them as unsupported: "Document types are not currently supported in this protocol." ## Prerequisite PRs This section lists prerequisite PRs and issues that would make the diff for this one lighter or easier to understand. It's preferable that these PRs be merged prior to this one; some are hard prerequisites. They mostly relate to parts of the codebase I've had to touch or ~pilfer~ inspect in this PR where I've made necessary changes, refactors and "drive-by improvements" that are mostly unrelated, although some directly unlock things I've needed in this patchset. It makes sense to pull them out to ease reviewability and make this patch more semantically self-contained. - #2516 - #2517 - #2522 - #2524 - #2528 - #2536 - #2537 - #2531 - #2538 - #2539 - #2542 - #3684 - #3678 - #3690 - #3713 - #3726 - #3752 ## Testing <!--- Please describe in detail how you tested your changes --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ~RPC v2 has still not been added to the Smithy specification. It is currently being worked on over in the [`smithy-rpc-v2`](https://github.com/awslabs/smithy/tree/smithy-rpc-v2) branch.~ This can only be tested _locally_ following these steps: ~1. Clone [the Smithy repository](https://github.com/smithy-lang/smithy/tree/smithy-rpc-v2) and checkout the `smithy-rpc-v2` branch. 2. Inside your local checkout of smithy-rs pointing to this PR's branch, make sure you've added `mavenLocal()` as a repository in the `build.gradle.kts` files. [Example](8df82fd). 4. Inside your local checkout of Smithy's `smithy-rpc-v2` branch: 1. Set `VERSION` to the current Smithy version used in smithy-rs (1.28.1 as of writing, but [check here](https://github.com/awslabs/smithy-rs/blob/main/gradle.properties#L21)). 2. Run `./gradlew clean build pTML`.~ ~6.~ 1. In your local checkout of the smithy-rs's `smithy-rpc-v2` branch, run `./gradlew codegen-server-test:build -P modules='rpcv2Cbor'`. ~You can troubleshoot whether you have Smithy correctly set up locally by inspecting `~/.m2/repository/software/amazon/smithy/smithy-protocols-traits`.~ ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [ ] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates ---- _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 e4ced33 commit c63e792

File tree

84 files changed

+4479
-246
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+4479
-246
lines changed

.cargo-deny-config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ exceptions = [
2525
{ allow = ["OpenSSL"], name = "ring", version = "*" },
2626
{ allow = ["OpenSSL"], name = "aws-lc-sys", version = "*" },
2727
{ allow = ["OpenSSL"], name = "aws-lc-fips-sys", version = "*" },
28+
{ allow = ["BlueOak-1.0.0"], name = "minicbor", version = "<=0.24.2" },
29+
# Safe to bump as long as license does not change -------------^
30+
# See D105255799.
2831
]
2932

3033
[[licenses.clarify]]

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ allprojects {
1818
val allowLocalDeps: String by project
1919
repositories {
2020
if (allowLocalDeps.toBoolean()) {
21-
mavenLocal()
21+
mavenLocal()
2222
}
2323
mavenCentral()
2424
google()

buildSrc/src/main/kotlin/CodegenTestCommon.kt

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,84 @@ fun generateImports(imports: List<String>): String =
2626
if (imports.isEmpty()) {
2727
""
2828
} else {
29-
"\"imports\": [${imports.map { "\"$it\"" }.joinToString(", ")}],"
29+
"\"imports\": [${imports.joinToString(", ") { "\"$it\"" }}],"
3030
}
3131

32+
val RustKeywords =
33+
setOf(
34+
"as",
35+
"break",
36+
"const",
37+
"continue",
38+
"crate",
39+
"else",
40+
"enum",
41+
"extern",
42+
"false",
43+
"fn",
44+
"for",
45+
"if",
46+
"impl",
47+
"in",
48+
"let",
49+
"loop",
50+
"match",
51+
"mod",
52+
"move",
53+
"mut",
54+
"pub",
55+
"ref",
56+
"return",
57+
"self",
58+
"Self",
59+
"static",
60+
"struct",
61+
"super",
62+
"trait",
63+
"true",
64+
"type",
65+
"unsafe",
66+
"use",
67+
"where",
68+
"while",
69+
"async",
70+
"await",
71+
"dyn",
72+
"abstract",
73+
"become",
74+
"box",
75+
"do",
76+
"final",
77+
"macro",
78+
"override",
79+
"priv",
80+
"typeof",
81+
"unsized",
82+
"virtual",
83+
"yield",
84+
"try",
85+
)
86+
87+
fun toRustCrateName(input: String): String {
88+
if (input.isBlank()) {
89+
throw IllegalArgumentException("Rust crate name cannot be empty")
90+
}
91+
val lowerCased = input.lowercase()
92+
// Replace any sequence of characters that are not lowercase letters, numbers, dashes, or underscores with a single underscore.
93+
val sanitized = lowerCased.replace(Regex("[^a-z0-9_-]+"), "_")
94+
// Trim leading or trailing underscores.
95+
val trimmed = sanitized.trim('_')
96+
// Check if the resulting string is empty, purely numeric, or a reserved name
97+
val finalName =
98+
when {
99+
trimmed.isEmpty() -> throw IllegalArgumentException("Rust crate name after sanitizing cannot be empty.")
100+
trimmed.matches(Regex("\\d+")) -> "n$trimmed" // Prepend 'n' if the name is purely numeric.
101+
trimmed in RustKeywords -> "${trimmed}_" // Append an underscore if the name is reserved.
102+
else -> trimmed
103+
}
104+
return finalName
105+
}
106+
32107
private fun generateSmithyBuild(
33108
projectDir: String,
34109
pluginName: String,
@@ -48,7 +123,7 @@ private fun generateSmithyBuild(
48123
${it.extraCodegenConfig ?: ""}
49124
},
50125
"service": "${it.service}",
51-
"module": "${it.module}",
126+
"module": "${toRustCrateName(it.module)}",
52127
"moduleVersion": "0.0.1",
53128
"moduleDescription": "test",
54129
"moduleAuthors": ["[email protected]"]

buildSrc/src/main/kotlin/CrateSet.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ object CrateSet {
5656
val SMITHY_RUNTIME_COMMON =
5757
listOf(
5858
"aws-smithy-async",
59+
"aws-smithy-cbor",
5960
"aws-smithy-checksums",
6061
"aws-smithy-compression",
6162
"aws-smithy-client",

codegen-client/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ dependencies {
2727
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
2828
implementation("software.amazon.smithy:smithy-waiters:$smithyVersion")
2929
implementation("software.amazon.smithy:smithy-rules-engine:$smithyVersion")
30+
implementation("software.amazon.smithy:smithy-protocol-traits:$smithyVersion")
3031

3132
// `smithy.framework#ValidationException` is defined here, which is used in event stream
32-
// marshalling/unmarshalling tests.
33+
// marshalling/unmarshalling tests.
3334
testImplementation("software.amazon.smithy:smithy-validation-model:$smithyVersion")
3435
}
3536

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/ClientCodegenDecorator.kt

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.error.ErrorC
1919
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
2020
import software.amazon.smithy.rust.codegen.core.smithy.customize.CombinedCoreCodegenDecorator
2121
import software.amazon.smithy.rust.codegen.core.smithy.customize.CoreCodegenDecorator
22-
import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolTestGenerator
2322
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap
2423
import java.util.ServiceLoader
2524
import java.util.logging.Logger
@@ -93,14 +92,6 @@ interface ClientCodegenDecorator : CoreCodegenDecorator<ClientCodegenContext, Cl
9392
codegenContext: ClientCodegenContext,
9493
baseCustomizations: List<ServiceRuntimePluginCustomization>,
9594
): List<ServiceRuntimePluginCustomization> = baseCustomizations
96-
97-
/**
98-
* Hook to override the protocol test generator
99-
*/
100-
fun protocolTestGenerator(
101-
codegenContext: ClientCodegenContext,
102-
baseGenerator: ProtocolTestGenerator,
103-
): ProtocolTestGenerator = baseGenerator
10495
}
10596

10697
/**
@@ -176,14 +167,6 @@ open class CombinedClientCodegenDecorator(decorators: List<ClientCodegenDecorato
176167
decorator.serviceRuntimePluginCustomizations(codegenContext, customizations)
177168
}
178169

179-
override fun protocolTestGenerator(
180-
codegenContext: ClientCodegenContext,
181-
baseGenerator: ProtocolTestGenerator,
182-
): ProtocolTestGenerator =
183-
combineCustomizations(baseGenerator) { decorator, gen ->
184-
decorator.protocolTestGenerator(codegenContext, gen)
185-
}
186-
187170
companion object {
188171
fun fromClasspath(
189172
context: PluginContext,

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiator.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ class ClientBuilderKindBehavior(val codegenContext: CodegenContext) : Instantiat
2828
override fun doesSetterTakeInOption(memberShape: MemberShape): Boolean = true
2929
}
3030

31-
class ClientInstantiator(private val codegenContext: ClientCodegenContext) : Instantiator(
31+
class ClientInstantiator(private val codegenContext: ClientCodegenContext, withinTest: Boolean = false) : Instantiator(
3232
codegenContext.symbolProvider,
3333
codegenContext.model,
3434
codegenContext.runtimeConfig,
3535
ClientBuilderKindBehavior(codegenContext),
36+
withinTest = false,
3637
) {
3738
fun renderFluentCall(
3839
writer: RustWriter,

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolTestGenerator.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class ClientProtocolTestGenerator(
114114
get() = AppliesTo.CLIENT
115115
override val expectFail: Set<FailingTest>
116116
get() = ExpectFail
117-
override val runOnly: Set<String>
117+
override val generateOnly: Set<String>
118118
get() = emptySet()
119119
override val disabledTests: Set<String>
120120
get() = emptySet()
@@ -128,7 +128,7 @@ class ClientProtocolTestGenerator(
128128
private val inputShape = operationShape.inputShape(codegenContext.model)
129129
private val outputShape = operationShape.outputShape(codegenContext.model)
130130

131-
private val instantiator = ClientInstantiator(codegenContext)
131+
private val instantiator = ClientInstantiator(codegenContext, withinTest = true)
132132

133133
private val codegenScope =
134134
arrayOf(
@@ -149,6 +149,8 @@ class ClientProtocolTestGenerator(
149149
}
150150

151151
private fun RustWriter.renderHttpRequestTestCase(httpRequestTestCase: HttpRequestTestCase) {
152+
logger.info("Generating request test: ${httpRequestTestCase.id}")
153+
152154
if (!protocolSupport.requestSerialization) {
153155
rust("/* test case disabled for this protocol (not yet supported) */")
154156
return
@@ -234,6 +236,8 @@ class ClientProtocolTestGenerator(
234236
testCase: HttpResponseTestCase,
235237
expectedShape: StructureShape,
236238
) {
239+
logger.info("Generating response test: ${testCase.id}")
240+
237241
if (!protocolSupport.responseDeserialization || (
238242
!protocolSupport.errorDeserialization &&
239243
expectedShape.hasTrait(
@@ -357,8 +361,8 @@ class ClientProtocolTestGenerator(
357361
if (body == "") {
358362
rustWriter.rustTemplate(
359363
"""
360-
// No body
361-
#{AssertEq}(::std::str::from_utf8(body).unwrap(), "");
364+
// No body.
365+
#{AssertEq}(&body, &bytes::Bytes::new());
362366
""",
363367
*codegenScope,
364368
)

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/ClientProtocolLoader.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait
1313
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
1414
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
1515
import software.amazon.smithy.model.shapes.ServiceShape
16+
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait
1617
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
1718
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationGenerator
1819
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
@@ -28,6 +29,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolLoader
2829
import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap
2930
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestJson
3031
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RestXml
32+
import software.amazon.smithy.rust.codegen.core.smithy.protocols.RpcV2Cbor
3133
import software.amazon.smithy.rust.codegen.core.util.hasTrait
3234

3335
class ClientProtocolLoader(supportedProtocols: ProtocolMap<OperationGenerator, ClientCodegenContext>) :
@@ -41,6 +43,7 @@ class ClientProtocolLoader(supportedProtocols: ProtocolMap<OperationGenerator, C
4143
Ec2QueryTrait.ID to ClientEc2QueryFactory(),
4244
RestJson1Trait.ID to ClientRestJsonFactory(),
4345
RestXmlTrait.ID to ClientRestXmlFactory(),
46+
Rpcv2CborTrait.ID to ClientRpcV2CborFactory(),
4447
)
4548
val Default = ClientProtocolLoader(DefaultProtocols)
4649
}
@@ -117,3 +120,12 @@ class ClientRestXmlFactory(
117120

118121
override fun support(): ProtocolSupport = CLIENT_PROTOCOL_SUPPORT
119122
}
123+
124+
class ClientRpcV2CborFactory : ProtocolGeneratorFactory<OperationGenerator, ClientCodegenContext> {
125+
override fun protocol(codegenContext: ClientCodegenContext): Protocol = RpcV2Cbor(codegenContext)
126+
127+
override fun buildProtocolGenerator(codegenContext: ClientCodegenContext): OperationGenerator =
128+
OperationGenerator(codegenContext, protocol(codegenContext))
129+
130+
override fun support(): ProtocolSupport = CLIENT_PROTOCOL_SUPPORT
131+
}

codegen-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
2929
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
3030
implementation("software.amazon.smithy:smithy-waiters:$smithyVersion")
31+
implementation("software.amazon.smithy:smithy-protocol-traits:$smithyVersion")
3132
}
3233

3334
fun gitCommitHash(): String {

0 commit comments

Comments
 (0)