diff --git a/Sources/ClientRuntime/Client/Client.swift b/Sources/ClientRuntime/Client/Client.swift index 424d580fe..f0441a1d1 100644 --- a/Sources/ClientRuntime/Client/Client.swift +++ b/Sources/ClientRuntime/Client/Client.swift @@ -4,7 +4,9 @@ // // SPDX-License-Identifier: Apache-2.0 // + public protocol Client { associatedtype Config: ClientConfiguration + init(config: Config) } diff --git a/Sources/ClientRuntime/Client/ClientBuilder.swift b/Sources/ClientRuntime/Client/ClientBuilder.swift index e8f007c2a..2124d5ffc 100644 --- a/Sources/ClientRuntime/Client/ClientBuilder.swift +++ b/Sources/ClientRuntime/Client/ClientBuilder.swift @@ -4,29 +4,36 @@ // // SPDX-License-Identifier: Apache-2.0 // + public class ClientBuilder { - private var plugins: [Plugin] + private struct PluginContainer: Plugin { + let plugin: any Plugin - public init(defaultPlugins: [Plugin] = []) { - self.plugins = defaultPlugins + func configureClient(clientConfiguration: inout ClientType.Config) async throws { + try await plugin.configureClient(clientConfiguration: &clientConfiguration) + } } - public func withPlugin(_ plugin: any Plugin) -> ClientBuilder { - self.plugins.append(plugin) + private var plugins = [PluginContainer]() + + public init() {} + + public func withPlugin(_ plugin: P) -> ClientBuilder where P.Config == ClientType.Config { + self.plugins.append(PluginContainer(plugin: plugin)) return self } public func build() async throws -> ClientType { - let configuration = try await resolve(plugins: self.plugins) + let configuration = try await resolve() return ClientType(config: configuration) } - func resolve(plugins: [any Plugin]) async throws -> ClientType.Config { - let clientConfiguration = try await ClientType.Config() + private func resolve() async throws -> ClientType.Config { + var config = try await ClientType.Config() for plugin in plugins { - try await plugin.configureClient(clientConfiguration: clientConfiguration) + try await plugin.configureClient(clientConfiguration: &config) } - return clientConfiguration + return config } } diff --git a/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift b/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift index 399878c9c..de5d9e981 100644 --- a/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift @@ -34,7 +34,7 @@ public protocol DefaultClientConfiguration: ClientConfiguration { /// Adds an `InterceptorProvider` that will be used to provide interceptors for all operations. /// /// - Parameter provider: The `InterceptorProvider` to add. - func addInterceptorProvider(_ provider: InterceptorProvider) + mutating func addInterceptorProvider(_ provider: InterceptorProvider) /// TODO(plugins): Add Checksum, etc. } diff --git a/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift b/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift index 5b390a4e5..f53554917 100644 --- a/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift @@ -39,5 +39,5 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration { /// Adds a `HttpInterceptorProvider` that will be used to provide interceptors for all HTTP operations. /// /// - Parameter provider: The `HttpInterceptorProvider` to add. - func addInterceptorProvider(_ provider: HttpInterceptorProvider) + mutating func addInterceptorProvider(_ provider: HttpInterceptorProvider) } diff --git a/Sources/ClientRuntime/Plugins/AuthSchemePlugin.swift b/Sources/ClientRuntime/Plugins/AuthSchemePlugin.swift index 4b3d016ff..4540cc02d 100644 --- a/Sources/ClientRuntime/Plugins/AuthSchemePlugin.swift +++ b/Sources/ClientRuntime/Plugins/AuthSchemePlugin.swift @@ -7,7 +7,7 @@ import SmithyHTTPAuthAPI -public class AuthSchemePlugin: Plugin { +public class AuthSchemePlugin: Plugin { private var authSchemes: [AuthScheme]? @@ -21,14 +21,12 @@ public class AuthSchemePlugin: Plugin { self.authSchemes = authSchemes } - public func configureClient(clientConfiguration: ClientConfiguration) { - if var config = clientConfiguration as? DefaultHttpClientConfiguration { - if self.authSchemes != nil { - config.authSchemes = self.authSchemes! - } - if self.authSchemeResolver != nil { - config.authSchemeResolver = self.authSchemeResolver! - } + public func configureClient(clientConfiguration: inout Config) { + if let authSchemes { + clientConfiguration.authSchemes = authSchemes + } + if let authSchemeResolver { + clientConfiguration.authSchemeResolver = authSchemeResolver } } } diff --git a/Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift b/Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift index df080b1c6..5b35940de 100644 --- a/Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift +++ b/Sources/ClientRuntime/Plugins/DefaultClientPlugin.swift @@ -7,23 +7,17 @@ import struct SmithyRetries.DefaultRetryStrategy -public class DefaultClientPlugin: Plugin { +public class DefaultClientPlugin: Plugin { + typealias DefaultRuntimeConfig = DefaultSDKRuntimeConfiguration + public init() {} - public func configureClient(clientConfiguration: ClientConfiguration) { - if var config = clientConfiguration as? DefaultClientConfiguration { - config.retryStrategyOptions = - DefaultSDKRuntimeConfiguration - .defaultRetryStrategyOptions - } - if var config = clientConfiguration as? DefaultHttpClientConfiguration { - let httpClientConfiguration = - DefaultSDKRuntimeConfiguration - .defaultHttpClientConfiguration - config.httpClientConfiguration = httpClientConfiguration - config.httpClientEngine = - DefaultSDKRuntimeConfiguration - .makeClient(httpClientConfiguration: httpClientConfiguration) - } + public func configureClient(clientConfiguration: inout Config) async throws { + clientConfiguration.retryStrategyOptions = DefaultRuntimeConfig.defaultRetryStrategyOptions + let httpClientConfiguration = DefaultRuntimeConfig .defaultHttpClientConfiguration + clientConfiguration.httpClientConfiguration = httpClientConfiguration + clientConfiguration.httpClientEngine = DefaultRuntimeConfig.makeClient( + httpClientConfiguration: httpClientConfiguration + ) } } diff --git a/Sources/ClientRuntime/Plugins/HttpClientPlugin.swift b/Sources/ClientRuntime/Plugins/HttpClientPlugin.swift index c7aea9893..620eba4af 100644 --- a/Sources/ClientRuntime/Plugins/HttpClientPlugin.swift +++ b/Sources/ClientRuntime/Plugins/HttpClientPlugin.swift @@ -8,10 +8,8 @@ import protocol SmithyHTTPAPI.HTTPClient import struct SmithyRetries.DefaultRetryStrategy -public class DefaultHttpClientPlugin: Plugin { - +public class DefaultHttpClientPlugin: Plugin { var httpClientConfiguration: HttpClientConfiguration - var httpClient: HTTPClient public init(httpClient: HTTPClient, httpClientConfiguration: HttpClientConfiguration) { @@ -27,10 +25,8 @@ public class DefaultHttpClientPlugin: Plugin { ) } - public func configureClient(clientConfiguration: ClientConfiguration) { - if var config = clientConfiguration as? DefaultHttpClientConfiguration { - config.httpClientConfiguration = self.httpClientConfiguration - config.httpClientEngine = self.httpClient - } + public func configureClient(clientConfiguration: inout Config) { + clientConfiguration.httpClientConfiguration = self.httpClientConfiguration + clientConfiguration.httpClientEngine = self.httpClient } } diff --git a/Sources/ClientRuntime/Plugins/Plugin.swift b/Sources/ClientRuntime/Plugins/Plugin.swift index 0e6482ee1..b6c3969b5 100644 --- a/Sources/ClientRuntime/Plugins/Plugin.swift +++ b/Sources/ClientRuntime/Plugins/Plugin.swift @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -public protocol Plugin { - func configureClient(clientConfiguration: ClientConfiguration) async throws +public protocol Plugin { + associatedtype Config: ClientConfiguration + + func configureClient(clientConfiguration: inout Config) async throws } diff --git a/Sources/ClientRuntime/Plugins/RetryPlugin.swift b/Sources/ClientRuntime/Plugins/RetryPlugin.swift index 1799fa77e..9b04508c7 100644 --- a/Sources/ClientRuntime/Plugins/RetryPlugin.swift +++ b/Sources/ClientRuntime/Plugins/RetryPlugin.swift @@ -7,7 +7,7 @@ import struct SmithyRetriesAPI.RetryStrategyOptions -public class RetryPlugin: Plugin { +public class RetryPlugin: Plugin { private var retryStrategyOptions: RetryStrategyOptions @@ -15,9 +15,7 @@ public class RetryPlugin: Plugin { self.retryStrategyOptions = retryStrategyOptions } - public func configureClient(clientConfiguration: ClientConfiguration) { - if var config = clientConfiguration as? DefaultClientConfiguration { - config.retryStrategyOptions = self.retryStrategyOptions - } + public func configureClient(clientConfiguration: inout Config) { + clientConfiguration.retryStrategyOptions = self.retryStrategyOptions } } diff --git a/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift b/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift index b9bd14066..ea368b257 100644 --- a/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift +++ b/Sources/ClientRuntime/Plugins/TelemetryPlugin.swift @@ -5,7 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // -public class TelemetryPlugin: Plugin { +public class TelemetryPlugin: Plugin { + private let telemetryProvider: TelemetryProvider public init(telemetryProvider: TelemetryProvider) { @@ -26,10 +27,8 @@ public class TelemetryPlugin: Plugin { ) } - public func configureClient(clientConfiguration: ClientConfiguration) { - if var config = clientConfiguration as? DefaultClientConfiguration { - config.telemetryProvider = self.telemetryProvider - } + public func configureClient(clientConfiguration: inout Config) { + clientConfiguration.telemetryProvider = self.telemetryProvider } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultClientConfiguration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultClientConfiguration.kt index a8b91917b..f756daa9f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultClientConfiguration.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultClientConfiguration.kt @@ -58,6 +58,7 @@ class DefaultClientConfiguration : ClientConfiguration { listOf( FunctionParameter.NoLabel("provider", ClientRuntimeTypes.Core.InterceptorProvider), ), + isMutating = true, ), ) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultHttpClientConfiguration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultHttpClientConfiguration.kt index cb2746867..a88e1c776 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultHttpClientConfiguration.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/DefaultHttpClientConfiguration.kt @@ -74,6 +74,7 @@ class DefaultHttpClientConfiguration : ClientConfiguration { listOf( FunctionParameter.NoLabel("provider", ClientRuntimeTypes.Core.HttpInterceptorProvider), ), + isMutating = true, ), ) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolServiceClient.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolServiceClient.kt index 93e4ebe48..f31ea8d81 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolServiceClient.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolServiceClient.kt @@ -75,30 +75,25 @@ open class HttpProtocolServiceClient( ClientRuntimeTypes.Core.ClientBuilder, serviceSymbol.name, ) { - writer.openBlock( - "return \$N<\$L>(defaultPlugins: [", - "])", + writer.write( + "return \$N<\$L>()", ClientRuntimeTypes.Core.ClientBuilder, serviceSymbol.name, - ) { - val defaultPlugins: MutableList = mutableListOf(DefaultClientPlugin()) + ) + writer.indent() + val defaultPlugins: MutableList = mutableListOf(DefaultClientPlugin()) - ctx.integrations - .flatMap { it.plugins(serviceConfig) } - .filter { it.isDefault } - .onEach { defaultPlugins.add(it) } + ctx.integrations + .flatMap { it.plugins(serviceConfig) } + .filter { it.isDefault } + .onEach { defaultPlugins.add(it) } - val pluginsIterator = defaultPlugins.iterator() + val pluginsIterator = defaultPlugins.iterator() - while (pluginsIterator.hasNext()) { - pluginsIterator.next().customInitialization(writer) - if (pluginsIterator.hasNext()) { - writer.write(",") - } - } - - writer.unwrite(",\n").write("") + while (pluginsIterator.hasNext()) { + writer.write(".withPlugin(\$L)", pluginsIterator.next().customInitialization(writer)) } + writer.dedent() } } writer.write("") @@ -113,7 +108,7 @@ open class HttpProtocolServiceClient( .joinToString(" & ") writer.openBlock( - "public class \$LConfiguration: \$L {", + "public struct \$LConfiguration: \$L {", "}", serviceConfig.clientName.toUpperCamelCase(), clientConfigurationProtocols, @@ -156,7 +151,7 @@ open class HttpProtocolServiceClient( open fun overrideConfigProperties(properties: List): List = properties private fun renderEmptyAsynchronousConfigInitializer(properties: List) { - writer.openBlock("public convenience required init() async throws {", "}") { + writer.openBlock("public init() async throws {", "}") { writer.openBlock("try await self.init(", ")") { properties.forEach { property -> writer.write("\$L: nil,", property.name) @@ -219,7 +214,7 @@ open class HttpProtocolServiceClient( } private fun renderSynchronousConfigInitializer(properties: List) { - writer.openBlock("public convenience init(", ") throws {") { + writer.openBlock("public init(", ") throws {") { properties.forEach { property -> writer.write("\$L: \$N = nil,", property.name, property.type.toOptional()) } @@ -246,7 +241,7 @@ open class HttpProtocolServiceClient( private fun renderAsynchronousConfigInitializer(properties: List) { if (properties.none { it.default?.isAsync == true }) return - writer.openBlock("public convenience init(", ") async throws {") { + writer.openBlock("public init(", ") async throws {") { properties.forEach { property -> writer.write("\$L: \$L = nil,", property.name, property.type.toOptional().renderSwiftType(writer)) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/Plugin.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/Plugin.kt index 74ad6e5bb..c7e073ab8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/Plugin.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/Plugin.kt @@ -8,9 +8,7 @@ interface Plugin { val isDefault: Boolean get() = false - fun customInitialization(writer: SwiftWriter) { - writer.writeInline("\$N()", className) - } + fun customInitialization(writer: SwiftWriter): String = writer.format("\$N()", className) fun render( ctx: ProtocolGenerator.GenerationContext, diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/Function.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/Function.kt index d3b3ee3a2..ba2f86827 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/Function.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/Function.kt @@ -20,18 +20,25 @@ data class Function( val accessModifier: AccessModifier = AccessModifier.Public, val isAsync: Boolean = false, val isThrowing: Boolean = false, + val isMutating: Boolean = false, ) { /** * Render this function using the given writer. */ fun render(writer: SwiftWriter) { + val renderedMutating = "mutating ".takeIf { isMutating } ?: "" val renderedParameters = parameters.joinToString(", ") { it.rendered(writer) } val renderedAsync = if (isAsync) "async " else "" val renderedThrows = if (isThrowing) "throws " else "" val renderedReturnType = returnType?.let { writer.format("-> \$N ", it) } ?: "" writer.openBlock( - "${accessModifier.renderedRightPad()}func $name($renderedParameters) $renderedAsync$renderedThrows$renderedReturnType{", + "\$L\$Lfunc \$L(\$L) \$L$renderedThrows$renderedReturnType{", "}", + accessModifier.renderedRightPad(), + renderedMutating, + name, + renderedParameters, + renderedAsync, ) { renderBody.accept(writer) } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolClientGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolClientGeneratorTests.kt index 2ace6c4f3..621f66e31 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolClientGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolClientGeneratorTests.kt @@ -38,7 +38,7 @@ public class RestJsonProtocolClient: ClientRuntime.Client { extension RestJsonProtocolClient { - public class RestJsonProtocolClientConfiguration: ClientRuntime.DefaultClientConfiguration & ClientRuntime.DefaultHttpClientConfiguration { + public struct RestJsonProtocolClientConfiguration: ClientRuntime.DefaultClientConfiguration & ClientRuntime.DefaultHttpClientConfiguration { public var telemetryProvider: ClientRuntime.TelemetryProvider public var retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions public var clientLogMode: ClientRuntime.ClientLogMode @@ -80,7 +80,7 @@ extension RestJsonProtocolClient { self.httpInterceptorProviders = httpInterceptorProviders } - public convenience init( + public init( telemetryProvider: ClientRuntime.TelemetryProvider? = nil, retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions? = nil, clientLogMode: ClientRuntime.ClientLogMode? = nil, @@ -110,7 +110,7 @@ extension RestJsonProtocolClient { ) } - public convenience required init() async throws { + public init() async throws { try await self.init( telemetryProvider: nil, retryStrategyOptions: nil, @@ -131,20 +131,19 @@ extension RestJsonProtocolClient { return "" } - public func addInterceptorProvider(_ provider: ClientRuntime.InterceptorProvider) { + public mutating func addInterceptorProvider(_ provider: ClientRuntime.InterceptorProvider) { self.interceptorProviders.append(provider) } - public func addInterceptorProvider(_ provider: ClientRuntime.HttpInterceptorProvider) { + public mutating func addInterceptorProvider(_ provider: ClientRuntime.HttpInterceptorProvider) { self.httpInterceptorProviders.append(provider) } } public static func builder() -> ClientRuntime.ClientBuilder { - return ClientRuntime.ClientBuilder(defaultPlugins: [ - ClientRuntime.DefaultClientPlugin() - ]) + return ClientRuntime.ClientBuilder() + .withPlugin(ClientRuntime.DefaultClientPlugin()) } } """