Skip to content

Commit 6dcb841

Browse files
authored
Changes for Protocol Tests (#3508)
2 parents 03fa175 + a0080e4 commit 6dcb841

File tree

17 files changed

+543
-182
lines changed

17 files changed

+543
-182
lines changed

buildSrc/src/main/kotlin/CodegenTestCommon.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,49 @@ fun generateImports(imports: List<String>): String =
2929
"\"imports\": [${imports.map { "\"$it\"" }.joinToString(", ")}],"
3030
}
3131

32+
fun toRustCrateName(input: String): String {
33+
val rustKeywords = setOf(
34+
// Strict Keywords.
35+
"as", "break", "const", "continue", "crate", "else", "enum", "extern",
36+
"false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
37+
"move", "mut", "pub", "ref", "return", "Self", "self", "static", "struct",
38+
"super", "trait", "true", "type", "unsafe", "use", "where", "while",
39+
40+
// Weak Keywords.
41+
"dyn", "async", "await", "try",
42+
43+
// Reserved for Future Use.
44+
"abstract", "become", "box", "do", "final", "macro", "override", "priv",
45+
"typeof", "unsized", "virtual", "yield",
46+
47+
// Primitive Types.
48+
"bool", "char", "i8", "i16", "i32", "i64", "i128", "isize",
49+
"u8", "u16", "u32", "u64", "u128", "usize", "f32", "f64", "str",
50+
51+
// Additional significant identifiers.
52+
"proc_macro"
53+
)
54+
55+
// Then within your function, you could include a check against this set
56+
if (input.isBlank()) {
57+
throw IllegalArgumentException("Rust crate name cannot be empty")
58+
}
59+
val lowerCased = input.lowercase()
60+
// Replace any sequence of characters that are not lowercase letters, numbers, or underscores with a single underscore.
61+
val sanitized = lowerCased.replace(Regex("[^a-z0-9_]+"), "_")
62+
// Trim leading or trailing underscores.
63+
val trimmed = sanitized.trim('_')
64+
// Check if the resulting string is empty, purely numeric, or a reserved name
65+
val finalName = when {
66+
trimmed.isEmpty() -> throw IllegalArgumentException("Rust crate name after sanitizing cannot be empty.")
67+
trimmed.matches(Regex("\\d+")) -> "n$trimmed" // Prepend 'n' if the name is purely numeric.
68+
trimmed in rustKeywords -> "${trimmed}_" // Append an underscore if the name is reserved.
69+
else -> trimmed
70+
}
71+
return finalName
72+
}
73+
74+
3275
private fun generateSmithyBuild(
3376
projectDir: String,
3477
pluginName: String,
@@ -48,7 +91,7 @@ private fun generateSmithyBuild(
4891
${it.extraCodegenConfig ?: ""}
4992
},
5093
"service": "${it.service}",
51-
"module": "${it.module}",
94+
"module": "${toRustCrateName(it.module)}",
5295
"moduleVersion": "0.0.1",
5396
"moduleDescription": "test",
5497
"moduleAuthors": ["protocoltest@example.com"]

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/Instantiator.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ import software.amazon.smithy.rust.codegen.core.util.hasTrait
6666
import software.amazon.smithy.rust.codegen.core.util.isTargetUnit
6767
import software.amazon.smithy.rust.codegen.core.util.letIf
6868
import java.math.BigDecimal
69+
import software.amazon.smithy.model.traits.DefaultTrait
70+
import software.amazon.smithy.rust.codegen.core.util.getTrait
6971

7072
/**
7173
* Class describing an instantiator section that can be used in a customization.
@@ -453,7 +455,7 @@ open class Instantiator(
453455
*/
454456
private fun fillDefaultValue(shape: Shape): Node =
455457
when (shape) {
456-
is MemberShape -> fillDefaultValue(model.expectShape(shape.target))
458+
is MemberShape -> shape.getTrait<DefaultTrait>()?.toNode() ?: fillDefaultValue(model.expectShape(shape.target))
457459

458460
// Aggregate shapes.
459461
is StructureShape -> Node.objectNode()

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ package software.amazon.smithy.rust.codegen.core.smithy.protocols
77

88
import software.amazon.smithy.codegen.core.CodegenException
99
import software.amazon.smithy.model.Model
10-
import software.amazon.smithy.model.pattern.UriPattern
1110
import software.amazon.smithy.model.shapes.OperationShape
1211
import software.amazon.smithy.model.shapes.ToShapeId
13-
import software.amazon.smithy.model.traits.HttpTrait
1412
import software.amazon.smithy.model.traits.TimestampFormatTrait
1513
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
1614
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
@@ -21,7 +19,9 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.parse.CborParse
2119
import software.amazon.smithy.rust.codegen.core.smithy.protocols.parse.StructuredDataParserGenerator
2220
import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.CborSerializerGenerator
2321
import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.StructuredDataSerializerGenerator
22+
import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticOutputTrait
2423
import software.amazon.smithy.rust.codegen.core.util.PANIC
24+
import software.amazon.smithy.rust.codegen.core.util.expectTrait
2525
import software.amazon.smithy.rust.codegen.core.util.isStreaming
2626
import software.amazon.smithy.rust.codegen.core.util.outputShape
2727

@@ -65,10 +65,16 @@ class RpcV2HttpBindingResolver(
6565
/**
6666
* > Responses for operations with no defined output type MUST NOT contain bodies in their HTTP responses.
6767
* > The `Content-Type` for the serialization format MUST NOT be set.
68-
*
69-
* TODO But the problem is we _always_ add input/output types in OperationNormalizer! This makes the `no_output_response` test fail.
7068
*/
71-
override fun responseContentType(operationShape: OperationShape): String = requestContentType(operationShape)
69+
override fun responseContentType(operationShape: OperationShape): String? {
70+
// When `syntheticOutputTrait.originalId == null` it implies that the operation had no output defined
71+
// in the Smithy model.
72+
val syntheticOutputTrait = operationShape.outputShape(model).expectTrait<SyntheticOutputTrait>()
73+
if (syntheticOutputTrait.originalId == null) {
74+
return null
75+
}
76+
return requestContentType(operationShape)
77+
}
7278
}
7379

7480
/**

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

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ class CborParserGenerator(
103103
*codegenScope,
104104
"ListSymbol" to listSymbol,
105105
) {
106-
val deserializeMemberWritable = writable { deserializeMember(memberShape) }
106+
val deserializeMemberWritable = deserializeMember(memberShape)
107107
if (isSparseList) {
108108
rustTemplate(
109109
"""
110110
let value = match decoder.datatype()? {
111111
#{SmithyCbor}::data::Type::Null => {
112-
let _v = decoder.null()?;
112+
decoder.null()?;
113113
None
114114
}
115115
_ => Some(#{DeserializeMember:W}?),
@@ -154,20 +154,20 @@ class CborParserGenerator(
154154
*codegenScope,
155155
"MapSymbol" to mapSymbol,
156156
) {
157-
val deserializeKeyWritable = writable { deserializeString(keyTarget) }
157+
val deserializeKeyWritable = deserializeString(keyTarget)
158158
rustTemplate(
159159
"""
160160
let key = #{DeserializeKey:W}?;
161161
""",
162162
"DeserializeKey" to deserializeKeyWritable,
163163
)
164-
val deserializeValueWritable = writable { deserializeMember(valueShape) }
164+
val deserializeValueWritable = deserializeMember(valueShape)
165165
if (isSparseMap) {
166166
rustTemplate(
167167
"""
168168
let value = match decoder.datatype()? {
169169
#{SmithyCbor}::data::Type::Null => {
170-
let _v = decoder.null()?;
170+
decoder.null()?;
171171
None
172172
}
173173
_ => Some(#{DeserializeValue:W}?),
@@ -206,33 +206,59 @@ class CborParserGenerator(
206206
*codegenScope,
207207
"Builder" to builderSymbol,
208208
) {
209-
withBlock("builder = match decoder.str()? {", "};") {
209+
withBlock("builder = match decoder.str()?.as_ref() {", "};") {
210210
for (member in includedMembers) {
211211
rustBlock("${member.memberName.dq()} =>") {
212-
withBlock("builder.${member.setterName()}(", ")") {
213-
conditionalBlock("Some(", ")", symbolProvider.toSymbol(member).isOptional()) {
214-
val symbol = symbolProvider.toSymbol(member)
215-
if (symbol.isRustBoxed()) {
216-
rustBlock("") {
217-
withBlock("let v = ", "?;") {
218-
deserializeMember(member)
212+
val callBuilderSetMemberFieldWritable = writable {
213+
withBlock("builder.${member.setterName()}(", ")") {
214+
conditionalBlock("Some(", ")", symbolProvider.toSymbol(member).isOptional()) {
215+
val symbol = symbolProvider.toSymbol(member)
216+
if (symbol.isRustBoxed()) {
217+
rustBlock("") {
218+
rustTemplate("let v = #{DeserializeMember:W}?;",
219+
"DeserializeMember" to deserializeMember(member))
220+
221+
for (customization in customizations) {
222+
customization.section(
223+
CborParserSection.BeforeBoxingDeserializedMember(
224+
member
225+
)
226+
)(this)
227+
}
228+
rust("Box::new(v)")
219229
}
220-
for (customization in customizations) {
221-
customization.section(CborParserSection.BeforeBoxingDeserializedMember(member))(this)
222-
}
223-
rust("Box::new(v)")
230+
} else {
231+
rustTemplate("#{DeserializeMember:W}?",
232+
"DeserializeMember" to deserializeMember(member))
224233
}
225-
} else {
226-
deserializeMember(member)
227-
rust("?")
228234
}
229235
}
230236
}
237+
238+
if (member.isOptional) {
239+
// Call `builder.set_member()` only if the value for the field on the wire is not null.
240+
rustTemplate(
241+
"""
242+
::aws_smithy_cbor::decode::set_optional(builder, decoder, |builder, decoder| {
243+
Ok(#{MemberSettingWritable:W})
244+
})?
245+
""",
246+
"MemberSettingWritable" to callBuilderSetMemberFieldWritable
247+
)
248+
}
249+
else {
250+
callBuilderSetMemberFieldWritable.invoke(this)
251+
}
231252
}
232253
}
233254

234-
// TODO Skip like in JSON or reject? I think we should reject in JSON too. Cut issue.
235-
rust("_ => { todo!() }")
255+
rust(
256+
"""
257+
_ => {
258+
decoder.skip()?;
259+
builder
260+
}
261+
""")
236262
}
237263
rust("Ok(builder)")
238264
}
@@ -256,7 +282,7 @@ class CborParserGenerator(
256282
val variantName = symbolProvider.toMemberName(member)
257283

258284
withBlock("${member.memberName.dq()} => #T::$variantName(", "?),", returnSymbolToParse.symbol) {
259-
deserializeMember(member)
285+
deserializeMember(member).invoke(this)
260286
}
261287
}
262288
// TODO Test client mode (parse unknown variant) and server mode (reject unknown variant).
@@ -423,13 +449,13 @@ class CborParserGenerator(
423449
return structureParser(operationShape, symbolProvider.symbolForBuilder(inputShape), includedMembers)
424450
}
425451

426-
private fun RustWriter.deserializeMember(memberShape: MemberShape) {
452+
private fun RustWriter.deserializeMember(memberShape: MemberShape) = writable {
427453
when (val target = model.expectShape(memberShape.target)) {
428454
// Simple shapes: https://smithy.io/2.0/spec/simple-types.html
429455
is BlobShape -> rust("decoder.blob()")
430456
is BooleanShape -> rust("decoder.boolean()")
431457

432-
is StringShape -> deserializeString(target)
458+
is StringShape -> deserializeString(target).invoke(this)
433459

434460
is ByteShape -> rust("decoder.byte()")
435461
is ShortShape -> rust("decoder.short()")
@@ -461,7 +487,7 @@ class CborParserGenerator(
461487
// }
462488
}
463489

464-
private fun RustWriter.deserializeString(target: StringShape, bubbleUp: Boolean = true) {
490+
private fun RustWriter.deserializeString(target: StringShape, bubbleUp: Boolean = true) = writable {
465491
// TODO Handle enum shapes
466492
rust("decoder.string()")
467493
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ class CborSerializerGenerator(
402402
rust(
403403
"""
404404
encoder.array(
405-
(${context.valueExpression.asRef()}).len().try_into().expect("`usize` to `u64` conversion failed")
405+
(${context.valueExpression.asValue()}).len().try_into().expect("`usize` to `u64` conversion failed")
406406
);
407407
"""
408408
)
@@ -421,7 +421,7 @@ class CborSerializerGenerator(
421421
rust(
422422
"""
423423
encoder.map(
424-
(${context.valueExpression.asRef()}).len().try_into().expect("`usize` to `u64` conversion failed")
424+
(${context.valueExpression.asValue()}).len().try_into().expect("`usize` to `u64` conversion failed")
425425
);
426426
"""
427427
)

codegen-server-test/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ buildscript {
3030
dependencies {
3131
implementation(project(":codegen-server"))
3232
implementation("software.amazon.smithy:smithy-aws-protocol-tests:$smithyVersion")
33+
implementation("software.amazon.smithy:smithy-protocol-tests:$smithyVersion")
3334
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")
3435
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
3536
implementation("software.amazon.smithy:smithy-validation-model:$smithyVersion")
@@ -45,8 +46,10 @@ val allCodegenTests = "../codegen-core/common-test-models".let { commonModels ->
4546
imports = listOf("$commonModels/naming-obstacle-course-structs.smithy"),
4647
),
4748
CodegenTest("com.amazonaws.simple#SimpleService", "simple", imports = listOf("$commonModels/simple.smithy")),
48-
CodegenTest("aws.protocoltests.rpcv2Cbor#RpcV2Protocol", "rpcv2_cbor"),
49-
CodegenTest("com.amazonaws.simple#RpcV2Service", "rpcv2", imports = listOf("$commonModels/rpcv2.smithy")),
49+
// CodegenTest("aws.protocoltests.restxml#RestXml", "restXml"),
50+
CodegenTest("smithy.protocoltests.rpcv2Cbor#RpcV2Protocol", "rpcv2Cbor"),
51+
// Todo: change this to rpcv2extra
52+
CodegenTest("com.amazonaws.simple#RpcV2Service", "rpcv2Extra", imports = listOf("$commonModels/rpcv2.smithy")),
5053
CodegenTest(
5154
"aws.protocoltests.rpcv2#RpcV2Protocol",
5255
"adwait-main",

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocol.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,5 +357,5 @@ class ServerRpcV2Protocol(
357357

358358
override fun serverRouterRuntimeConstructor() = "rpc_v2_router"
359359

360-
override fun serverContentTypeCheckNoModeledInput() = true
360+
override fun serverContentTypeCheckNoModeledInput() = false
361361
}

codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/serialize/CborSerializerGeneratorTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import software.amazon.smithy.rust.codegen.core.util.getTrait
3232
import software.amazon.smithy.rust.codegen.core.util.outputShape
3333
import software.amazon.smithy.rust.codegen.server.smithy.customize.ServerCodegenDecorator
3434
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolTestGenerator
35-
import software.amazon.smithy.rust.codegen.server.smithy.generators.serverInstantiator
35+
import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerInstantiator
3636
import software.amazon.smithy.rust.codegen.server.smithy.testutil.serverIntegrationTest
3737
import java.io.File
3838

@@ -81,7 +81,7 @@ internal class CborSerializerGeneratorTest {
8181
"SerdeCbor" to CargoDependency.SerdeCbor.toType(),
8282
)
8383

84-
val instantiator = serverInstantiator(codegenContext)
84+
val instantiator = ServerInstantiator(codegenContext)
8585
val rpcV2 = RpcV2(codegenContext)
8686

8787
for (operationShape in codegenContext.model.operationShapes) {

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ kotlin.code.style=official
2121

2222
# codegen
2323
smithyGradlePluginVersion=0.7.0
24-
smithyVersion=1.43.0
24+
smithyVersion=1.46.0
2525

2626
# kotlin
2727
kotlinVersion=1.9.20
Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
[package]
22
name = "aws-smithy-cbor"
33
version = "0.0.0-smithy-rs-head"
4-
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "David Pérez <d@vidp.dev>"]
4+
authors = [
5+
"AWS Rust SDK Team <aws-sdk-rust@amazon.com>",
6+
"David Pérez <d@vidp.dev>",
7+
]
58
description = "CBOR utilities for smithy-rs."
69
edition = "2021"
710
license = "Apache-2.0"
811
repository = "https://github.com/awslabs/smithy-rs"
912

1013
[dependencies.minicbor]
1114
version = "0.19.1" # TODO Update
12-
features = ["alloc"] # To write to a `Vec<u8>`: https://docs.rs/minicbor/latest/minicbor/encode/write/trait.Write.html#impl-Write-for-Vec%3Cu8%3E
15+
features = [
16+
# To write to a `Vec<u8>`: https://docs.rs/minicbor/latest/minicbor/encode/write/trait.Write.html#impl-Write-for-Vec%3Cu8%3E
17+
"alloc",
18+
# To support reading `f16` to accomodate fewer bytes transmitted that fit the value.
19+
"half",
20+
]
1321

1422
[dependencies]
1523
aws-smithy-types = { path = "../aws-smithy-types" }
@@ -19,3 +27,14 @@ all-features = true
1927
targets = ["x86_64-unknown-linux-gnu"]
2028
rustdoc-args = ["--cfg", "docsrs"]
2129
# End of docs.rs metadata
30+
31+
[dev-dependencies]
32+
criterion = "0.5.1"
33+
34+
[[bench]]
35+
name = "string"
36+
harness = false
37+
38+
[[bench]]
39+
name = "blob"
40+
harness = false

0 commit comments

Comments
 (0)