Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for smithy.protocols#rpcv2Cbor protocol #1103

Merged
merged 133 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
6eb23d0
commit all
lauzadis May 9, 2024
46473e0
Add dependency on smithy-protocol-traits
lauzadis May 28, 2024
f210817
latest commit
lauzadis May 28, 2024
714b04b
commit unstaged files
lauzadis May 29, 2024
aa57a2d
tests
lauzadis Jun 5, 2024
d212ac0
decode tests are passing
lauzadis Jun 5, 2024
0d8daae
Upgrade to Smithy 1.49.0
lauzadis Jun 5, 2024
c3f6f31
Add Rpcv2Cbor as a protocol generator
lauzadis Jun 5, 2024
add554f
Latest commit
lauzadis Jun 6, 2024
61cc03a
Latest commit
lauzadis Jun 7, 2024
0e92a58
Fix encoding/decoding of negative integers
lauzadis Jun 10, 2024
6c8b1fa
Fix error deserializer
lauzadis Jun 10, 2024
2d998f3
Small fixes
lauzadis Jun 10, 2024
b3f6d0c
ktlintFormat
lauzadis Jun 10, 2024
0f2a9ef
remove unused variable
lauzadis Jun 10, 2024
ac31d50
apiDump
lauzadis Jun 10, 2024
1184b7b
Generate tests from cbor-decode-error-tests.json
lauzadis Jun 10, 2024
6718936
Cleanup
lauzadis Jun 10, 2024
859111a
ktlintFormat
lauzadis Jun 10, 2024
1f777c6
test list and map serde
lauzadis Jun 10, 2024
1763955
ktlintFormat
lauzadis Jun 10, 2024
20468df
Replace CborSerialName
lauzadis Jun 10, 2024
b0b1b87
Replace CborSerialName
lauzadis Jun 10, 2024
5046ddc
fix some codegen failures
lauzadis Jun 10, 2024
4ee5366
revert temporary BigIntegerTest changes
lauzadis Jun 11, 2024
a7a62e4
use peekMinorByte
lauzadis Jun 12, 2024
b895e4f
Making requests to CBOR services.
lauzadis Jun 12, 2024
045fb5b
ktlintFormat
lauzadis Jun 12, 2024
4057079
remove temporary variable
lauzadis Jun 12, 2024
e7fdc80
correct serializer name
lauzadis Jun 12, 2024
975e97a
Add TimestampFormat import
lauzadis Jun 12, 2024
f6b5fd4
update test expectations
lauzadis Jun 12, 2024
80f37de
Send `Accept` header
lauzadis Jun 12, 2024
c662ee8
Add ByteArray deserializer to primitive list
lauzadis Jun 12, 2024
9bdc4a3
`isBinaryMediaType` util
lauzadis Jun 12, 2024
ba0382c
Add smithy-protocol-tests
lauzadis Jun 12, 2024
89981e1
RPCv2 protocol tests _mostly_ passing, need to finish implementing `a…
lauzadis Jun 14, 2024
93c454e
RPCv2 protocol tests _mostly_ passing, need to finish implementing `a…
lauzadis Jun 14, 2024
ffbfd6a
ktlint
lauzadis Jun 14, 2024
59fc1bf
Fix `empty_input` and `no_input` protocol tests
lauzadis Jun 17, 2024
4c19672
Skip deserializing explicit null structure values
lauzadis Jun 17, 2024
6336085
Remove unused `major`
lauzadis Jun 17, 2024
de302e9
Remove unused import
lauzadis Jun 17, 2024
c1dff2f
Add `Rpcv2Cbor` to the list of all protocols
lauzadis Jun 17, 2024
81ee676
Correct `equals` implementation for lists of blobs
lauzadis Jun 18, 2024
1012069
remove fixme comment
lauzadis Jun 18, 2024
7cf68a9
Handle NaN comparisons
lauzadis Jun 18, 2024
54b9407
Protocol tests!!!
lauzadis Jun 18, 2024
a2c8e21
ktlintFormat
lauzadis Jun 18, 2024
307e78f
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into feat…
lauzadis Jun 18, 2024
ee86760
Add prioritized protocol resolution
lauzadis Jun 19, 2024
06414a1
Fix tests
lauzadis Jun 19, 2024
5570862
ktlint
lauzadis Jun 19, 2024
613a4fe
simplify
lauzadis Jun 19, 2024
71bc4fe
Rename `deserializeInstant`
lauzadis Jun 19, 2024
6fdf110
Also rename `deserializeByteArray`
lauzadis Jun 19, 2024
9cf6ba7
finish rename
lauzadis Jun 19, 2024
f467bfb
remove unnecessary `open`
lauzadis Jun 19, 2024
95542d3
clarify timestamp format
lauzadis Jun 19, 2024
8f86350
remove `CborSerializeStructGenerator` and `CborSerializeUnionGenerator`
lauzadis Jun 19, 2024
638d1aa
simplify
lauzadis Jun 19, 2024
0ed06d6
Add a KDoc
lauzadis Jun 19, 2024
5a85556
Simplify `CborDeserializer` a bit, not fully done
lauzadis Jun 19, 2024
9dcc7a6
Simplify `CborDeserializer` again
lauzadis Jun 19, 2024
829e333
Simplify `CborSerializer`
lauzadis Jun 19, 2024
a8bc2c7
Add a convenience method for writing Cbor values to a buffer
lauzadis Jun 19, 2024
1883661
ktlint
lauzadis Jun 19, 2024
03060ff
utils rename
lauzadis Jun 19, 2024
01dbc4a
Simplify `encodeArgument` and `decodeArgument`. Only ByteArray functi…
lauzadis Jun 19, 2024
246323a
Fix `deserializeFloatingPoint`
lauzadis Jun 19, 2024
6484781
Make new deserialize functions more consistent
lauzadis Jun 19, 2024
588101e
Simplify BigInteger utility functions
lauzadis Jun 19, 2024
8c0c320
Simplify BigInteger utility functions
lauzadis Jun 19, 2024
cd41ebc
Clean up a bit
lauzadis Jun 19, 2024
a9ed26a
Simplify
lauzadis Jun 19, 2024
535864f
Refactor a little
lauzadis Jun 19, 2024
5821ffd
ktlintFormat
lauzadis Jun 19, 2024
af4d9fe
Fix warnings in tests
lauzadis Jun 19, 2024
7335806
changelog
lauzadis Jun 19, 2024
66b738e
Fix negative representation (handles MAX_VALUE without overflow now)
lauzadis Jun 19, 2024
4e2938a
ktlint
lauzadis Jun 19, 2024
de03d4e
changelog
lauzadis Jun 20, 2024
8a8888b
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into feat…
lauzadis Jun 24, 2024
3a40040
Emit business metric
lauzadis Jun 24, 2024
9fa0846
Safe compare NaNs
lauzadis Jun 26, 2024
a19da6c
ktlint
lauzadis Jun 26, 2024
7f7b133
Update to `SerialKind.String`
lauzadis Jun 26, 2024
c2e9fd6
Upgrade to smithy 1.50.0
lauzadis Jun 27, 2024
ebd3fde
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into feat…
lauzadis Jun 27, 2024
5750ba4
ktlint v1.3.0
lauzadis Jun 27, 2024
c9069a5
Ignore test with typo
lauzadis Jun 28, 2024
ad5f77c
Add PR link to the FIXME
lauzadis Jun 28, 2024
f5143b0
Add smithy-protocol response header validation
lauzadis Jul 3, 2024
d5d6c6c
TimestampFormatTrait.Format.UNKNOWN
lauzadis Jul 3, 2024
8567688
Map accepts any CBOR value, style changes
lauzadis Jul 3, 2024
7c5f14a
Tag ID as an enum
lauzadis Jul 3, 2024
61037b3
Use case-sensitive field lookups
lauzadis Jul 3, 2024
4918bf1
Make `roundTripImpl` a suspend function
lauzadis Jul 3, 2024
788a8d5
Make MAJOR_BYTE_MASK private
lauzadis Jul 3, 2024
6db7b63
relocate decodeArgument
lauzadis Jul 3, 2024
b8df86d
use readFully
lauzadis Jul 3, 2024
b5a29dd
Add tests for response header interceptor
lauzadis Jul 3, 2024
2284b2e
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into feat…
lauzadis Jul 8, 2024
9f14b26
BigInteger plus/minus operators
lauzadis Jul 8, 2024
5e63377
BigInteger byte operations
lauzadis Jul 8, 2024
6342fc5
encode(into: SdkBuffer)
lauzadis Jul 8, 2024
32e3756
SdkBuffer -> SdkBufferedSink
lauzadis Jul 8, 2024
6b64147
Remove unnecessary major/minor checks
lauzadis Jul 8, 2024
7eb67cd
Serialize directly into an HttpBody
lauzadis Jul 8, 2024
de6f9ff
Rename Rpcv2Cbor -> RpcV2Cbor
lauzadis Jul 8, 2024
c13956c
Rename
lauzadis Jul 8, 2024
00a1862
Add more checks around map/structure deserialization
lauzadis Jul 8, 2024
58bbe52
Make IndefiniteList/IndefiniteMap take a List/Map instead of MutableL…
lauzadis Jul 8, 2024
e1fd290
ktlint
lauzadis Jul 9, 2024
0ad4d50
Break up mega-implementation into multiple smaller files
lauzadis Jul 9, 2024
770519f
ktlint
lauzadis Jul 9, 2024
104f4aa
Update comments
lauzadis Jul 9, 2024
5cad413
Throw exception when deserialized number does not fit in desired type
lauzadis Jul 9, 2024
cb83f4d
Add TODOs, FIXMEs, and safely handle potential overflow
lauzadis Jul 9, 2024
b6e36a5
ktlint
lauzadis Jul 9, 2024
2c41bc3
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into feat…
lauzadis Jul 9, 2024
6791e40
Fix warning
lauzadis Jul 9, 2024
764fea6
Fix "Stream must be replayable to calculate a body hash" error
lauzadis Jul 9, 2024
5d397c1
Remove duplicate deserializeByte
lauzadis Jul 10, 2024
186735c
Number.kt -> Numbers.kt
lauzadis Jul 10, 2024
6c80127
Capitalize V in SmithyRpcv2Protocols
lauzadis Jul 10, 2024
bcca021
Simple.kt -> SimpleTypes.kt, make Null an object, relocate String to …
lauzadis Jul 10, 2024
88a42bd
throw ServiceException
lauzadis Jul 10, 2024
061ee7d
Delegate to java.math.BigDecimal for mantissa/exponent operations
lauzadis Jul 11, 2024
febe4e7
ktlintFormat
lauzadis Jul 11, 2024
db158d2
unmark CBOR as an RPC-bound protocol
lauzadis Jul 11, 2024
840ab18
Add Native stubs for new features
lauzadis Jul 11, 2024
49f07fa
ktlintFormat
lauzadis Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changes/4f6cf597-a267-4bca-a8b9-98aa055a9a72.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "4f6cf597-a267-4bca-a8b9-98aa055a9a72",
"type": "feature",
"description": "Add support for `smithy.protocols#rpcv2Cbor` protocol",
"issues": [
"https://github.com/awslabs/aws-sdk-kotlin/issues/1302"
]
}
8 changes: 8 additions & 0 deletions .changes/d079d678-08d3-437a-b638-2ef11e339938.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "d079d678-08d3-437a-b638-2ef11e339938",
"type": "feature",
"description": "Add support for prioritized protocol resolution",
"issues": [
"https://github.com/smithy-lang/smithy-kotlin/issues/843"
]
}
2 changes: 2 additions & 0 deletions codegen/protocol-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ val enabledProtocols = listOf(
ProtocolTest("aws-restxml", "aws.protocoltests.restxml#RestXml"),
ProtocolTest("aws-restxml-xmlns", "aws.protocoltests.restxml.xmlns#RestXmlWithNamespace"),
ProtocolTest("aws-query", "aws.protocoltests.query#AwsQuery"),
ProtocolTest("smithy-rpcv2-cbor", "smithy.protocoltests.rpcv2Cbor#RpcV2Protocol"),

// Custom hand written tests
ProtocolTest("error-correction-json", "aws.protocoltests.errorcorrection#RequiredValueJson"),
Expand Down Expand Up @@ -82,6 +83,7 @@ dependencies {
// the aws-protocol-tests dependency is found when generating code such that the `includeServices` transform
// actually works
codegen(libs.smithy.aws.protocol.tests)
codegen(libs.smithy.protocol.tests)
}

tasks.generateSmithyProjections {
Expand Down
1 change: 1 addition & 0 deletions codegen/smithy-aws-kotlin-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
api(libs.smithy.aws.iam.traits)
api(libs.smithy.aws.cloudformation.traits)
api(libs.smithy.protocol.test.traits)
api(libs.smithy.protocol.traits)
implementation(libs.smithy.aws.endpoints)

testImplementation(libs.junit.jupiter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ class SdkProtocolGeneratorSupplier : KotlinIntegration {
RestXml(),
AwsQuery(),
Ec2Query(),
Rpcv2Cbor(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.kotlin.codegen.aws.protocols

import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.StaticHttpBindingResolver
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.model.*
import software.amazon.smithy.kotlin.codegen.model.traits.SyntheticClone
import software.amazon.smithy.kotlin.codegen.rendering.protocol.*
import software.amazon.smithy.kotlin.codegen.rendering.serde.CborParserGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.CborSerializerGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataParserGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataSerializerGenerator
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.HttpBinding
import software.amazon.smithy.model.pattern.UriPattern
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.model.traits.UnitTypeTrait
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait

class Rpcv2Cbor : AwsHttpBindingProtocolGenerator() {
lauzadis marked this conversation as resolved.
Show resolved Hide resolved
override val protocol: ShapeId = Rpcv2CborTrait.ID
override val defaultTimestampFormat = TimestampFormatTrait.Format.UNKNOWN // not used in Rpcv2Cbor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: This protocol doesn't use any timestamp formatting at all, correct? If so, using UNKNOWN feels wrong. Seems like it should just be null. This might also be an indication that we are missing an abstraction somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be null because the defaultTimestampFormat is non-nullable. We could change the interface and make it nullable but that introduces a large swath of changes that I'm not sure are appropriate.

We might be missing an abstraction, I definitely found some broken assumptions during this implemtation since this is our first binary protocol. It's not immediately clear to me how these can be separated out though.


override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver =
Rpcv2CborHttpBindingResolver(model, serviceShape)

override fun structuredDataSerializer(ctx: ProtocolGenerator.GenerationContext): StructuredDataSerializerGenerator =
CborSerializerGenerator(this)

override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator =
CborParserGenerator(this)

override fun renderDeserializeErrorDetails(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.write("#T.deserialize(payload)", RuntimeTypes.SmithyRpcv2Protocols.Cbor.Rpcv2CborErrorDeserializer)
}

override fun getDefaultHttpMiddleware(ctx: ProtocolGenerator.GenerationContext): List<ProtocolMiddleware> {
// Every request for the rpcv2Cbor protocol MUST contain a `smithy-protocol` header with the value of `rpc-v2-cbor`
val smithyProtocolHeaderMiddleware = MutateHeadersMiddleware(overrideHeaders = mapOf("smithy-protocol" to "rpc-v2-cbor"))
lauzadis marked this conversation as resolved.
Show resolved Hide resolved

// Requests with event stream responses for the rpcv2Cbor protocol MUST include an `Accept` header set to the value `application/vnd.amazon.eventstream`
val eventStreamsAcceptHeaderMiddleware = object : ProtocolMiddleware {
private val mutateHeadersMiddleware = MutateHeadersMiddleware(extraHeaders = mapOf("Accept" to "application/vnd.amazon.eventstream"))

override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean = op.isOutputEventStream(ctx.model)
override val name: String = "Rpcv2CborEventStreamsAcceptHeaderMiddleware"
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) = mutateHeadersMiddleware.render(ctx, op, writer)
}

// Emit a metric to track usage of Rpcv2Cbor
val businessMetricsMiddleware = object : ProtocolMiddleware {
override val name: String = "Rpcv2CborBusinessMetricsMiddleware"
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.write("op.context.#T(#T.PROTOCOL_RPC_V2_CBOR)", RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric, RuntimeTypes.Core.BusinessMetrics.SmithyBusinessMetric)
}
}

return super.getDefaultHttpMiddleware(ctx) + listOf(
smithyProtocolHeaderMiddleware,
eventStreamsAcceptHeaderMiddleware,
businessMetricsMiddleware
)
}

/**
* Exact copy of [AwsHttpBindingProtocolGenerator.renderSerializeHttpBody] but with a custom
* [OperationShape.hasHttpBody] function to handle protocol-specific serialization rules.
*/
override fun renderSerializeHttpBody(
ctx: ProtocolGenerator.GenerationContext,
op: OperationShape,
writer: KotlinWriter,
) {
val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service)
if (!op.hasHttpBody(ctx)) return

// payload member(s)
val requestBindings = resolver.requestBindings(op)
val httpPayload = requestBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD }
if (httpPayload != null) {
renderExplicitHttpPayloadSerializer(ctx, httpPayload, writer)
} else {
val documentMembers = requestBindings.filterDocumentBoundMembers()
// Unbound document members that should be serialized into the document format for the protocol.
// delegate to the generate operation body serializer function
val sdg = structuredDataSerializer(ctx)
val opBodySerializerFn = sdg.operationSerializer(ctx, op, documentMembers)
writer.write("val payload = #T(context, input)", opBodySerializerFn)
writer.write("builder.body = #T.fromBytes(payload)", RuntimeTypes.Http.HttpBody)
}
renderContentTypeHeader(ctx, op, writer, resolver)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is a copy of HttpBindingProtocolGenerator's renderSerializeHttpBody, not AwsHttpBindingProtocolGenerator.

Question: Why doesn't the existing HttpBindingResolver.hasHttpBody method work here? Could it be made to work for this protocol and thus eliminate the need for so much code duplication?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing hasHttpBody returns true if there are any request members bound to the payload. The rpc-v2-cbor spec says:

Requests for operations with no defined input type (as in, they target the Unit shape) MUST NOT contain bodies in their HTTP requests. The Content-Type for the serialization format MUST NOT be set.

This difference was causing some protocol tests to fail. For example, when an input targets a non-Unit shape which doesn't have any request members, the original hasHttpBody would return false, but the CBOR spec expects true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when an input targets a non-Unit shape which doesn't have any request members

Do you mean something like:

operation Foo {
    input: FooInput
}

structure FooInput { }

Wouldn't the non-CBOR protocols also have no body in that case?

Copy link
Contributor Author

@lauzadis lauzadis Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's correct, in this example, non-CBOR protocols would have no body, but CBOR is expected to send a body.

Here is the specific protocol test that fails without this custom hasHttpBody function: https://github.com/smithy-lang/smithy/blob/main/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy#L106

It fails because there is no Content-Type header sent, but it is required


/**
* @return whether the operation input does _not_ target the unit shape ([UnitTypeTrait.UNIT])
*/
private fun OperationShape.hasHttpBody(ctx: ProtocolGenerator.GenerationContext): Boolean {
val input = ctx.model.expectShape(inputShape).targetOrSelf(ctx.model).let {
// If the input has been synthetically cloned from the original (most likely),
// pull the archetype and check _that_
it.getTrait<SyntheticClone>()?.let { clone ->
ctx.model.expectShape(clone.archetype).targetOrSelf(ctx.model)
} ?: it
}

return input.id != UnitTypeTrait.UNIT
}

override fun renderContentTypeHeader(
ctx: ProtocolGenerator.GenerationContext,
op: OperationShape,
writer: KotlinWriter,
resolver: HttpBindingResolver,
) {
writer.write("builder.headers.setMissing(\"Content-Type\", #S)", resolver.determineRequestContentType(op))
}

class Rpcv2CborHttpBindingResolver(
model: Model,
val serviceShape: ServiceShape,
) : StaticHttpBindingResolver(
model,
serviceShape,
HttpTrait.builder().code(200).method("POST").uri(UriPattern.parse("/")).build(),
"application/cbor",
TimestampFormatTrait.Format.EPOCH_SECONDS,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: What is TimestampFormatTrait.Format.EPOCH_SECONDS used for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not used, it could be any value, TimestampFormatTrait.Format.UNKNOWN might be better. This is related to your previous comment about unused timestamp format but it will be tricky to untangle


override fun httpTrait(operationShape: OperationShape): HttpTrait = HttpTrait
.builder()
.code(200)
.method("POST")
.uri(UriPattern.parse("/service/${serviceShape.id.name}/operation/${operationShape.id.name}"))
.build()

override fun determineRequestContentType(operationShape: OperationShape): String = when {
operationShape.isInputEventStream(model) -> "application/vnd.amazon.eventstream"
else -> "application/cbor"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator()

val ignoredTests = TestMemberDelta(
setOf(
// This test requires populating blob members with a default value of "", which the sdk doesn't do
// These tests require populating blob members with a default value of "", which the sdk doesn't do
"AwsJson10ClientPopulatesDefaultValuesInInput",
"RpcV2CborClientPopulatesDefaultValuesInInput",

// FIXME Bug in protocol test. Temporarily disabled until the next release of smithy
// https://github.com/smithy-lang/smithy/commit/a1642aef6c6e43e3192c4f4532f6f8cea45f2a0c
"RpcV2CborDeserializesDenseSetMapAndSkipsNull",
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ val RPC_BOUND_PROTOCOLS = setOf(
"awsJson1_1",
"awsQuery",
"ec2Query",
"rpcv2Cbor",
)

/**
Expand Down
1 change: 1 addition & 0 deletions codegen/smithy-kotlin-codegen-testutils/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ version = codegenVersion
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(libs.smithy.aws.traits)
implementation(libs.smithy.protocol.traits)
api(project(":codegen:smithy-kotlin-codegen"))

// Test dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import software.amazon.smithy.model.knowledge.HttpBindingIndex
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.TimestampFormatTrait
import software.amazon.smithy.model.traits.Trait
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait
import software.amazon.smithy.utils.StringUtils

// This file houses test classes and functions relating to the code generator (protocols, serializers, etc)
Expand Down Expand Up @@ -150,6 +151,7 @@ private val allProtocols = setOf(
Ec2QueryTrait.ID,
RestJson1Trait.ID,
RestXmlTrait.ID,
Rpcv2CborTrait.ID,
)

/** An HttpBindingProtocolGenerator for testing (nothing is rendered for serializing/deserializing payload bodies) */
Expand Down
1 change: 1 addition & 0 deletions codegen/smithy-kotlin-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
api(libs.smithy.waiters)
implementation(libs.smithy.rules.engine)
implementation(libs.smithy.aws.traits)
implementation(libs.smithy.protocol.traits)
implementation(libs.smithy.protocol.test.traits)
implementation(libs.jsoup)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

package software.amazon.smithy.kotlin.codegen

import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait
import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait
import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.kotlin.codegen.lang.isValidPackageName
import software.amazon.smithy.kotlin.codegen.utils.getOrNull
Expand All @@ -17,6 +23,7 @@ import software.amazon.smithy.model.node.StringNode
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait
import java.util.Optional
import java.util.logging.Logger
import kotlin.IllegalArgumentException
Expand All @@ -34,6 +41,17 @@ private const val API_SETTINGS = "api"
// Optional specification of sdkId for models that provide them, otherwise Service's shape id name is used
private const val SDK_ID = "sdkId"

// Prioritized list of protocols supported for code generation
private val DEFAULT_PROTOCOL_RESOLUTION_PRIORITY = setOf<ShapeId>(
Rpcv2CborTrait.ID,
AwsJson1_0Trait.ID,
AwsJson1_1Trait.ID,
RestJson1Trait.ID,
RestXmlTrait.ID,
AwsQueryTrait.ID,
Ec2QueryTrait.ID,
)

/**
* Settings used by [KotlinCodegenPlugin]
*/
Expand Down Expand Up @@ -133,9 +151,10 @@ data class KotlinSettings(
supportedProtocolTraits: Set<ShapeId>,
): ShapeId {
val resolvedProtocols: Set<ShapeId> = serviceIndex.getProtocols(service).keys
val protocol = resolvedProtocols.firstOrNull(supportedProtocolTraits::contains)
val protocol = api.protocolResolutionPriority.firstOrNull { it in resolvedProtocols && supportedProtocolTraits.contains(it) }
return protocol ?: throw UnresolvableProtocolException(
"The ${service.id} service supports the following unsupported protocols $resolvedProtocols. " +
"They were evaluated using the prioritized list: ${api.protocolResolutionPriority.joinToString()}. " +
"The following protocol generators were found on the class path: $supportedProtocolTraits",
)
}
Expand Down Expand Up @@ -195,7 +214,6 @@ data class BuildSettings(
}.orNull()
}
}.orNull()

BuildSettings(generateFullProject, generateBuildFiles, annotations, generateMultiplatformProject)
}.orElse(Default)

Expand Down Expand Up @@ -274,12 +292,14 @@ data class ApiSettings(
val nullabilityCheckMode: CheckMode = CheckMode.CLIENT_CAREFUL,
val defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.WHEN_DIFFERENT,
val enableEndpointAuthProvider: Boolean = false,
val protocolResolutionPriority: Set<ShapeId> = DEFAULT_PROTOCOL_RESOLUTION_PRIORITY,
) {
companion object {
const val VISIBILITY = "visibility"
const val NULLABILITY_CHECK_MODE = "nullabilityCheckMode"
const val DEFAULT_VALUE_SERIALIZATION_MODE = "defaultValueSerializationMode"
const val ENABLE_ENDPOINT_AUTH_PROVIDER = "enableEndpointAuthProvider"
const val PROTOCOL_RESOLUTION_PRIORITY = "protocolResolutionPriority"

fun fromNode(node: Optional<ObjectNode>): ApiSettings = node.map {
val visibility = node.get()
Expand All @@ -298,7 +318,14 @@ data class ApiSettings(
),
)
val enableEndpointAuthProvider = node.get().getBooleanMemberOrDefault(ENABLE_ENDPOINT_AUTH_PROVIDER, false)
ApiSettings(visibility, checkMode, defaultValueSerializationMode, enableEndpointAuthProvider)

val protocolResolutionPriority = node.get()
.getArrayMember(PROTOCOL_RESOLUTION_PRIORITY).getOrNull()
?.map { ShapeId.from(it.asStringNode().get().value) }?.toSet() ?: run {
DEFAULT_PROTOCOL_RESOLUTION_PRIORITY
}

ApiSettings(visibility, checkMode, defaultValueSerializationMode, enableEndpointAuthProvider, protocolResolutionPriority)
}.orElse(Default)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ data class KotlinDependency(
val SERDE_JSON = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.json", RUNTIME_GROUP, "serde-json", RUNTIME_VERSION)
val SERDE_XML = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.xml", RUNTIME_GROUP, "serde-xml", RUNTIME_VERSION)
val SERDE_FORM_URL = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.formurl", RUNTIME_GROUP, "serde-form-url", RUNTIME_VERSION)
val SERDE_CBOR = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.serde.cbor", RUNTIME_GROUP, "serde-cbor", RUNTIME_VERSION)
val SMITHY_CLIENT = KotlinDependency(GradleConfiguration.Api, "$RUNTIME_ROOT_NS.client", RUNTIME_GROUP, "smithy-client", RUNTIME_VERSION)
val SMITHY_TEST = KotlinDependency(GradleConfiguration.TestImplementation, "$RUNTIME_ROOT_NS.smithy.test", RUNTIME_GROUP, "smithy-test", RUNTIME_VERSION)
val DEFAULT_HTTP_ENGINE = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.http.engine", RUNTIME_GROUP, "http-client-engine-default", RUNTIME_VERSION)
Expand All @@ -125,6 +126,8 @@ data class KotlinDependency(
val HTTP_AUTH = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.http.auth", RUNTIME_GROUP, "http-auth", RUNTIME_VERSION)
val HTTP_AUTH_AWS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.http.auth", RUNTIME_GROUP, "http-auth-aws", RUNTIME_VERSION)
val IDENTITY_API = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS", RUNTIME_GROUP, "identity-api", RUNTIME_VERSION)
val SMITHY_RPCV2_PROTOCOLS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.rpcv2", RUNTIME_GROUP, "smithy-rpcv2-protocols", RUNTIME_VERSION)
val SMITHY_RPCV2_PROTOCOLS_CBOR = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.rpcv2.cbor", RUNTIME_GROUP, "smithy-rpcv2-protocols", RUNTIME_VERSION)

// External third-party dependencies
val KOTLIN_STDLIB = KotlinDependency(GradleConfiguration.Implementation, "kotlin", "org.jetbrains.kotlin", "kotlin-stdlib", KOTLIN_COMPILER_VERSION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,12 @@ object RuntimeTypes {
val QueryLiteral = symbol("QueryLiteral")
val FormUrlSerializer = symbol("FormUrlSerializer")
}

object SerdeCbor : RuntimeTypePackage(KotlinDependency.SERDE_CBOR) {
val CborSerializer = symbol("CborSerializer")
val CborDeserializer = symbol("CborDeserializer")
val CborSerialName = symbol("CborSerialName")
}
}

object Auth {
Expand Down Expand Up @@ -422,6 +428,12 @@ object RuntimeTypes {
val parseEc2QueryErrorResponseNoSuspend = symbol("parseEc2QueryErrorResponseNoSuspend")
}

object SmithyRpcv2Protocols : RuntimeTypePackage(KotlinDependency.SMITHY_RPCV2_PROTOCOLS) {
lauzadis marked this conversation as resolved.
Show resolved Hide resolved
object Cbor : RuntimeTypePackage(KotlinDependency.SMITHY_RPCV2_PROTOCOLS_CBOR) {
val Rpcv2CborErrorDeserializer = symbol("Rpcv2CborErrorDeserializer")
}
}

object AwsEventStream : RuntimeTypePackage(KotlinDependency.AWS_EVENT_STREAM) {
val HeaderValue = symbol("HeaderValue")
val Message = symbol("Message")
Expand Down
Loading
Loading