Skip to content

Commit 2e44922

Browse files
authored
fix: overhaul endpoint discovery (#1221)
1 parent ed95d7b commit 2e44922

File tree

26 files changed

+527
-292
lines changed

26 files changed

+527
-292
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "49af01b8-6fed-4add-ace0-9f027e83425a",
3+
"type": "feature",
4+
"description": "⚠️ **IMPORTANT**: Refactor endpoint discoverer classes into interfaces so custom implementations may be provided",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#1413"
7+
],
8+
"requiresMinorVersionBump": true
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "e6515649-dab5-4be9-b4b4-b289369960d5",
3+
"type": "bugfix",
4+
"description": "Favor `endpointUrl` instead of endpoint discovery if both are provided",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#1413"
7+
]
8+
}

codegen/smithy-kotlin-codegen-testutils/src/main/kotlin/software/amazon/smithy/kotlin/codegen/test/ModelTestUtils.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import software.amazon.smithy.build.MockManifest
88
import software.amazon.smithy.codegen.core.SymbolProvider
99
import software.amazon.smithy.kotlin.codegen.*
1010
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
11+
import software.amazon.smithy.kotlin.codegen.core.GenerationContext
1112
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
1213
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
1314
import software.amazon.smithy.kotlin.codegen.model.OperationNormalizer
@@ -122,9 +123,11 @@ fun Model.newTestContext(
122123
val manifest = MockManifest()
123124
val provider: SymbolProvider = KotlinCodegenPlugin.createSymbolProvider(model = this, rootNamespace = packageName, serviceName = serviceName, settings = settings)
124125
val service = this.getShape(ShapeId.from("$packageName#$serviceName")).get().asServiceShape().get()
125-
val delegator = KotlinDelegator(settings, this, manifest, provider, integrations)
126126

127-
val ctx = ProtocolGenerator.GenerationContext(
127+
val codegenCtx = GenerationContext(this, provider, settings, generator, integrations)
128+
val delegator = KotlinDelegator(codegenCtx, manifest, integrations)
129+
130+
val generationCtx = ProtocolGenerator.GenerationContext(
128131
settings,
129132
this,
130133
service,
@@ -133,7 +136,8 @@ fun Model.newTestContext(
133136
generator.protocol,
134137
delegator,
135138
)
136-
return TestContext(ctx, manifest, generator)
139+
140+
return TestContext(generationCtx, manifest, generator)
137141
}
138142

139143
fun TestContext.toCodegenContext() = object : CodegenContext {

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/CodegenVisitor.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ class CodegenVisitor(context: PluginContext) : ShapeVisitor.Default<Unit>() {
8787
integration.decorateSymbolProvider(settings, model, provider)
8888
}
8989

90-
writers = KotlinDelegator(settings, model, fileManifest, symbolProvider, integrations)
91-
9290
protocolGenerator = resolveProtocolGenerator(integrations, model, service, settings)
93-
applicationProtocol = protocolGenerator?.applicationProtocol ?: ApplicationProtocol.createDefaultHttpApplicationProtocol()
94-
9591
baseGenerationContext = GenerationContext(model, symbolProvider, settings, protocolGenerator, integrations)
92+
93+
writers = KotlinDelegator(baseGenerationContext, fileManifest, integrations)
94+
95+
applicationProtocol = protocolGenerator?.applicationProtocol ?: ApplicationProtocol.createDefaultHttpApplicationProtocol()
9696
}
9797

9898
private fun resolveProtocolGenerator(

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getConte
150150
*/
151151
inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.getContextValue(key: SectionKey<V>): V = getContextValue(key.name)
152152

153+
/**
154+
* Convenience function to set a typed value in the context
155+
* @param key
156+
*/
157+
inline fun <W : AbstractCodeWriter<W>, reified V> AbstractCodeWriter<W>.putContextValue(
158+
key: SectionKey<V>,
159+
value: V,
160+
): W = putContext(key.name, value)
161+
153162
/**
154163
* Convenience function to set context only if there is no value already associated with the given [key]
155164
*/

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package software.amazon.smithy.kotlin.codegen.core
88
import software.amazon.smithy.codegen.core.SymbolProvider
99
import software.amazon.smithy.kotlin.codegen.KotlinSettings
1010
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
11+
import software.amazon.smithy.kotlin.codegen.integration.SectionKey
1112
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
1213
import software.amazon.smithy.model.Model
1314
import software.amazon.smithy.model.shapes.Shape
@@ -16,6 +17,10 @@ import software.amazon.smithy.model.shapes.Shape
1617
* Common codegen properties required across different codegen contexts
1718
*/
1819
interface CodegenContext {
20+
companion object {
21+
val Key = SectionKey<CodegenContext>("CodegenContext")
22+
}
23+
1924
val model: Model
2025
val symbolProvider: SymbolProvider
2126
val settings: KotlinSettings

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ package software.amazon.smithy.kotlin.codegen.core
66

77
import software.amazon.smithy.build.FileManifest
88
import software.amazon.smithy.codegen.core.*
9-
import software.amazon.smithy.kotlin.codegen.KotlinSettings
109
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
1110
import software.amazon.smithy.kotlin.codegen.model.SymbolProperty
1211
import software.amazon.smithy.kotlin.codegen.utils.namespaceToPath
13-
import software.amazon.smithy.model.Model
1412
import software.amazon.smithy.model.shapes.Shape
1513
import java.nio.file.Paths
1614

@@ -21,13 +19,10 @@ const val DEFAULT_TEST_SOURCE_SET_ROOT = "./src/test/kotlin/"
2119
* Manages writers for Kotlin files.
2220
*/
2321
class KotlinDelegator(
24-
private val settings: KotlinSettings,
25-
private val model: Model,
22+
private val ctx: CodegenContext,
2623
val fileManifest: FileManifest,
27-
private val symbolProvider: SymbolProvider,
2824
private val integrations: List<KotlinIntegration> = listOf(),
2925
) {
30-
3126
private val writers: MutableMap<String, KotlinWriter> = mutableMapOf()
3227

3328
// Tracks dependencies for source not provided by codegen that may reside in the service source tree.
@@ -91,7 +86,7 @@ class KotlinDelegator(
9186
shape: Shape,
9287
block: (KotlinWriter) -> Unit,
9388
) {
94-
val symbol = symbolProvider.toSymbol(shape)
89+
val symbol = ctx.symbolProvider.toSymbol(shape)
9590
useSymbolWriter(symbol, block)
9691
}
9792

@@ -151,7 +146,9 @@ class KotlinDelegator(
151146
val needsNewline = writers.containsKey(formattedFilename)
152147
val writer = writers.getOrPut(formattedFilename) {
153148
val kotlinWriter = KotlinWriter(namespace)
154-
if (settings.debug) kotlinWriter.enableStackTraceComments(true)
149+
kotlinWriter.putContextValue(CodegenContext.Key, ctx)
150+
151+
if (ctx.settings.debug) kotlinWriter.enableStackTraceComments(true)
155152

156153
// Register all integrations [SectionWriterBindings] on the writer.
157154
integrations.forEach { integration ->

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ object RuntimeTypes {
4545
object HttpClient : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT) {
4646
val SdkHttpClient = symbol("SdkHttpClient")
4747

48-
object Middleware : RuntimeTypePackage(KotlinDependency.HTTP, "middleware") {
48+
object Middleware : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT, "middleware") {
4949
val MutateHeadersMiddleware = symbol("MutateHeaders")
5050
}
5151

52-
object Operation : RuntimeTypePackage(KotlinDependency.HTTP, "operation") {
52+
object Operation : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT, "operation") {
5353
val AuthSchemeResolver = symbol("AuthSchemeResolver")
5454
val context = symbol("context")
5555
val EndpointResolver = symbol("EndpointResolver")
@@ -68,18 +68,19 @@ object RuntimeTypes {
6868
val setResolvedEndpoint = symbol("setResolvedEndpoint")
6969
}
7070

71-
object Config : RuntimeTypePackage(KotlinDependency.HTTP, "config") {
71+
object Config : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT, "config") {
7272
val HttpClientConfig = symbol("HttpClientConfig")
7373
val HttpEngineConfig = symbol("HttpEngineConfig")
7474
}
7575

76-
object Engine : RuntimeTypePackage(KotlinDependency.HTTP, "engine") {
76+
object Engine : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT, "engine") {
7777
val HttpClientEngine = symbol("HttpClientEngine")
7878
val manage = symbol("manage", "engine.internal", isExtension = true)
7979
}
8080

81-
object Interceptors : RuntimeTypePackage(KotlinDependency.HTTP, "interceptors") {
81+
object Interceptors : RuntimeTypePackage(KotlinDependency.HTTP_CLIENT, "interceptors") {
8282
val ContinueInterceptor = symbol("ContinueInterceptor")
83+
val DiscoveredEndpointErrorInterceptor = symbol("DiscoveredEndpointErrorInterceptor")
8384
val HttpInterceptor = symbol("HttpInterceptor")
8485
val HttpChecksumRequiredInterceptor = symbol("HttpChecksumRequiredInterceptor")
8586
val FlexibleChecksumsRequestInterceptor = symbol("FlexibleChecksumsRequestInterceptor")
@@ -97,7 +98,6 @@ object RuntimeTypes {
9798
}
9899

99100
object Core : RuntimeTypePackage(KotlinDependency.CORE) {
100-
val Clock = symbol("Clock", "time")
101101
val ExecutionContext = symbol("ExecutionContext", "operation")
102102
val ErrorMetadata = symbol("ErrorMetadata")
103103
val ServiceErrorMetadata = symbol("ServiceErrorMetadata")
@@ -125,11 +125,12 @@ object RuntimeTypes {
125125
val attributesOf = symbol("attributesOf")
126126
val AttributeKey = symbol("AttributeKey")
127127
val createOrAppend = symbol("createOrAppend")
128+
val ExpiringKeyedCache = symbol("ExpiringKeyedCache")
128129
val get = symbol("get")
129130
val mutableMultiMapOf = symbol("mutableMultiMapOf")
131+
val PeriodicSweepCache = symbol("PeriodicSweepCache")
130132
val putIfAbsent = symbol("putIfAbsent")
131133
val putIfAbsentNotNull = symbol("putIfAbsentNotNull")
132-
val ReadThroughCache = symbol("ReadThroughCache")
133134
val toMutableAttributes = symbol("toMutableAttributes")
134135
val emptyAttributes = symbol("emptyAttributes")
135136
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ExceptionBaseClassGenerator.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import software.amazon.smithy.codegen.core.Symbol
1010
import software.amazon.smithy.kotlin.codegen.KotlinSettings
1111
import software.amazon.smithy.kotlin.codegen.core.*
1212
import software.amazon.smithy.kotlin.codegen.integration.SectionId
13-
import software.amazon.smithy.kotlin.codegen.integration.SectionKey
1413
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
1514
import software.amazon.smithy.kotlin.codegen.model.namespace
1615
import software.amazon.smithy.model.knowledge.TopDownIndex
@@ -31,12 +30,10 @@ object ExceptionBaseClassGenerator {
3130
/**
3231
* Defines a section in which code can be added to the body of the base exception type.
3332
*/
34-
object ExceptionBaseClassSection : SectionId {
35-
val CodegenContext: SectionKey<CodegenContext> = SectionKey("CodegenContext")
36-
}
33+
object ExceptionBaseClassSection : SectionId
3734

3835
fun render(ctx: CodegenContext, writer: KotlinWriter) {
39-
writer.declareSection(ExceptionBaseClassSection, mapOf(ExceptionBaseClassSection.CodegenContext to ctx)) {
36+
writer.declareSection(ExceptionBaseClassSection) {
4037
ServiceExceptionBaseClassGenerator().render(ctx, writer)
4138
}
4239
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.kotlin.codegen.rendering.endpoints.discovery
6+
7+
import software.amazon.smithy.aws.traits.clientendpointdiscovery.ClientEndpointDiscoveryTrait
8+
import software.amazon.smithy.codegen.core.Symbol
9+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
10+
import software.amazon.smithy.kotlin.codegen.core.*
11+
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
12+
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
13+
import software.amazon.smithy.kotlin.codegen.model.expectShape
14+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
15+
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.SdkEndpointBuiltinIntegration
16+
import software.amazon.smithy.model.shapes.OperationShape
17+
import software.amazon.smithy.model.shapes.ServiceShape
18+
19+
class DefaultEndpointDiscovererGenerator(private val ctx: CodegenContext, private val delegator: KotlinDelegator) {
20+
private val symbol = symbolFor(ctx.settings)
21+
private val service = ctx.model.expectShape<ServiceShape>(ctx.settings.service)
22+
private val clientSymbol = ctx.symbolProvider.toSymbol(service)
23+
private val operationName = run {
24+
val epDiscoveryTrait = service.expectTrait<ClientEndpointDiscoveryTrait>()
25+
val operation = ctx.model.expectShape<OperationShape>(epDiscoveryTrait.operation)
26+
operation.defaultName()
27+
}
28+
29+
companion object {
30+
fun symbolFor(settings: KotlinSettings): Symbol = buildSymbol {
31+
val clientName = clientName(settings.sdkId)
32+
name = "Default${clientName}EndpointDiscoverer"
33+
namespace = "${settings.pkg.name}.endpoints"
34+
}
35+
}
36+
37+
fun render() {
38+
delegator.applyFileWriter(symbol) {
39+
val service = clientName(ctx.settings.sdkId)
40+
dokka(
41+
"""
42+
A class which looks up specific endpoints for $service calls via the `$operationName` API. These
43+
unique endpoints are cached as appropriate to avoid unnecessary latency in subsequent calls.
44+
@param cache An [ExpiringKeyedCache] implementation used to cache discovered hosts
45+
""".trimIndent(),
46+
)
47+
48+
withBlock(
49+
"#1L class #2T(#1L val cache: #3T<DiscoveryParams, #4T> = #5T(10.#6T)) : #7T {",
50+
"}",
51+
ctx.settings.api.visibility,
52+
symbol,
53+
RuntimeTypes.Core.Collections.ExpiringKeyedCache,
54+
RuntimeTypes.Core.Net.Host,
55+
RuntimeTypes.Core.Collections.PeriodicSweepCache,
56+
KotlinTypes.Time.minutes,
57+
EndpointDiscovererInterfaceGenerator.symbolFor(ctx.settings),
58+
) {
59+
renderAsEndpointResolver()
60+
write("")
61+
renderInvalidate()
62+
}
63+
}
64+
}
65+
66+
private fun KotlinWriter.renderAsEndpointResolver() {
67+
withBlock(
68+
"override fun asEndpointResolver(client: #1T, delegate: #2T): #2T = #2T { request ->",
69+
"}",
70+
clientSymbol,
71+
RuntimeTypes.HttpClient.Operation.EndpointResolver,
72+
) {
73+
withBlock("if (client.config.#L == null) {", "}", SdkEndpointBuiltinIntegration.EndpointUrlProp.propertyName) {
74+
write("val identity = request.identity")
75+
write(
76+
"""require(identity is #T) { "Endpoint discovery requires AWS credentials" }""",
77+
RuntimeTypes.Auth.Credentials.AwsCredentials.Credentials,
78+
)
79+
write("")
80+
write("val cacheKey = DiscoveryParams(client.config.region, identity.accessKeyId)")
81+
write("request.context[DiscoveryParamsKey] = cacheKey")
82+
write("val discoveredHost = cache.get(cacheKey) { discoverHost(client) }")
83+
write("")
84+
write("val originalEndpoint = delegate.resolve(request)")
85+
withBlock("#T(", ")", RuntimeTypes.SmithyClient.Endpoints.Endpoint) {
86+
write("originalEndpoint.uri.copy { host = discoveredHost },")
87+
write("originalEndpoint.headers,")
88+
write("originalEndpoint.attributes,")
89+
}
90+
91+
// If user manually specifies endpointUrl, skip endpoint discovery
92+
closeAndOpenBlock("} else {")
93+
write("delegate.resolve(request)")
94+
}
95+
}
96+
}
97+
98+
private fun KotlinWriter.renderInvalidate() {
99+
withBlock("override public suspend fun invalidate(context: #T) {", "}", RuntimeTypes.Core.ExecutionContext) {
100+
write("context.getOrNull(DiscoveryParamsKey)?.let { cache.invalidate(it) }")
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)