Skip to content

Commit 1077f10

Browse files
authored
feat: provide client config property for region provider (#1210)
* api dump * feat: add region provider * remove unrelated apiDump * address pr reviews * address pr reviews * address pr review * lint * address pr reviews * address pr reviews * remove breaking change post
1 parent e7e342d commit 1077f10

File tree

7 files changed

+227
-2
lines changed

7 files changed

+227
-2
lines changed

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/customization/RegionSupport.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,27 @@ class RegionSupport : KotlinIntegration {
4040
name = "region"
4141
symbol = KotlinTypes.String.toBuilder().nullable().build()
4242
documentation = """
43-
The region to sign with and make requests to.
43+
The AWS region to sign with and make requests to. When specified, this static region configuration
44+
takes precedence over other region resolution methods.
45+
46+
The region resolution order is:
47+
1. Static region (if specified)
48+
2. Custom region provider (if configured)
49+
3. Default region provider chain
50+
""".trimIndent()
51+
}
52+
53+
val RegionProviderProp: ConfigProperty = ConfigProperty {
54+
name = "regionProvider"
55+
symbol = RuntimeTypes.SmithyClient.Region.RegionProvider
56+
documentation = """
57+
An optional region provider that determines the AWS region for client operations. When specified, this provider
58+
takes precedence over the default region provider chain, unless a static region is explicitly configured.
59+
60+
The region resolution order is:
61+
1. Static region (if specified)
62+
2. Custom region provider (if configured)
63+
3. Default region provider chain
4464
""".trimIndent()
4565
}
4666
}
@@ -57,7 +77,7 @@ class RegionSupport : KotlinIntegration {
5777
return supportsSigv4 || hasRegionBuiltin || isAwsSdk
5878
}
5979

60-
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(RegionProp)
80+
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(RegionProp, RegionProviderProp)
6181

6282
override fun customizeEndpointResolution(ctx: ProtocolGenerator.GenerationContext): EndpointCustomization =
6383
object : EndpointCustomization {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package software.amazon.smithy.kotlin.codegen.aws.customization
2+
3+
import org.junit.jupiter.api.Test
4+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
5+
import software.amazon.smithy.kotlin.codegen.model.expectShape
6+
import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientConfigGenerator
7+
import software.amazon.smithy.kotlin.codegen.test.*
8+
import software.amazon.smithy.model.shapes.ServiceShape
9+
10+
class RegionSupportTest {
11+
@Test
12+
fun testRegionSupportProperties() {
13+
val model = """
14+
namespace com.test
15+
16+
use aws.protocols#awsJson1_1
17+
use aws.api#service
18+
use aws.auth#sigv4
19+
20+
@service(sdkId: "service with overrides", endpointPrefix: "service-with-overrides")
21+
@sigv4(name: "example")
22+
@awsJson1_1
23+
service Example {
24+
version: "1.0.0",
25+
operations: [GetFoo]
26+
}
27+
28+
operation GetFoo {}
29+
""".toSmithyModel()
30+
31+
val serviceShape = model.expectShape<ServiceShape>("com.test#Example")
32+
33+
val testCtx = model.newTestContext(serviceName = "Example")
34+
val writer = KotlinWriter("com.test")
35+
36+
val renderingCtx = testCtx.toRenderingContext(writer, serviceShape)
37+
.copy(integrations = listOf(RegionSupport()))
38+
39+
ServiceClientConfigGenerator(serviceShape, detectDefaultProps = false).render(renderingCtx, renderingCtx.writer)
40+
val contents = writer.toString()
41+
42+
val expectedProps = """
43+
public val region: String? = builder.region
44+
public val regionProvider: RegionProvider? = builder.regionProvider
45+
""".formatForTest()
46+
contents.shouldContainOnlyOnceWithDiff(expectedProps)
47+
48+
val expectedImpl = """
49+
/**
50+
* The AWS region to sign with and make requests to. When specified, this static region configuration
51+
* takes precedence over other region resolution methods.
52+
*
53+
* The region resolution order is:
54+
* 1. Static region (if specified)
55+
* 2. Custom region provider (if configured)
56+
* 3. Default region provider chain
57+
*/
58+
public var region: String? = null
59+
60+
/**
61+
* An optional region provider that determines the AWS region for client operations. When specified, this provider
62+
* takes precedence over the default region provider chain, unless a static region is explicitly configured.
63+
*
64+
* The region resolution order is:
65+
* 1. Static region (if specified)
66+
* 2. Custom region provider (if configured)
67+
* 3. Default region provider chain
68+
*/
69+
public var regionProvider: RegionProvider? = null
70+
""".formatForTest(indent = " ")
71+
contents.shouldContainOnlyOnceWithDiff(expectedImpl)
72+
}
73+
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ object RuntimeTypes {
251251
val Url = symbol("Url")
252252
}
253253
}
254+
255+
object Region : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "region") {
256+
val RegionProvider = symbol("RegionProvider")
257+
}
254258
}
255259

256260
object Serde : RuntimeTypePackage(KotlinDependency.SERDE) {

runtime/smithy-client/api/smithy-client.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,15 @@ public final class aws/smithy/kotlin/runtime/client/endpoints/functions/Url {
353353
public fun toString ()Ljava/lang/String;
354354
}
355355

356+
public abstract interface class aws/smithy/kotlin/runtime/client/region/RegionProvider {
357+
public abstract fun getRegion (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
358+
}
359+
360+
public class aws/smithy/kotlin/runtime/client/region/RegionProviderChain : aws/smithy/kotlin/runtime/client/region/RegionProvider {
361+
public fun <init> (Ljava/util/List;)V
362+
public fun <init> ([Laws/smithy/kotlin/runtime/client/region/RegionProvider;)V
363+
protected final fun getProviders ()[Laws/smithy/kotlin/runtime/client/region/RegionProvider;
364+
public fun getRegion (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
365+
public fun toString ()Ljava/lang/String;
366+
}
367+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.smithy.kotlin.runtime.client.region
7+
8+
/**
9+
* Interface for providing AWS region information. Implementations are free to use any strategy for
10+
* providing region information
11+
*/
12+
public interface RegionProvider {
13+
/**
14+
* Return the region name to use. If region information is not available, implementations should return null
15+
*/
16+
public suspend fun getRegion(): String?
17+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.smithy.kotlin.runtime.client.region
7+
8+
import aws.smithy.kotlin.runtime.telemetry.logging.logger
9+
import aws.smithy.kotlin.runtime.util.asyncLazy
10+
import kotlin.coroutines.coroutineContext
11+
12+
/**
13+
* Composite [RegionProvider] that delegates to a chain of providers.
14+
* [providers] are consulted in the order given and the first region found is returned
15+
*
16+
* @param providers the list of providers to delegate to
17+
*/
18+
public open class RegionProviderChain(
19+
protected vararg val providers: RegionProvider,
20+
) : RegionProvider {
21+
22+
public constructor(providers: List<RegionProvider>) : this(*providers.toTypedArray())
23+
24+
private val resolvedRegion = asyncLazy(::resolveRegion)
25+
26+
init {
27+
require(providers.isNotEmpty()) { "at least one provider must be in the chain" }
28+
}
29+
30+
override fun toString(): String =
31+
(listOf(this) + providers).map { it::class.simpleName }.joinToString(" -> ")
32+
33+
override suspend fun getRegion(): String? = resolvedRegion.get()
34+
35+
private suspend fun resolveRegion(): String? {
36+
val logger = coroutineContext.logger<RegionProviderChain>()
37+
for (provider in providers) {
38+
try {
39+
val region = provider.getRegion()
40+
if (region != null) {
41+
logger.debug { "resolved region ($region) from $provider " }
42+
return region
43+
}
44+
logger.debug { "failed to resolve region from $provider" }
45+
} catch (ex: Exception) {
46+
logger.debug { "unable to load region from $provider: ${ex.message}" }
47+
}
48+
}
49+
50+
return null
51+
}
52+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package aws.smithy.kotlin.runtime.client.region
7+
8+
import kotlinx.coroutines.test.runTest
9+
import kotlin.test.Test
10+
import kotlin.test.assertEquals
11+
import kotlin.test.assertFails
12+
13+
class RegionProviderChainTest {
14+
@Test
15+
fun testNoProviders() {
16+
assertFails("at least one provider") {
17+
RegionProviderChain()
18+
}
19+
}
20+
data class TestProvider(val region: String? = null) : RegionProvider {
21+
override suspend fun getRegion(): String? = region
22+
}
23+
24+
@Test
25+
fun testChain() = runTest {
26+
val chain = RegionProviderChain(
27+
TestProvider(null),
28+
TestProvider("us-east-1"),
29+
TestProvider("us-east-2"),
30+
)
31+
32+
assertEquals("us-east-1", chain.getRegion())
33+
}
34+
35+
@Test
36+
fun testChainList() = runTest {
37+
val providers = listOf<RegionProvider>(
38+
TestProvider(null),
39+
TestProvider("us-east-1"),
40+
TestProvider("us-east-2"),
41+
)
42+
43+
val chain = RegionProviderChain(providers)
44+
45+
assertEquals("us-east-1", chain.getRegion())
46+
}
47+
}

0 commit comments

Comments
 (0)