Skip to content

Merge main into server sdk #1337

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

Merged
merged 7 commits into from
Jul 21, 2025
Merged
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
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 @@ -11,13 +11,23 @@ import software.amazon.smithy.kotlin.codegen.aws.protocols.core.AbstractQueryFor
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 +55,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
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
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
@@ -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 @@ -8,6 +8,7 @@ import software.amazon.smithy.build.MockManifest
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.kotlin.codegen.*
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
import software.amazon.smithy.kotlin.codegen.core.GenerationContext
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.model.OperationNormalizer
Expand Down Expand Up @@ -122,9 +123,11 @@ fun Model.newTestContext(
val manifest = MockManifest()
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model = this, rootNamespace = packageName, serviceName = serviceName, settings = settings)
val service = this.getShape(ShapeId.from("$packageName#$serviceName")).get().asServiceShape().get()
val delegator = KotlinDelegator(settings, this, manifest, provider, integrations)

val ctx = ProtocolGenerator.GenerationContext(
val codegenCtx = GenerationContext(this, provider, settings, generator, integrations)
val delegator = KotlinDelegator(codegenCtx, manifest, integrations)

val generationCtx = ProtocolGenerator.GenerationContext(
settings,
this,
service,
Expand All @@ -133,7 +136,8 @@ fun Model.newTestContext(
generator.protocol,
delegator,
)
return TestContext(ctx, manifest, generator)

return TestContext(generationCtx, manifest, generator)
}

fun TestContext.toCodegenContext() = object : CodegenContext {
Expand Down Expand Up @@ -173,7 +177,7 @@ fun Model.defaultSettings(
sdkId: String = TestModelDefault.SDK_ID,
generateDefaultBuildFiles: Boolean = false,
nullabilityCheckMode: CheckMode = CheckMode.CLIENT_CAREFUL,
defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.WHEN_DIFFERENT,
defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.DEFAULT,
): KotlinSettings {
val serviceId = if (serviceName == null) {
this.inferService()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ class CodegenVisitor(context: PluginContext) : ShapeVisitor.Default<Unit>() {
integration.decorateSymbolProvider(settings, model, provider)
}

writers = KotlinDelegator(settings, model, fileManifest, symbolProvider, integrations)
protocolGenerator = resolveProtocolGenerator(integrations, model, service, settings)
applicationProtocol = protocolGenerator?.applicationProtocol ?: ApplicationProtocol.createDefaultHttpApplicationProtocol()

baseGenerationContext = GenerationContext(model, symbolProvider, settings, protocolGenerator, integrations)

writers = KotlinDelegator(baseGenerationContext, fileManifest, integrations)

applicationProtocol = protocolGenerator?.applicationProtocol ?: ApplicationProtocol.createDefaultHttpApplicationProtocol()
}

private fun resolveProtocolGenerator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@

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.aws.traits.protocols.*
import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.kotlin.codegen.lang.isValidPackageName
import software.amazon.smithy.kotlin.codegen.service.ServiceFramework
Expand All @@ -25,10 +20,10 @@ 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.*
import java.util.logging.Logger
import java.util.stream.Collectors
import kotlin.IllegalArgumentException
import kotlin.streams.toList

// shapeId of service from which to generate an SDK
private const val SERVICE = "service"
Expand Down Expand Up @@ -169,7 +164,7 @@ fun Model.inferService(): ShapeId {
val services = shapes(ServiceShape::class.java)
.map(Shape::getId)
.sorted()
.toList()
.collect(Collectors.toList())

return when {
services.isEmpty() -> {
Expand Down Expand Up @@ -282,10 +277,15 @@ enum class DefaultValueSerializationMode(val value: String) {

override fun toString(): String = value
companion object {
/**
* The default value serialization mode, which is [ALWAYS]
*/
val DEFAULT = ALWAYS

fun fromValue(value: String): DefaultValueSerializationMode =
values().find {
it.value == value
} ?: throw IllegalArgumentException("$value is not a valid DefaultValueSerializationMode, expected one of ${values().map { it.value }}")
requireNotNull(entries.find { it.value.equals(value, ignoreCase = true) }) {
"$value is not a valid DefaultValueSerializationMode, expected one of ${values().map { it.value }}"
}
}
}

Expand All @@ -300,7 +300,7 @@ enum class DefaultValueSerializationMode(val value: String) {
data class ApiSettings(
val visibility: Visibility = Visibility.PUBLIC,
val nullabilityCheckMode: CheckMode = CheckMode.CLIENT_CAREFUL,
val defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.WHEN_DIFFERENT,
val defaultValueSerializationMode: DefaultValueSerializationMode = DefaultValueSerializationMode.DEFAULT,
val enableEndpointAuthProvider: Boolean = false,
val protocolResolutionPriority: Set<ShapeId> = DEFAULT_PROTOCOL_RESOLUTION_PRIORITY,
) {
Expand All @@ -324,7 +324,7 @@ data class ApiSettings(
node.get()
.getStringMemberOrDefault(
DEFAULT_VALUE_SERIALIZATION_MODE,
DefaultValueSerializationMode.WHEN_DIFFERENT.value,
DefaultValueSerializationMode.DEFAULT.value,
),
)
val enableEndpointAuthProvider = node.get().getBooleanMemberOrDefault(ENABLE_ENDPOINT_AUTH_PROVIDER, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getConte
*/
inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getContextValue(key: SectionKey<V>): V = getContextValue(key.name)

/**
* Convenience function to set a typed value in the context
* @param key
*/
inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.putContextValue(
key: SectionKey<V>,
value: V,
): W = putContext(key.name, value)

/**
* Convenience function to set context only if there is no value already associated with the given [key]
*/
Expand Down
Loading
Loading