Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .brazil.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

"com.squareup.okhttp3:okhttp-coroutines:5.*": "OkHttp3Coroutines-5.x",
"com.squareup.okhttp3:okhttp:5.*": "OkHttp3-5.x",
"com.squareup.okhttp3:okhttp-jvm:5.*": "OkHttp3-5.x",
"com.squareup.okio:okio-jvm:3.*": "OkioJvm-3.x",
"io.opentelemetry:opentelemetry-api:1.*": "Maven-io-opentelemetry_opentelemetry-api-1.x",
"io.opentelemetry:opentelemetry-extension-kotlin:1.*": "Maven-io-opentelemetry_opentelemetry-extension-kotlin-1.x",
"org.slf4j:slf4j-api:2.*": "Maven-org-slf4j_slf4j-api-2.x",
"aws.sdk.kotlin.crt:aws-crt-kotlin:0.10.*": "AwsCrtKotlin-0.10.x",
"aws.sdk.kotlin.crt:aws-crt-kotlin:0.9.*": "AwsCrtKotlin-0.9.x",
"aws.sdk.kotlin.crt:aws-crt-kotlin:0.8.*": "AwsCrtKotlin-0.8.x",
"com.squareup.okhttp3:okhttp:4.*": "OkHttp3-4.x",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/merge-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
uses: awslabs/aws-kotlin-repo-tools/.github/actions/merge-main@main
with:
ci-user-pat: ${{ secrets.CI_USER_PAT }}
exempt-branches: # Add any if required
exempt-branches: # Add any if required
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## [1.5.1] - 07/17/2025

## [1.5.0] - 07/17/2025

### Features
* Upgrade to Kotlin 2.2.0
* [#1413](https://github.com/awslabs/aws-sdk-kotlin/issues/1413) ⚠️ **IMPORTANT**: Refactor endpoint discoverer classes into interfaces so custom implementations may be provided

### Fixes
* [#1311](https://github.com/smithy-lang/smithy-kotlin/issues/1311) Reimplement idle connection monitoring using `okhttp3.EventListener` instead of now-internal `okhttp3.ConnectionListener`
* [#1608](https://github.com/awslabs/aws-sdk-kotlin/issues/1608) Switch to always serialize defaults in requests. Previously fields were not serialized in requests if they weren't `@required` and hadn't been changed from the default value.
* [#1413](https://github.com/awslabs/aws-sdk-kotlin/issues/1413) Favor `endpointUrl` instead of endpoint discovery if both are provided

### Miscellaneous
* Add telemetry provider configuration to `DefaultAwsSigner`

## [1.4.23] - 07/15/2025

## [1.4.22] - 07/02/2025
Expand Down
3 changes: 0 additions & 3 deletions bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import java.util.*

plugins {
`maven-publish`
Expand Down Expand Up @@ -52,7 +50,6 @@ fun createBomConstraintsAndVersionCatalog() {

fun Project.artifactId(target: KotlinTarget): String = when (target) {
is KotlinMetadataTarget -> name
is KotlinJsTarget -> "$name-js"
else -> "$name-${target.targetName.lowercase()}"
}

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ apiValidation {
"nullability-tests",
"paginator-tests",
"waiter-tests",
"service-codegen-tests",
"compile",
"slf4j-1x-consumer",
"slf4j-2x-consumer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,27 @@ class RegionSupport : KotlinIntegration {
name = "region"
symbol = KotlinTypes.String.toBuilder().nullable().build()
documentation = """
The region to sign with and make requests to.
The AWS region to sign with and make requests to. When specified, this static region configuration
takes precedence over other region resolution methods.

The region resolution order is:
1. Static region (if specified)
2. Custom region provider (if configured)
3. Default region provider chain
""".trimIndent()
}

val RegionProviderProp: ConfigProperty = ConfigProperty {
name = "regionProvider"
symbol = RuntimeTypes.SmithyClient.Region.RegionProvider
documentation = """
An optional region provider that determines the AWS region for client operations. When specified, this provider
takes precedence over the default region provider chain, unless a static region is explicitly configured.

The region resolution order is:
1. Static region (if specified)
2. Custom region provider (if configured)
3. Default region provider chain
""".trimIndent()
}
}
Expand All @@ -57,7 +77,7 @@ class RegionSupport : KotlinIntegration {
return supportsSigv4 || hasRegionBuiltin || isAwsSdk
}

override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(RegionProp)
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(RegionProp, RegionProviderProp)

override fun customizeEndpointResolution(ctx: ProtocolGenerator.GenerationContext): EndpointCustomization =
object : EndpointCustomization {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,28 @@ package software.amazon.smithy.kotlin.codegen.aws.protocols

import software.amazon.smithy.aws.traits.protocols.AwsQueryErrorTrait
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AbstractQueryFormUrlSerializerGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AwsHttpBindingProtocolGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.QueryHttpBindingProtocolGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.formurl.QuerySerdeFormUrlDescriptorGenerator
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.RenderingContext
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
import software.amazon.smithy.kotlin.codegen.model.*
import software.amazon.smithy.kotlin.codegen.rendering.protocol.*
import software.amazon.smithy.kotlin.codegen.rendering.serde.*
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
import software.amazon.smithy.kotlin.codegen.model.getTrait
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
import software.amazon.smithy.kotlin.codegen.rendering.protocol.toRenderingContext
import software.amazon.smithy.kotlin.codegen.rendering.serde.FormUrlSerdeDescriptorGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataParserGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.StructuredDataSerializerGenerator
import software.amazon.smithy.kotlin.codegen.rendering.serde.XmlParserGenerator
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.*
import software.amazon.smithy.model.traits.XmlFlattenedTrait
import software.amazon.smithy.model.traits.XmlNameTrait

/**
* Handles generating the aws.protocols#awsQuery protocol for services.
Expand Down Expand Up @@ -45,7 +56,7 @@ class AwsQuery : QueryHttpBindingProtocolGenerator() {
writer: KotlinWriter,
) {
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend)
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse)
}
}

Expand Down Expand Up @@ -76,6 +87,14 @@ private class AwsQuerySerializerGenerator(
members: List<MemberShape>,
writer: KotlinWriter,
): FormUrlSerdeDescriptorGenerator = AwsQuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)

override fun errorSerializer(
ctx: ProtocolGenerator.GenerationContext,
errorShape: StructureShape,
members: List<MemberShape>,
): Symbol {
TODO("Used for service-codegen. Not yet implemented")
}
}

private class AwsQueryXmlParserGenerator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AbstractQueryFormUrlSerializerGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.core.QueryHttpBindingProtocolGenerator
import software.amazon.smithy.kotlin.codegen.aws.protocols.formurl.QuerySerdeFormUrlDescriptorGenerator
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.RenderingContext
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
import software.amazon.smithy.kotlin.codegen.model.getTrait
import software.amazon.smithy.kotlin.codegen.model.isNullable
Expand Down Expand Up @@ -39,7 +42,7 @@ class Ec2Query : QueryHttpBindingProtocolGenerator() {
writer: KotlinWriter,
) {
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponseNoSuspend)
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseEc2QueryErrorResponse)
}
}

Expand Down Expand Up @@ -95,6 +98,14 @@ private class Ec2QuerySerializerGenerator(
members: List<MemberShape>,
writer: KotlinWriter,
): FormUrlSerdeDescriptorGenerator = Ec2QuerySerdeFormUrlDescriptorGenerator(ctx.toRenderingContext(protocolGenerator, shape, writer), members)

override fun errorSerializer(
ctx: ProtocolGenerator.GenerationContext,
errorShape: StructureShape,
members: List<MemberShape>,
): Symbol {
TODO("Used for service-codegen. Not yet implemented")
}
}

private class Ec2QueryParserGenerator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ class RestJson1 : JsonHttpBindingProtocolGenerator() {
writer: KotlinWriter,
) {
super.renderSerializeHttpBody(ctx, op, writer)
if (ctx.settings.build.generateServiceProject) return

val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service)
if (!resolver.hasHttpBody(op)) return

if (!resolver.hasHttpRequestBody(op)) return

// restjson1 has some different semantics and expectations around empty structures bound via @httpPayload trait
// * empty structures get serialized to `{}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ open class RestXml : AwsHttpBindingProtocolGenerator() {
writer: KotlinWriter,
) {
writer.write("""checkNotNull(payload){ "unable to parse error from empty response" }""")
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponseNoSuspend)
writer.write("#T(payload)", RuntimeTypes.AwsXmlProtocols.parseRestXmlErrorResponse)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,30 @@ class RpcV2Cbor : AwsHttpBindingProtocolGenerator() {
if (!op.hasHttpBody(ctx)) return

// payload member(s)
val requestBindings = resolver.requestBindings(op)
val httpPayload = requestBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD }
val bindings = if (ctx.settings.build.generateServiceProject) {
resolver.responseBindings(op)
} else {
resolver.requestBindings(op)
}
val httpPayload = bindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD }

if (httpPayload != null) {
renderExplicitHttpPayloadSerializer(ctx, httpPayload, writer)
} else {
val documentMembers = requestBindings.filterDocumentBoundMembers()
val documentMembers = bindings.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("builder.body = #T(context, input)", opBodySerializerFn)
if (ctx.settings.build.generateServiceProject) {
writer.write("response = #T(context, input)", opBodySerializerFn)
} else {
writer.write("builder.body = #T(context, input)", opBodySerializerFn)
}
}
if (!ctx.settings.build.generateServiceProject) {
renderContentTypeHeader(ctx, op, writer, resolver)
}
renderContentTypeHeader(ctx, op, writer, resolver)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ open class StaticHttpBindingResolver(
/**
* By default returns all inputs as [HttpBinding.Location.DOCUMENT]
*/
override fun requestBindings(operationShape: OperationShape): List<HttpBindingDescriptor> {
if (!operationShape.input.isPresent) return emptyList()
val input = model.expectShape(operationShape.input.get())
return input.members().map { member -> HttpBindingDescriptor(member, HttpBinding.Location.DOCUMENT) }.toList()
override fun requestBindings(shape: Shape): List<HttpBindingDescriptor> {
when (shape) {
is OperationShape -> {
if (!shape.input.isPresent) return emptyList()
val input = model.expectShape(shape.input.get())
return input.members().map { member -> HttpBindingDescriptor(member, HttpBinding.Location.DOCUMENT) }.toList()
}
is StructureShape -> return shape.members().map { member -> member.toHttpBindingDescriptor() }.toList()
else -> error("unimplemented shape type for http response bindings: $shape")
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package software.amazon.smithy.kotlin.codegen.aws.customization

import org.junit.jupiter.api.Test
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientConfigGenerator
import software.amazon.smithy.kotlin.codegen.test.*
import software.amazon.smithy.model.shapes.ServiceShape

class RegionSupportTest {
@Test
fun testRegionSupportProperties() {
val model = """
namespace com.test

use aws.protocols#awsJson1_1
use aws.api#service
use aws.auth#sigv4

@service(sdkId: "service with overrides", endpointPrefix: "service-with-overrides")
@sigv4(name: "example")
@awsJson1_1
service Example {
version: "1.0.0",
operations: [GetFoo]
}

operation GetFoo {}
""".toSmithyModel()

val serviceShape = model.expectShape<ServiceShape>("com.test#Example")

val testCtx = model.newTestContext(serviceName = "Example")
val writer = KotlinWriter("com.test")

val renderingCtx = testCtx.toRenderingContext(writer, serviceShape)
.copy(integrations = listOf(RegionSupport()))

ServiceClientConfigGenerator(serviceShape, detectDefaultProps = false).render(renderingCtx, renderingCtx.writer)
val contents = writer.toString()

val expectedProps = """
public val region: String? = builder.region
public val regionProvider: RegionProvider? = builder.regionProvider
""".formatForTest()
contents.shouldContainOnlyOnceWithDiff(expectedProps)

val expectedImpl = """
/**
* The AWS region to sign with and make requests to. When specified, this static region configuration
* takes precedence over other region resolution methods.
*
* The region resolution order is:
* 1. Static region (if specified)
* 2. Custom region provider (if configured)
* 3. Default region provider chain
*/
public var region: String? = null

/**
* An optional region provider that determines the AWS region for client operations. When specified, this provider
* takes precedence over the default region provider chain, unless a static region is explicitly configured.
*
* The region resolution order is:
* 1. Static region (if specified)
* 2. Custom region provider (if configured)
* 3. Default region provider chain
*/
public var regionProvider: RegionProvider? = null
""".formatForTest(indent = " ")
contents.shouldContainOnlyOnceWithDiff(expectedImpl)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ class AwsHttpBindingProtocolGeneratorTest {
): Symbol {
error("Unneeded for test")
}

override fun errorSerializer(
ctx: ProtocolGenerator.GenerationContext,
errorShape: StructureShape,
members: List<MemberShape>,
): Symbol {
error("Unneeded for test")
}
}

override val protocol: ShapeId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ class MockHttpProtocolGenerator(model: Model) : HttpBindingProtocolGenerator() {
val symbol = ctx.symbolProvider.toSymbol(shape)
name = "serialize" + StringUtils.capitalize(symbol.name) + "Payload"
}

override fun errorSerializer(
ctx: ProtocolGenerator.GenerationContext,
errorShape: StructureShape,
members: List<MemberShape>,
): Symbol {
error("Unneeded for test")
}
}

override fun operationErrorHandler(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Symbol = buildSymbol {
Expand Down
Loading
Loading