Skip to content

Commit

Permalink
feat: add retry and http client plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFossAWS committed Mar 1, 2024
1 parent c6430c4 commit d553ad3
Show file tree
Hide file tree
Showing 16 changed files with 315 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration {
var httpClientEngine: HTTPClient { get set }

/// Configuration for the HTTP client.
var httpClientConfiguration: HttpClientConfiguration { get }
var httpClientConfiguration: HttpClientConfiguration { get set }

/// List of auth schemes to use for client calls.
///
Expand Down
11 changes: 10 additions & 1 deletion Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ public class DefaultClientPlugin: Plugin {
public init() {}
public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultClientConfiguration {
// Populate default values for configuration here if they are missing
config.retryStrategyOptions =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.defaultRetryStrategyOptions
}

if var config = clientConfiguration as? DefaultHttpClientConfiguration {
var httpClientConfiguration =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.defaultHttpClientConfiguration
config.httpClientConfiguration = httpClientConfiguration
config.httpClientEngine =
DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.makeClient(httpClientConfiguration: httpClientConfiguration)
}
}
}
33 changes: 33 additions & 0 deletions Sources/ClientRuntime/Plugins/HttpClientPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

public class DefaultHttpClientPlugin: Plugin {

var httpClientConfiguration: HttpClientConfiguration

var httpClient: HTTPClient

public init(httpClient: HTTPClient, httpClientConfiguration: HttpClientConfiguration) {
self.httpClient = httpClient
self.httpClientConfiguration = httpClientConfiguration
}

public convenience init(httpClientConfiguration: HttpClientConfiguration) {
self.init(
httpClient: DefaultSDKRuntimeConfiguration<DefaultRetryStrategy, DefaultRetryErrorInfoProvider>
.makeClient(httpClientConfiguration: httpClientConfiguration),
httpClientConfiguration: httpClientConfiguration
)
}

public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultHttpClientConfiguration {
config.httpClientConfiguration = self.httpClientConfiguration
config.httpClientEngine = self.httpClient
}
}
}
21 changes: 21 additions & 0 deletions Sources/ClientRuntime/Plugins/RetryPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

public class RetryPlugin: Plugin {

private var retryStrategyOptions: RetryStrategyOptions

public init(retryStrategyOptions: RetryStrategyOptions) {
self.retryStrategyOptions = retryStrategyOptions
}

public func configureClient(clientConfiguration: ClientConfiguration) {
if var config = clientConfiguration as? DefaultClientConfiguration {
config.retryStrategyOptions = self.retryStrategyOptions
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ object ClientRuntimeTypes {
}

object Auth {
val AuthSchemes = runtimeSymbolWithoutNamespace("[ClientRuntime.AuthScheme]?")
val AuthSchemeResolver = runtimeSymbolWithoutNamespace("ClientRuntime.AuthSchemeResolver")
val AuthSchemes = runtimeSymbolWithoutNamespace("[ClientRuntime.AuthScheme]")
val AuthSchemeResolver = runtimeSymbol("AuthSchemeResolver")
val AuthSchemeResolverParams = runtimeSymbol("AuthSchemeResolverParameters")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package software.amazon.smithy.swift.codegen.config

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.swift.codegen.Dependency
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.model.buildSymbol

/**
Expand All @@ -18,7 +19,7 @@ interface ClientConfiguration {
*/
val swiftProtocolName: Symbol?

val properties: Set<ConfigProperty>
fun getProperties(ctx: ProtocolGenerator.GenerationContext): Set<ConfigProperty>

companion object {
fun runtimeSymbol(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
package software.amazon.smithy.swift.codegen.config

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.swift.codegen.model.isOptional

data class ConfigProperty(
val name: String,
val type: Symbol,
Expand All @@ -19,14 +21,8 @@ data class ConfigProperty(
isAsync: Boolean = false
) : this(name, type, DefaultProvider(default, isThrowable, isAsync))

val isOptional: Boolean = type.name.endsWith('?')

fun toOptionalType(): String {
return if (isOptional) type.name else "${type.name}?"
}

init {
if (!this.isOptional && default == null)
if (!type.isOptional() && default == null)
throw RuntimeException("Non-optional client config property must have a default value")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,31 @@ import software.amazon.smithy.swift.codegen.ClientRuntimeTypes
import software.amazon.smithy.swift.codegen.SwiftDependency
import software.amazon.smithy.swift.codegen.SwiftTypes
import software.amazon.smithy.swift.codegen.config.ClientConfiguration.Companion.runtimeSymbol
import software.amazon.smithy.swift.codegen.model.toNullable
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.model.toOptional

class DefaultClientConfiguration : ClientConfiguration {
override val swiftProtocolName: Symbol
get() = runtimeSymbol("DefaultClientConfiguration", SwiftDependency.CLIENT_RUNTIME)

override val properties: Set<ConfigProperty>
get() = setOf(
ConfigProperty("logger", ClientRuntimeTypes.Core.Logger, "AWSClientConfigDefaultsProvider.logger(clientName)"),
ConfigProperty("retryStrategyOptions", ClientRuntimeTypes.Core.RetryStrategyOptions, "AWSClientConfigDefaultsProvider.retryStrategyOptions()", true),
ConfigProperty("clientLogMode", ClientRuntimeTypes.Core.ClientLogMode, "AWSClientConfigDefaultsProvider.clientLogMode"),
ConfigProperty("endpoint", SwiftTypes.String.toNullable()),
ConfigProperty("idempotencyTokenGenerator", ClientRuntimeTypes.Core.IdempotencyTokenGenerator, "AWSClientConfigDefaultsProvider.idempotencyTokenGenerator"),
)
override fun getProperties(ctx: ProtocolGenerator.GenerationContext): Set<ConfigProperty> = setOf(
ConfigProperty("logger", ClientRuntimeTypes.Core.Logger, "AWSClientConfigDefaultsProvider.logger(clientName)"),
ConfigProperty(
"retryStrategyOptions",
ClientRuntimeTypes.Core.RetryStrategyOptions,
"AWSClientConfigDefaultsProvider.retryStrategyOptions()",
true
),
ConfigProperty(
"clientLogMode",
ClientRuntimeTypes.Core.ClientLogMode,
"AWSClientConfigDefaultsProvider.clientLogMode"
),
ConfigProperty("endpoint", SwiftTypes.String.toOptional()),
ConfigProperty(
"idempotencyTokenGenerator",
ClientRuntimeTypes.Core.IdempotencyTokenGenerator,
"AWSClientConfigDefaultsProvider.idempotencyTokenGenerator"
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,25 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.swift.codegen.ClientRuntimeTypes
import software.amazon.smithy.swift.codegen.SwiftDependency
import software.amazon.smithy.swift.codegen.config.ClientConfiguration.Companion.runtimeSymbol
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
import software.amazon.smithy.swift.codegen.model.toOptional

class DefaultHttpClientConfiguration : ClientConfiguration {
override val swiftProtocolName: Symbol
get() = runtimeSymbol("DefaultHttpClientConfiguration", SwiftDependency.CLIENT_RUNTIME)

override val properties: Set<ConfigProperty>
get() = setOf(
ConfigProperty("httpClientEngine", ClientRuntimeTypes.Http.HttpClient, "AWSClientConfigDefaultsProvider.httpClientEngine"),
ConfigProperty("httpClientConfiguration", ClientRuntimeTypes.Http.HttpClientConfiguration, "AWSClientConfigDefaultsProvider.httpClientConfiguration"),
ConfigProperty("authSchemes", ClientRuntimeTypes.Auth.AuthSchemes, ""),
ConfigProperty("authSchemeResolver", ClientRuntimeTypes.Auth.AuthSchemeResolver, "")
)
override fun getProperties(ctx: ProtocolGenerator.GenerationContext): Set<ConfigProperty> = setOf(
ConfigProperty(
"httpClientEngine",
ClientRuntimeTypes.Http.HttpClient,
"AWSClientConfigDefaultsProvider.httpClientEngine"
),
ConfigProperty(
"httpClientConfiguration",
ClientRuntimeTypes.Http.HttpClientConfiguration,
"AWSClientConfigDefaultsProvider.httpClientConfiguration"
),
ConfigProperty("authSchemes", ClientRuntimeTypes.Auth.AuthSchemes.toOptional()),
ConfigProperty("authSchemeResolver", ClientRuntimeTypes.Auth.AuthSchemeResolver.toOptional())
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ package software.amazon.smithy.swift.codegen.integration
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.swift.codegen.ClientRuntimeTypes
import software.amazon.smithy.swift.codegen.SwiftWriter
import software.amazon.smithy.swift.codegen.config.ConfigProperty
import software.amazon.smithy.swift.codegen.integration.plugins.DefaultClientPlugin
import software.amazon.smithy.swift.codegen.model.renderSwiftType
import software.amazon.smithy.swift.codegen.model.toOptional
import software.amazon.smithy.swift.codegen.utils.toUpperCamelCase

open class HttpProtocolServiceClient(
Expand Down Expand Up @@ -42,6 +45,7 @@ open class HttpProtocolServiceClient(
writer.write("")
renderClientExtension(serviceSymbol)
renderLogHandlerFactory(serviceSymbol)
renderServiceSpecificPlugins(serviceSymbol)
}

open fun renderInitFunction(properties: List<ClientProperty>) {
Expand All @@ -66,7 +70,7 @@ open class HttpProtocolServiceClient(

open fun renderConvenienceInitFunctions(serviceSymbol: Symbol) {
writer.openBlock("public convenience required init() throws {", "}") {
writer.write("let config = try \$L(\"\$L\", \"\$L\")", serviceConfig.typeName, serviceName, serviceSymbol.name)
writer.write("let config = try \$L()", serviceConfig.typeName)
writer.write("self.init(config: config)")
}
writer.write("")
Expand All @@ -81,19 +85,13 @@ open class HttpProtocolServiceClient(
writer.openBlock("return ClientBuilder<\$L>(defaultPlugins: [", "])", serviceSymbol.name) {

val defaultPlugins: MutableList<Plugin> = mutableListOf(DefaultClientPlugin())
val customPlugins: MutableList<Plugin> = mutableListOf()

ctx.integrations
.flatMap { it.plugins() }
.onEach {
if (it.isDefault) {
defaultPlugins.add(it)
} else {
customPlugins.add(it)
}
}
.flatMap { it.plugins(serviceConfig) }
.filter { it.isDefault }
.onEach { defaultPlugins.add(it) }

val pluginsIterator = (defaultPlugins + customPlugins).iterator()
val pluginsIterator = defaultPlugins.iterator()

while (pluginsIterator.hasNext()) {
pluginsIterator.next().customInitialization(writer)
Expand All @@ -110,11 +108,39 @@ open class HttpProtocolServiceClient(
}

open fun renderClientConfig(serviceSymbol: Symbol) {
writer.write(
"public typealias \$LConfiguration = \$N",
val clientConfigurationProtocols =
ctx.integrations
.flatMap { it.clientConfigurations(ctx) }
.mapNotNull { it.swiftProtocolName?.name }
.joinToString(" & ")

writer.openBlock(
"public class \$LConfiguration: \$L {", "}",
serviceConfig.clientName.toUpperCamelCase(),
ClientRuntimeTypes.Core.DefaultSDKRuntimeConfiguration
)
clientConfigurationProtocols
) {
val properties: List<ConfigProperty> = ctx.integrations
.flatMap { it.clientConfigurations(ctx).flatMap { it.getProperties(ctx) } }
.let { overrideConfigProperties(it) }

renderConfigClassVariables(properties)

renderConfigInitializer(properties)

renderSynchronousConfigInitializer(properties)

renderAsynchronousConfigInitializer(properties)

renderCustomConfigInitializer(properties)
}
writer.write("")
}

open fun renderCustomConfigInitializer(properties: List<ConfigProperty>) {
}

open fun overrideConfigProperties(properties: List<ConfigProperty>): List<ConfigProperty> {
return properties
}

private fun renderLogHandlerFactory(serviceSymbol: Symbol) {
Expand All @@ -139,4 +165,74 @@ open class HttpProtocolServiceClient(
}
writer.write("")
}

/**
* Declare class variables in client configuration class
*/
private fun renderConfigClassVariables(properties: List<ConfigProperty>) {
properties
.forEach {
writer.write("public var \$L: \$L", it.name, it.type.renderSwiftType())
writer.write("")
}
writer.write("")
}

private fun renderConfigInitializer(properties: List<ConfigProperty>) {
writer.openBlock(
"private init(\$L) {", "}",
properties.joinToString(", ") { "_ ${it.name}: ${it.type.renderSwiftType()}" }
) {
properties.forEach {
writer.write("self.\$L = \$L", it.name, it.name)
}
}
writer.write("")
}

private fun renderSynchronousConfigInitializer(properties: List<ConfigProperty>) {
writer.openBlock(
"public convenience init(\$L) throws {", "}",
properties.joinToString(", ") { "${it.name}: ${it.type.toOptional().renderSwiftType()} = nil" }
) {
writer.write(
"self.init(\$L)",
properties.joinToString(", ") {
if (it.default?.isAsync == true) {
it.name
} else {
it.default?.render(it.name) ?: it.name
}
}
)
}
writer.write("")
}

private fun renderAsynchronousConfigInitializer(properties: List<ConfigProperty>) {
if (properties.none { it.default?.isAsync == true }) return

writer.openBlock(
"public convenience init(\$L) async throws {", "}",
properties.joinToString(", ") { "${it.name}: ${it.type.toOptional().renderSwiftType()} = nil" }
) {
writer.write(
"self.init(\$L)",
properties.joinToString(", ") {
if (it.default?.isAsync == true) {
it.default.render()
} else {
it.default?.render(it.name) ?: it.name
}
}
)
}
writer.write("")
}

private fun renderServiceSpecificPlugins(serviceSymbol: Symbol) {
ctx.integrations
.flatMap { it.plugins(serviceConfig) }
.onEach { it.render(writer) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import software.amazon.smithy.swift.codegen.SwiftWriter
interface Plugin {
val className: Symbol
val isDefault: Boolean
get() = false

fun customInitialization(writer: SwiftWriter) {
writer.writeInline("\$L()", className)
}

fun render(writer: SwiftWriter) {
}
}
Loading

0 comments on commit d553ad3

Please sign in to comment.