Skip to content

Commit df2ad9a

Browse files
authored
misc: upgrade to latest version of smithy-kotlin (#1338)
1 parent 07a4751 commit df2ad9a

File tree

12 files changed

+427
-5
lines changed

12 files changed

+427
-5
lines changed

aws-runtime/aws-http/api/aws-http.api

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public final class aws/sdk/kotlin/runtime/http/AwsUserAgentMetadata$Companion {
4848
public final class aws/sdk/kotlin/runtime/http/AwsUserAgentMetadataKt {
4949
public static final field AWS_APP_ID_ENV Ljava/lang/String;
5050
public static final field AWS_APP_ID_PROP Ljava/lang/String;
51+
public static final field BUSINESS_METRICS_MAX_LENGTH I
5152
}
5253

5354
public final class aws/sdk/kotlin/runtime/http/ExecutionEnvMetadata {
@@ -139,6 +140,14 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AddUserAgentMetadata
139140
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
140141
}
141142

143+
public final class aws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric : java/lang/Enum, aws/smithy/kotlin/runtime/businessmetrics/BusinessMetric {
144+
public static final field S3_EXPRESS_BUCKET Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
145+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
146+
public fun getIdentifier ()Ljava/lang/String;
147+
public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
148+
public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric;
149+
}
150+
142151
public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
143152
public static final field INSTANCE Laws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor;
144153
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -162,6 +171,29 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor :
162171
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
163172
}
164173

174+
public final class aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
175+
public fun <init> ()V
176+
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
177+
public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
178+
public fun modifyBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
179+
public fun modifyBeforeRetryLoop (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
180+
public fun modifyBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
181+
public fun modifyBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
182+
public fun modifyBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
183+
public fun readAfterAttempt (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
184+
public fun readAfterDeserialization (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
185+
public fun readAfterExecution (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;)V
186+
public fun readAfterSerialization (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
187+
public fun readAfterSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
188+
public fun readAfterTransmit (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V
189+
public fun readBeforeAttempt (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
190+
public fun readBeforeDeserialization (Laws/smithy/kotlin/runtime/client/ProtocolResponseInterceptorContext;)V
191+
public fun readBeforeExecution (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V
192+
public fun readBeforeSerialization (Laws/smithy/kotlin/runtime/client/RequestInterceptorContext;)V
193+
public fun readBeforeSigning (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
194+
public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V
195+
}
196+
165197
public final class aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor : aws/smithy/kotlin/runtime/client/Interceptor {
166198
public fun <init> ()V
167199
public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/AwsUserAgentMetadata.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import kotlin.jvm.JvmInline
1414

1515
internal const val AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"
1616
public const val AWS_APP_ID_ENV: String = "AWS_SDK_UA_APP_ID"
17+
private const val USER_AGENT_SPEC_VERSION = "2.1"
18+
public const val BUSINESS_METRICS_MAX_LENGTH: Int = 1024
1719

1820
// non-standard environment variables/properties
1921
public const val AWS_APP_ID_PROP: String = "aws.userAgentAppId"
@@ -65,7 +67,7 @@ public data class AwsUserAgentMetadata(
6567
get() = buildList {
6668
add(sdkMetadata)
6769
customMetadata?.extras?.takeIf { it.containsKey("internal") }?.let { add("md/internal") }
68-
add(uaPair("ua", "2.0")) // User agent specification version 2.0
70+
add(uaPair("ua", USER_AGENT_SPEC_VERSION))
6971
add(apiMetadata)
7072
add(osMetadata)
7173
add(languageMetadata)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.runtime.http.interceptors
6+
7+
import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH
8+
import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT
9+
import aws.smithy.kotlin.runtime.InternalApi
10+
import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric
11+
import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics
12+
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
13+
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
14+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
15+
import aws.smithy.kotlin.runtime.http.request.toBuilder
16+
17+
/**
18+
* Appends business metrics to the `User-Agent` header.
19+
*/
20+
public class BusinessMetricsInterceptor : HttpInterceptor {
21+
override suspend fun modifyBeforeTransmit(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): HttpRequest {
22+
context.executionContext.getOrNull(BusinessMetrics)?.let { metrics ->
23+
val metricsString = formatMetrics(metrics)
24+
val currentUserAgentHeader = context.protocolRequest.headers[USER_AGENT]
25+
val modifiedRequest = context.protocolRequest.toBuilder()
26+
27+
modifiedRequest.headers[USER_AGENT] = currentUserAgentHeader + metricsString
28+
29+
return modifiedRequest.build()
30+
}
31+
return context.protocolRequest
32+
}
33+
}
34+
35+
/**
36+
* Makes sure the metrics do not exceed the maximum size and truncates them if so.
37+
*/
38+
private fun formatMetrics(metrics: MutableSet<String>): String {
39+
if (metrics.isEmpty()) return ""
40+
val metricsString = metrics.joinToString(",", "m/")
41+
val metricsByteArray = metricsString.encodeToByteArray()
42+
43+
if (metricsByteArray.size <= BUSINESS_METRICS_MAX_LENGTH) return metricsString
44+
45+
val lastCommaIndex = metricsByteArray
46+
.sliceArray(0 until 1024)
47+
.indexOfLast { it == ','.code.toByte() }
48+
.takeIf { it != -1 }
49+
50+
lastCommaIndex?.let {
51+
return metricsByteArray.decodeToString(
52+
0,
53+
lastCommaIndex,
54+
true,
55+
)
56+
}
57+
58+
throw IllegalStateException("Business metrics are incorrectly formatted: $metricsString")
59+
}
60+
61+
/**
62+
* AWS SDK specific business metrics
63+
*/
64+
@InternalApi
65+
public enum class AwsBusinessMetric(public override val identifier: String) : BusinessMetric {
66+
S3_EXPRESS_BUCKET("J"),
67+
}

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/AwsUserAgentMetadataTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class AwsUserAgentMetadataTest {
4545
val expected = listOf(
4646
"aws-sdk-kotlin/1.2.3",
4747
"md/internal",
48-
"ua/2.0",
48+
"ua/2.1",
4949
"api/test-service#1.2.3",
5050
"os/linux#ubuntu-20.04",
5151
"lang/kotlin#1.4.31",
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.runtime.http.interceptors
6+
7+
import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH
8+
import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT
9+
import aws.smithy.kotlin.runtime.businessmetrics.SmithyBusinessMetric
10+
import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric
11+
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
12+
import aws.smithy.kotlin.runtime.collections.get
13+
import aws.smithy.kotlin.runtime.http.*
14+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
15+
import aws.smithy.kotlin.runtime.net.url.Url
16+
import aws.smithy.kotlin.runtime.operation.ExecutionContext
17+
import kotlinx.coroutines.test.runTest
18+
import kotlin.test.Test
19+
import kotlin.test.assertFailsWith
20+
import kotlin.test.assertFalse
21+
import kotlin.test.assertTrue
22+
23+
class BusinessMetricsInterceptorTest {
24+
@Test
25+
fun noBusinessMetrics() = runTest {
26+
val executionContext = ExecutionContext()
27+
val interceptor = BusinessMetricsInterceptor()
28+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
29+
val userAgentHeader = request.headers[USER_AGENT]!!
30+
31+
assertFalse(userAgentHeader.endsWith("m/"))
32+
}
33+
34+
@Test
35+
fun businessMetrics() = runTest {
36+
val executionContext = ExecutionContext()
37+
executionContext.emitBusinessMetric(AwsBusinessMetric.S3_EXPRESS_BUCKET)
38+
39+
val interceptor = BusinessMetricsInterceptor()
40+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
41+
val userAgentHeader = request.headers[USER_AGENT]!!
42+
43+
assertTrue(
44+
userAgentHeader.endsWith(
45+
"m/${AwsBusinessMetric.S3_EXPRESS_BUCKET.identifier}",
46+
),
47+
)
48+
}
49+
50+
@Test
51+
fun multipleBusinessMetrics() = runTest {
52+
val executionContext = ExecutionContext()
53+
executionContext.emitBusinessMetric(AwsBusinessMetric.S3_EXPRESS_BUCKET)
54+
executionContext.emitBusinessMetric(SmithyBusinessMetric.GZIP_REQUEST_COMPRESSION)
55+
56+
val interceptor = BusinessMetricsInterceptor()
57+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
58+
val userAgentHeader = request.headers[USER_AGENT]!!
59+
60+
assertTrue(
61+
userAgentHeader.endsWith(
62+
"m/${AwsBusinessMetric.S3_EXPRESS_BUCKET.identifier},${SmithyBusinessMetric.GZIP_REQUEST_COMPRESSION.identifier}",
63+
),
64+
)
65+
}
66+
67+
@Test
68+
fun truncateBusinessMetrics() = runTest {
69+
val executionContext = ExecutionContext()
70+
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf()
71+
72+
for (i in 0..1024) {
73+
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics].add(i.toString())
74+
}
75+
76+
val rawMetrics = executionContext[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics]
77+
val rawMetricsString = rawMetrics.joinToString(",", "m/")
78+
val rawMetricsByteArray = rawMetricsString.encodeToByteArray()
79+
80+
assertTrue(rawMetricsByteArray.size >= BUSINESS_METRICS_MAX_LENGTH)
81+
82+
val interceptor = BusinessMetricsInterceptor()
83+
val request = interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
84+
val userAgentHeader = request.headers[USER_AGENT]!!
85+
val truncatedMetrics = "m/" + userAgentHeader.substringAfter("m/")
86+
87+
assertTrue(truncatedMetrics.encodeToByteArray().size <= BUSINESS_METRICS_MAX_LENGTH)
88+
assertFalse(truncatedMetrics.endsWith(","))
89+
}
90+
91+
@Test
92+
fun malformedBusinessMetrics() = runTest {
93+
val executionContext = ExecutionContext()
94+
95+
executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf(
96+
"A".repeat(BUSINESS_METRICS_MAX_LENGTH),
97+
)
98+
99+
val interceptor = BusinessMetricsInterceptor()
100+
101+
assertFailsWith<IllegalStateException>("Business metrics are incorrectly formatted:") {
102+
interceptor.modifyBeforeTransmit(interceptorContext(executionContext))
103+
}
104+
}
105+
}
106+
107+
private fun interceptorContext(executionContext: ExecutionContext): ProtocolRequestInterceptorContext<Any, HttpRequest> =
108+
object : ProtocolRequestInterceptorContext<Any, HttpRequest> {
109+
override val protocolRequest: HttpRequest = HttpRequest(
110+
HttpMethod.GET,
111+
Url.parse("https://test.aws.com?foo=bar"),
112+
Headers {
113+
append(USER_AGENT, "aws-sdk-kotlin/1.2.3 ua/2.1 api/test-service#1.2.3...")
114+
},
115+
)
116+
override val executionContext: ExecutionContext = executionContext
117+
override val request: Any = Unit
118+
}

aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/middleware/UserAgentTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class UserAgentTest {
4747
assertTrue(request.headers.contains(X_AMZ_USER_AGENT))
4848
assertEquals("aws-sdk-kotlin/1.2.3", request.headers[X_AMZ_USER_AGENT])
4949
assertTrue(
50-
request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 ua/2.0 api/test-service#1.2.3"),
50+
request.headers[USER_AGENT]!!.startsWith("aws-sdk-kotlin/1.2.3 ua/2.1 api/test-service#1.2.3"),
5151
"$USER_AGENT header didn't start with expected value. Found: ${request.headers[USER_AGENT]}",
5252
)
5353
}

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ object AwsRuntimeTypes {
6060
object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") {
6161
val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor")
6262
val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor")
63+
val BusinessMetricsInterceptor = symbol("BusinessMetricsInterceptor")
64+
val AwsBusinessMetric = symbol("AwsBusinessMetric")
6365
}
6466

6567
object Retries {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.codegen
6+
7+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
8+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
9+
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes
10+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
11+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriter
12+
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
13+
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointBusinessMetrics
14+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
15+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
16+
import software.amazon.smithy.model.shapes.OperationShape
17+
18+
/**
19+
* Renders the addition of the [BusinessMetricsInterceptor] and endpoint business metrics emitters
20+
*/
21+
class BusinessMetricsIntegration : KotlinIntegration {
22+
override val order: Byte
23+
get() = super.order
24+
25+
override val sectionWriters: List<SectionWriterBinding>
26+
get() = listOf(
27+
SectionWriterBinding(EndpointBusinessMetrics, endpointBusinessMetricsSectionWriter),
28+
)
29+
30+
private val endpointBusinessMetricsSectionWriter = SectionWriter { writer, _ ->
31+
writer.write("")
32+
writer.write(
33+
"if (endpoint.attributes.contains(#T)) request.context.#T(#T.SERVICE_ENDPOINT_OVERRIDE)",
34+
RuntimeTypes.Core.BusinessMetrics.ServiceEndpointOverride,
35+
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
36+
RuntimeTypes.Core.BusinessMetrics.SmithyBusinessMetric,
37+
)
38+
writer.write(
39+
"if (endpoint.attributes.contains(#T)) request.context.#T(#T.ACCOUNT_ID_BASED_ENDPOINT)",
40+
RuntimeTypes.Core.BusinessMetrics.AccountIdBasedEndpointAccountId,
41+
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
42+
RuntimeTypes.Core.BusinessMetrics.SmithyBusinessMetric,
43+
)
44+
writer.write(
45+
"if (endpoint.attributes.contains(#T.SigningService) && endpoint.attributes[#T.SigningService] == \"s3express\") request.context.#T(#T.S3_EXPRESS_BUCKET)",
46+
AwsSigningAttributes,
47+
AwsSigningAttributes,
48+
RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric,
49+
AwsRuntimeTypes.Http.Interceptors.AwsBusinessMetric,
50+
)
51+
writer.write("")
52+
}
53+
54+
override fun customizeMiddleware(
55+
ctx: ProtocolGenerator.GenerationContext,
56+
resolved: List<ProtocolMiddleware>,
57+
): List<ProtocolMiddleware> = resolved + userAgentBusinessMetricsMiddleware
58+
59+
private val userAgentBusinessMetricsMiddleware = object : ProtocolMiddleware {
60+
override val name: String = "UserAgentBusinessMetrics"
61+
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
62+
writer.write(
63+
"op.interceptors.add(#T())",
64+
AwsRuntimeTypes.Http.Interceptors.BusinessMetricsInterceptor,
65+
)
66+
}
67+
}
68+
}

codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ aws.sdk.kotlin.codegen.customization.cloudfrontkeyvaluestore.BackfillSigV4ACusto
4242
aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegration
4343
aws.sdk.kotlin.codegen.customization.s3.express.S3ExpressIntegration
4444
aws.sdk.kotlin.codegen.customization.s3.S3ExpiresIntegration
45+
aws.sdk.kotlin.codegen.BusinessMetricsIntegration

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ coroutines-version = "1.7.3"
99
atomicfu-version = "0.23.1"
1010

1111
# smithy-kotlin codegen and runtime are versioned separately
12-
smithy-kotlin-runtime-version = "1.2.7"
13-
smithy-kotlin-codegen-version = "0.32.7"
12+
smithy-kotlin-runtime-version = "1.2.10"
13+
smithy-kotlin-codegen-version = "0.32.10"
1414

1515
# codegen
1616
smithy-version = "1.49.0"

0 commit comments

Comments
 (0)