Skip to content

Commit 2e88034

Browse files
authored
feat: add configurable retry policy to clients (#797)
1 parent 4f5fa48 commit 2e88034

File tree

7 files changed

+159
-5
lines changed

7 files changed

+159
-5
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "fbd99a2c-dd01-4b42-8c9a-55d2bf8e42fe",
3+
"type": "feature",
4+
"description": "Add configuration for retry policy on clients"
5+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ object RuntimeTypes {
137137
val OutputAcceptor = symbol("OutputAcceptor")
138138
val RetryDirective = symbol("RetryDirective")
139139
val RetryErrorType = symbol("RetryErrorType")
140+
val RetryPolicy = symbol("RetryPolicy")
140141
val StandardRetryPolicy = symbol("StandardRetryPolicy")
141142
val SuccessAcceptor = symbol("SuccessAcceptor")
142143
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ServiceClientConfigGenerator(
4949
add(RuntimeConfigProperty.IdempotencyTokenProvider)
5050
}
5151

52+
add(RuntimeConfigProperty.RetryPolicy)
5253
add(RuntimeConfigProperty.RetryStrategy)
5354
add(RuntimeConfigProperty.Tracer)
5455

@@ -98,16 +99,20 @@ class ServiceClientConfigGenerator(
9899
fun render(ctx: CodegenContext, writer: KotlinWriter) = render(ctx, emptyList(), writer)
99100

100101
override fun render(ctx: CodegenContext, props: Collection<ConfigProperty>, writer: KotlinWriter) {
101-
val allProps = props.toMutableList()
102+
val allPropsByName = props.byName().toMutableMap()
102103
if (detectDefaultProps) {
104+
val defaultPropsByName = detectDefaultProps(ctx, serviceShape).byName()
103105
// register auto detected properties
104-
allProps.addAll(detectDefaultProps(ctx, serviceShape))
106+
allPropsByName.putAll(defaultPropsByName)
105107
}
106108

107109
// register properties from integrations
108-
val integrationProps = ctx.integrations.flatMap { it.additionalServiceConfigProps(ctx) }
109-
allProps.addAll(integrationProps)
110-
super.render(ctx, allProps, writer)
110+
ctx
111+
.integrations
112+
.map { it.additionalServiceConfigProps(ctx).byName() }
113+
.forEach(allPropsByName::putAll)
114+
115+
super.render(ctx, allPropsByName.values.toList(), writer)
111116
}
112117

113118
override fun renderBuilderBuildMethod(writer: KotlinWriter) {
@@ -116,3 +121,5 @@ class ServiceClientConfigGenerator(
116121
writer.write("override fun build(): #configClass.name:L = #configClass.name:L(this)")
117122
}
118123
}
124+
125+
private fun Collection<ConfigProperty>.byName(): Map<String, ConfigProperty> = associateBy(ConfigProperty::propertyName)

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ class ConfigProperty private constructor(builder: Builder) {
169169
builtInProperty(name, builtInSymbol("String", defaultValue), documentation, baseClass)
170170
}
171171

172+
fun toBuilder(): Builder = Builder().apply {
173+
symbol = this@ConfigProperty.symbol
174+
builderSymbol = this@ConfigProperty.builderSymbol.takeUnless { it == this@ConfigProperty.symbol }
175+
toBuilderExpression = this@ConfigProperty.toBuilderExpression
176+
name = this@ConfigProperty.propertyName
177+
documentation = this@ConfigProperty.documentation
178+
baseClass = this@ConfigProperty.baseClass
179+
builderBaseClass = this@ConfigProperty.builderBaseClass
180+
propertyType = this@ConfigProperty.propertyType
181+
additionalImports = this@ConfigProperty.additionalImports
182+
order = this@ConfigProperty.order
183+
}
184+
172185
class Builder {
173186
var symbol: Symbol? = null
174187
var builderSymbol: Symbol? = null

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package software.amazon.smithy.kotlin.codegen.rendering.util
77

88
import software.amazon.smithy.codegen.core.CodegenException
9+
import software.amazon.smithy.codegen.core.SymbolReference
910
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
1011
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1112
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
@@ -52,6 +53,26 @@ object RuntimeConfigProperty {
5253
""".trimIndent()
5354
}
5455

56+
val RetryPolicy = ConfigProperty {
57+
symbol = buildSymbol {
58+
name = "RetryPolicy<Any?>"
59+
reference(RuntimeTypes.Core.Retries.Policy.RetryPolicy, SymbolReference.ContextOption.USE)
60+
}
61+
name = "retryPolicy"
62+
documentation = """
63+
The policy to use for evaluating operation results and determining whether/how to retry.
64+
""".trimIndent()
65+
66+
propertyType = ConfigPropertyType.RequiredWithDefault("StandardRetryPolicy.Default")
67+
baseClass = RuntimeTypes.SmithyClient.SdkClientConfig
68+
builderBaseClass = buildSymbol {
69+
name = "${baseClass!!.name}.Builder<Config>"
70+
namespace = baseClass!!.namespace
71+
}
72+
73+
additionalImports = listOf(RuntimeTypes.Core.Retries.Policy.StandardRetryPolicy)
74+
}
75+
5576
val RetryStrategy = ConfigProperty {
5677
symbol = RuntimeTypes.Core.Retries.RetryStrategy
5778
name = "retryStrategy"

codegen/smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ServiceClientConfigGeneratorTest.kt

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import software.amazon.smithy.kotlin.codegen.loadModelFromResource
1515
import software.amazon.smithy.kotlin.codegen.model.*
1616
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigProperty
1717
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigPropertyType
18+
import software.amazon.smithy.kotlin.codegen.rendering.util.RuntimeConfigProperty
1819
import software.amazon.smithy.kotlin.codegen.test.*
1920
import software.amazon.smithy.model.Model
2021
import software.amazon.smithy.model.shapes.ServiceShape
@@ -50,6 +51,7 @@ public class Config private constructor(builder: Builder) : HttpClientConfig, Id
5051
public val endpointProvider: EndpointProvider = requireNotNull(builder.endpointProvider) { "endpointProvider is a required configuration property" }
5152
override val idempotencyTokenProvider: IdempotencyTokenProvider = builder.idempotencyTokenProvider ?: IdempotencyTokenProvider.Default
5253
override val interceptors: kotlin.collections.List<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = builder.interceptors
54+
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
5355
override val retryStrategy: RetryStrategy = builder.retryStrategy ?: StandardRetryStrategy()
5456
override val sdkLogMode: SdkLogMode = builder.sdkLogMode
5557
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, "${TestModelDefault.SERVICE_NAME}")
@@ -84,6 +86,11 @@ public class Config private constructor(builder: Builder) : HttpClientConfig, Id
8486
*/
8587
override var interceptors: kotlin.collections.MutableList<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = kotlin.collections.mutableListOf()
8688
89+
/**
90+
* The policy to use for evaluating operation results and determining whether/how to retry.
91+
*/
92+
override var retryPolicy: RetryPolicy<Any?>? = null
93+
8794
/**
8895
* The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the
8996
* strategy.
@@ -202,6 +209,95 @@ public class Config private constructor(builder: Builder) {
202209
contents.shouldContain(expectedProps)
203210
}
204211

212+
@Test
213+
fun `it overrides props by name`() {
214+
val model = getModel()
215+
val serviceShape = model.expectShape<ServiceShape>(TestModelDefault.SERVICE_SHAPE_ID)
216+
217+
val testCtx = model.newTestContext()
218+
val writer = createWriter()
219+
val customIntegration = object : KotlinIntegration {
220+
private val overriddenLogMode = RuntimeConfigProperty.SdkLogMode.toBuilder().apply {
221+
symbol = symbol!!.toBuilder().apply {
222+
defaultValue("SdkLogMode.LogRequest") // replaces SdkLogMode.Default
223+
}.build()
224+
}.build()
225+
226+
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(
227+
ConfigProperty.Int("customProp"),
228+
overriddenLogMode,
229+
)
230+
}
231+
232+
val renderingCtx = testCtx.toRenderingContext(writer, serviceShape)
233+
.copy(integrations = listOf(customIntegration))
234+
235+
ServiceClientConfigGenerator(serviceShape, detectDefaultProps = true).render(renderingCtx, renderingCtx.writer)
236+
val contents = writer.toString()
237+
238+
val expectedBuilderProps = """
239+
/**
240+
* Override the default HTTP client engine used to make SDK requests (e.g. configure proxy behavior, timeouts, concurrency, etc).
241+
* NOTE: The caller is responsible for managing the lifetime of the engine when set. The SDK
242+
* client will not close it when the client is closed.
243+
*/
244+
override var httpClientEngine: HttpClientEngine? = null
245+
246+
public var customProp: Int? = null
247+
248+
/**
249+
* The endpoint provider used to determine where to make service requests.
250+
*/
251+
public var endpointProvider: EndpointProvider? = null
252+
253+
/**
254+
* Override the default idempotency token generator. SDK clients will generate tokens for members
255+
* that represent idempotent tokens when not explicitly set by the caller using this generator.
256+
*/
257+
override var idempotencyTokenProvider: IdempotencyTokenProvider? = null
258+
259+
/**
260+
* Add an [aws.smithy.kotlin.runtime.client.Interceptor] that will have access to read and modify
261+
* the request and response objects as they are processed by the SDK.
262+
* Interceptors added using this method are executed in the order they are configured and are always
263+
* later than any added automatically by the SDK.
264+
*/
265+
override var interceptors: kotlin.collections.MutableList<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = kotlin.collections.mutableListOf()
266+
267+
/**
268+
* The policy to use for evaluating operation results and determining whether/how to retry.
269+
*/
270+
override var retryPolicy: RetryPolicy<Any?>? = null
271+
272+
/**
273+
* The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the
274+
* strategy.
275+
*/
276+
override var retryStrategy: RetryStrategy? = null
277+
278+
/**
279+
* Configure events that will be logged. By default clients will not output
280+
* raw requests or responses. Use this setting to opt-in to additional debug logging.
281+
*
282+
* This can be used to configure logging of requests, responses, retries, etc of SDK clients.
283+
*
284+
* **NOTE**: Logging of raw requests or responses may leak sensitive information! It may also have
285+
* performance considerations when dumping the request/response body. This is primarily a tool for
286+
* debug purposes.
287+
*/
288+
override var sdkLogMode: SdkLogMode = SdkLogMode.LogRequest
289+
290+
/**
291+
* The tracer that is responsible for creating trace spans and wiring them up to a tracing backend (e.g.,
292+
* a trace probe). By default, this will create a standard tracer that uses the service name for the root
293+
* trace span and delegates to a logging trace probe (i.e.,
294+
* `DefaultTracer(LoggingTraceProbe, "<service-name>")`).
295+
*/
296+
override var tracer: Tracer? = null
297+
"""
298+
contents.shouldContain(expectedBuilderProps)
299+
}
300+
205301
@Test
206302
fun `it finds idempotency token via resources`() {
207303
val model = """

runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/SdkClientConfig.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package aws.smithy.kotlin.runtime.client
66

77
import aws.smithy.kotlin.runtime.retries.RetryStrategy
8+
import aws.smithy.kotlin.runtime.retries.policy.RetryPolicy
89
import aws.smithy.kotlin.runtime.util.Buildable
910

1011
/**
@@ -24,6 +25,11 @@ public interface SdkClientConfig {
2425
public val sdkLogMode: SdkLogMode
2526
get() = SdkLogMode.Default
2627

28+
/**
29+
* The policy to use for evaluating operation results and determining whether/how to retry.
30+
*/
31+
public val retryPolicy: RetryPolicy<Any?>
32+
2733
/**
2834
* The [RetryStrategy] the client will use to retry failed operations.
2935
*/
@@ -47,6 +53,11 @@ public interface SdkClientConfig {
4753
*/
4854
public var sdkLogMode: SdkLogMode
4955

56+
/**
57+
* The policy to use for evaluating operation results and determining whether/how to retry.
58+
*/
59+
public var retryPolicy: RetryPolicy<Any?>?
60+
5061
/**
5162
* Configure the [RetryStrategy] the client will use to retry failed operations.
5263
*/

0 commit comments

Comments
 (0)