diff --git a/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift b/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift index 30df0a5b2..8d47cf431 100644 --- a/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultClientConfiguration.swift @@ -31,5 +31,10 @@ public protocol DefaultClientConfiguration: ClientConfiguration { /// If none is provided, only a default logger provider will be used. var telemetryProvider: TelemetryProvider { get set } + /// Add an `InterceptorProvider` that will be used to provide interceptors for all operations. + /// + /// - Parameter provider: The `InterceptorProvider` to add. + func addInterceptorProvider(_ provider: InterceptorProvider) + /// TODO(plugins): Add Checksum, etc. } diff --git a/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift b/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift index 20344f0d1..1d5ea1a49 100644 --- a/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultHttpClientConfiguration.swift @@ -29,4 +29,9 @@ public protocol DefaultHttpClientConfiguration: ClientConfiguration { /// /// Defaults to a auth scheme resolver generated based on Smithy service model. var authSchemeResolver: AuthSchemeResolver { get set } + + /// Add an `HttpInterceptorProvider` that will be used to provide interceptors for all HTTP operations. + /// + /// - Parameter provider: The `HttpInterceptorProvider` to add. + func addInterceptorProvider(_ provider: HttpInterceptorProvider) } diff --git a/Sources/ClientRuntime/Idempotency/IdempotencyTokenMiddleware.swift b/Sources/ClientRuntime/Idempotency/IdempotencyTokenMiddleware.swift index 693d31cdb..ad892b41c 100644 --- a/Sources/ClientRuntime/Idempotency/IdempotencyTokenMiddleware.swift +++ b/Sources/ClientRuntime/Idempotency/IdempotencyTokenMiddleware.swift @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 // -import protocol Smithy.HasAttributes import class Smithy.Context public struct IdempotencyTokenMiddleware: Middleware { @@ -42,7 +41,7 @@ extension IdempotencyTokenMiddleware: HttpInterceptor { public typealias InputType = OperationStackInput public typealias OutputType = OperationStackOutput - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { let withToken = addToken(input: context.getInput(), attributes: context.getAttributes()) context.updateInput(updated: withToken) } diff --git a/Sources/ClientRuntime/Interceptor/AnyInterceptor.swift b/Sources/ClientRuntime/Interceptor/AnyInterceptor.swift index 770dbb4a5..5a7393f6f 100644 --- a/Sources/ClientRuntime/Interceptor/AnyInterceptor.swift +++ b/Sources/ClientRuntime/Interceptor/AnyInterceptor.swift @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// Type-erased, concrete interceptor. /// @@ -22,11 +22,10 @@ internal struct AnyInterceptor< InputType, OutputType, RequestType: RequestMessage, - ResponseType: ResponseMessage, - AttributesType: HasAttributes + ResponseType: ResponseMessage > { internal typealias InterceptorContextType = DefaultInterceptorContext< - InputType, OutputType, RequestType, ResponseType, AttributesType + InputType, OutputType, RequestType, ResponseType > internal typealias InterceptorFn = (InterceptorContextType) async throws -> Void @@ -55,8 +54,7 @@ internal struct AnyInterceptor< I.InputType == InputType, I.OutputType == OutputType, I.RequestType == RequestType, - I.ResponseType == ResponseType, - I.AttributesType == AttributesType { + I.ResponseType == ResponseType { self.readBeforeExecution = interceptor.readBeforeExecution(context:) self.modifyBeforeSerialization = interceptor.modifyBeforeSerialization(context:) self.readBeforeSerialization = interceptor.readBeforeSerialization(context:) diff --git a/Sources/ClientRuntime/Interceptor/DefaultInterceptorContext.swift b/Sources/ClientRuntime/Interceptor/DefaultInterceptorContext.swift index 631627ad8..d24d88a9b 100644 --- a/Sources/ClientRuntime/Interceptor/DefaultInterceptorContext.swift +++ b/Sources/ClientRuntime/Interceptor/DefaultInterceptorContext.swift @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// Default implementation for all interceptor context types. /// @@ -17,16 +17,15 @@ public class DefaultInterceptorContext< InputType, OutputType, RequestType: RequestMessage, - ResponseType: ResponseMessage, - AttributesType: HasAttributes + ResponseType: ResponseMessage >: InterceptorContext { - private var attributes: AttributesType + private var attributes: Context private var input: InputType private var request: RequestType? private var response: ResponseType? private var result: Result? - public init(input: InputType, attributes: AttributesType) { + public init(input: InputType, attributes: Context) { self.input = input self.attributes = attributes } @@ -35,9 +34,13 @@ public class DefaultInterceptorContext< self.input } - public func getAttributes() -> AttributesType { + public func getAttributes() -> Context { return self.attributes } + + internal func setResult(result: Result) { + self.result = result + } } extension DefaultInterceptorContext: BeforeSerialization {} @@ -73,8 +76,13 @@ extension DefaultInterceptorContext: MutableResponse { } extension DefaultInterceptorContext: AfterDeserialization { - public func getResult() -> Result { - self.result! + public func getOutput() throws -> OutputType { + switch self.result! { + case .success(let output): + return output + case .failure(let error): + throw error + } } } @@ -85,8 +93,8 @@ extension DefaultInterceptorContext: AfterAttempt { } extension DefaultInterceptorContext: MutableOutputAfterAttempt { - public func updateResult(updated: Result) { - self.result = updated + public func updateOutput(updated: OutputType) { + self.result = .success(updated) } } diff --git a/Sources/ClientRuntime/Interceptor/HasAttributes+Logger.swift b/Sources/ClientRuntime/Interceptor/HasAttributes+Logger.swift deleted file mode 100644 index 1b6d876a7..000000000 --- a/Sources/ClientRuntime/Interceptor/HasAttributes+Logger.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import protocol Smithy.HasAttributes -import protocol Smithy.LogAgent -import struct Smithy.AttributeKey - -extension HasAttributes { - - var logger: LogAgent? { - get { get(key: loggerKey) } - set { set(key: loggerKey, value: newValue) } - } -} - -private let loggerKey = AttributeKey(name: "loggerKey") diff --git a/Sources/ClientRuntime/Interceptor/HttpInterceptor.swift b/Sources/ClientRuntime/Interceptor/HttpInterceptor.swift index 33f8c7b2f..a413199a9 100644 --- a/Sources/ClientRuntime/Interceptor/HttpInterceptor.swift +++ b/Sources/ClientRuntime/Interceptor/HttpInterceptor.swift @@ -10,7 +10,4 @@ import class SmithyHTTPAPI.SdkHttpRequest import class SmithyHTTPAPI.HttpResponse public protocol HttpInterceptor: Interceptor -where - RequestType == SdkHttpRequest, - ResponseType == HttpResponse, - AttributesType == Smithy.Context {} +where RequestType == SdkHttpRequest, ResponseType == HttpResponse {} diff --git a/Sources/ClientRuntime/Interceptor/HttpInterceptorProvider.swift b/Sources/ClientRuntime/Interceptor/HttpInterceptorProvider.swift new file mode 100644 index 000000000..7994f1e72 --- /dev/null +++ b/Sources/ClientRuntime/Interceptor/HttpInterceptorProvider.swift @@ -0,0 +1,17 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Provides implementations of `HttpInterceptor`. +/// +/// For the generic counterpart, see `InterceptorProvider`. +public protocol HttpInterceptorProvider { + + /// Creates an instance of an `HttpInterceptor` implementation. + /// + /// - Returns: The `HttpInterceptor` implementation. + func create() -> any HttpInterceptor +} diff --git a/Sources/ClientRuntime/Interceptor/Interceptor.swift b/Sources/ClientRuntime/Interceptor/Interceptor.swift index 0fefb967a..348de0913 100644 --- a/Sources/ClientRuntime/Interceptor/Interceptor.swift +++ b/Sources/ClientRuntime/Interceptor/Interceptor.swift @@ -7,10 +7,9 @@ import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// An interceptor allows injecting code into the SDK's request execution pipeline. -public protocol Interceptor { +public protocol Interceptor { /// The type of the modeled operation input. associatedtype InputType @@ -24,162 +23,159 @@ public protocol Interceptor) async throws + func readBeforeExecution(context: some BeforeSerialization) async throws /// A hook called before the operation input is serialized into the transport message. This /// method has the ability to modify the operation input. - func modifyBeforeSerialization(context: some MutableInput) async throws + func modifyBeforeSerialization(context: some MutableInput) async throws /// A hook called before the operation input is serialized into the transport message. - func readBeforeSerialization(context: some BeforeSerialization) async throws + func readBeforeSerialization(context: some BeforeSerialization) async throws /// A hook called after the operation input is serialized into the transport message. - func readAfterSerialization(context: some AfterSerialization) async throws + func readAfterSerialization(context: some AfterSerialization) async throws /// A hook called before the retry loop is entered. This method has the ability to modify the transport /// request message. - func modifyBeforeRetryLoop(context: some MutableRequest) async throws + func modifyBeforeRetryLoop(context: some MutableRequest) async throws /// A hook called before each attempt at sending the tranport request message to the service. - func readBeforeAttempt(context: some AfterSerialization) async throws + func readBeforeAttempt(context: some AfterSerialization) async throws /// A hook called before the transport request message is signed. This method has the ability to modify /// the transport request message. - func modifyBeforeSigning(context: some MutableRequest) async throws + func modifyBeforeSigning(context: some MutableRequest) async throws /// A hook called before the transport request message is signed. - func readBeforeSigning(context: some AfterSerialization) async throws + func readBeforeSigning(context: some AfterSerialization) async throws /// A hook called after the transport request message is signed. - func readAfterSigning(context: some AfterSerialization) async throws + func readAfterSigning(context: some AfterSerialization) async throws /// A hook called before the transport request message is sent to the service. This method has the ability /// to modify the transport request message. - func modifyBeforeTransmit(context: some MutableRequest) async throws + func modifyBeforeTransmit(context: some MutableRequest) async throws /// A hook called before the transport request message is sent to the service. - func readBeforeTransmit(context: some AfterSerialization) async throws + func readBeforeTransmit(context: some AfterSerialization) async throws /// A hook called after the transport request message is sent to the service, and a transport response /// message has been received. func readAfterTransmit( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws /// A hook called before the transport response message is deserialized. This method has the ability to /// modify the transport response message. func modifyBeforeDeserialization( - context: some MutableResponse + context: some MutableResponse ) async throws /// A hook alled before the transport response message is deserialized. func readBeforeDeserialization( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws /// A hook called after the transport response message is deserialized. func readAfterDeserialization( - context: some AfterDeserialization + context: some AfterDeserialization ) async throws /// A hook called when an attempt is completed. This method has the ability to modify the operation output. func modifyBeforeAttemptCompletion( - context: some MutableOutputAfterAttempt + context: some MutableOutputAfterAttempt ) async throws /// A hook called when an attempt is completed. func readAfterAttempt( - context: some AfterAttempt + context: some AfterAttempt ) async throws /// A hook called when execution is completed. This method has the ability to modify the operation output. func modifyBeforeCompletion( - context: some MutableOutputFinalization + context: some MutableOutputFinalization ) async throws /// A hook called when execution is completed. func readAfterExecution( - context: some Finalization + context: some Finalization ) async throws } extension Interceptor { - public func readBeforeExecution(context: some BeforeSerialization) async throws {} + public func readBeforeExecution(context: some BeforeSerialization) async throws {} - public func modifyBeforeSerialization(context: some MutableInput) async throws {} + public func modifyBeforeSerialization(context: some MutableInput) async throws {} - public func readBeforeSerialization(context: some BeforeSerialization) async throws {} + public func readBeforeSerialization(context: some BeforeSerialization) async throws {} public func readAfterSerialization( - context: some AfterSerialization + context: some AfterSerialization ) async throws {} public func modifyBeforeRetryLoop( - context: some MutableRequest + context: some MutableRequest ) async throws {} public func readBeforeAttempt( - context: some AfterSerialization + context: some AfterSerialization ) async throws {} - public func modifyBeforeSigning(context: some MutableRequest) async throws { + public func modifyBeforeSigning(context: some MutableRequest) async throws { } public func readBeforeSigning( - context: some AfterSerialization + context: some AfterSerialization ) async throws {} public func readAfterSigning( - context: some AfterSerialization + context: some AfterSerialization ) async throws {} public func modifyBeforeTransmit( - context: some MutableRequest + context: some MutableRequest ) async throws {} public func readBeforeTransmit( - context: some AfterSerialization + context: some AfterSerialization ) async throws {} public func readAfterTransmit( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws {} public func modifyBeforeDeserialization( - context: some MutableResponse + context: some MutableResponse ) async throws {} public func readBeforeDeserialization( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws {} public func readAfterDeserialization( - context: some AfterDeserialization + context: some AfterDeserialization ) async throws {} public func modifyBeforeAttemptCompletion( - context: some MutableOutputAfterAttempt + context: some MutableOutputAfterAttempt ) async throws {} public func readAfterAttempt( - context: some AfterAttempt + context: some AfterAttempt ) async throws {} public func modifyBeforeCompletion( - context: some MutableOutputFinalization + context: some MutableOutputFinalization ) async throws {} public func readAfterExecution( - context: some Finalization + context: some Finalization ) async throws {} } extension Interceptor { - func erase() -> AnyInterceptor { + func erase() -> AnyInterceptor { return AnyInterceptor(interceptor: self) } } diff --git a/Sources/ClientRuntime/Interceptor/InterceptorContext.swift b/Sources/ClientRuntime/Interceptor/InterceptorContext.swift index 698d83aa9..411f2776c 100644 --- a/Sources/ClientRuntime/Interceptor/InterceptorContext.swift +++ b/Sources/ClientRuntime/Interceptor/InterceptorContext.swift @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// The base type of all context objects passed to `Interceptor` methods. public protocol InterceptorContext: AnyObject { @@ -24,21 +24,18 @@ public protocol InterceptorContext: AnyObject { /// The type of the transport message that will be received by the operation being invoked. associatedtype ResponseType: ResponseMessage - /// The type of the attributes that will be available to all interceptors. - associatedtype AttributesType: HasAttributes - /// - Returns: The input for the operation being invoked. func getInput() -> InputType /// - Returns: The attributes available in this interceptor context. - func getAttributes() -> AttributesType + func getAttributes() -> Context } /// Context given to interceptor hooks called before request serialization. -public protocol BeforeSerialization: InterceptorContext {} +public protocol BeforeSerialization: InterceptorContext {} /// Context given to interceptor hooks that can mutate the operation input. -public protocol MutableInput: InterceptorContext { +public protocol MutableInput: InterceptorContext { /// Mutates the operation input. /// - Parameter updated: The updated operation input. @@ -48,7 +45,7 @@ public protocol MutableInput: InterceptorContext { /// Context given to interceptor hooks called after request serialization. /// /// These hooks have access to the serialized `RequestType`. -public protocol AfterSerialization: InterceptorContext { +public protocol AfterSerialization: InterceptorContext { /// - Returns: The serialized request. func getRequest() -> RequestType @@ -57,7 +54,7 @@ public protocol AfterSerialization: Inte /// Context given to interceptor hooks that can mutate the serialized request. /// /// These hooks have access to the serialized `RequestType` -public protocol MutableRequest: InterceptorContext { +public protocol MutableRequest: InterceptorContext { /// - Returns: The serialized request. func getRequest() -> RequestType @@ -70,7 +67,7 @@ public protocol MutableRequest: Intercep /// Context given to interceptor hooks called before response deserialization, after the response has been received. /// /// These hooks have access to the serialized `RequestType` and `ResponseType`. -public protocol BeforeDeserialization: InterceptorContext { +public protocol BeforeDeserialization: InterceptorContext { /// - Returns: The serialized request. func getRequest() -> RequestType @@ -81,7 +78,7 @@ public protocol BeforeDeserialization: InterceptorContext { +public protocol MutableResponse: InterceptorContext { /// - Returns: The serialized request. func getRequest() -> RequestType @@ -96,7 +93,7 @@ public protocol MutableResponse: +public protocol AfterDeserialization: InterceptorContext { /// - Returns: The serialized request. @@ -105,28 +102,30 @@ public protocol AfterDeserialization ResponseType - /// - Returns: The operation result. - func getResult() -> Result + /// - Returns: The operation output. + /// - Throws: The latest error that occurred during execution, if present. + func getOutput() throws -> OutputType } /// Context given to interceptor hooks called after each attempt at sending the request. /// /// These hooks have access to the serialized `RequestType` and `ResponseType` (if a response was received), as well as the operation output. -public protocol AfterAttempt: InterceptorContext { +public protocol AfterAttempt: InterceptorContext { /// - Returns: The serialized request. func getRequest() -> RequestType /// - Returns: The serialized response, if one was received. func getResponse() -> ResponseType? - /// - Returns: The operation result. - func getResult() -> Result + /// - Returns: The operation output. + /// - Throws: The latest error that occurred during execution, if present. + func getOutput() throws -> OutputType } /// Context given to interceptor hooks that can mutate the operation output, called after each attempt at sending the request. /// /// These hooks have access to the serialized `RequestType` and `ResponseType` (if a response was received), as well as the operation output. -public protocol MutableOutputAfterAttempt: +public protocol MutableOutputAfterAttempt: InterceptorContext { /// - Returns: The serialized request. @@ -135,34 +134,36 @@ public protocol MutableOutputAfterAttempt ResponseType? - /// - Returns: The operation result. - func getResult() -> Result + /// - Returns: The operation output. + /// - Throws: The latest error that occurred during execution, if present. + func getOutput() throws -> OutputType - /// Mutates the operation result. - /// - Parameter updated: The updated result. - func updateResult(updated: Result) + /// Mutates the operation output. + /// - Parameter updated: The updated output. + func updateOutput(updated: OutputType) } /// Context given to interceptor hooks called after execution. /// /// These hooks have access to the serialized `RequestType` (if it was successfully serialized) and the `ResponseType` /// (if a response was received), as well as the operation output. -public protocol Finalization: InterceptorContext { +public protocol Finalization: InterceptorContext { /// - Returns: The serialized request, if available. func getRequest() -> RequestType? /// - Returns: The serialized response, if one was received. func getResponse() -> ResponseType? - /// - Returns: The operation result. - func getResult() -> Result + /// - Returns: The operation output. + /// - Throws: The latest error that occurred during execution, if present. + func getOutput() throws -> OutputType } /// Context given to interceptor hooks that can mutate the operation output, called after execution. /// /// These hooks have access to the serialized `RequestType` (if it was successfully serialized) and the `ResponseType` /// (if a response was received), as well as the operation output. -public protocol MutableOutputFinalization: +public protocol MutableOutputFinalization: InterceptorContext { /// - Returns: The serialized request, if available. @@ -171,10 +172,11 @@ public protocol MutableOutputFinalization ResponseType? - /// - Returns: The operation result. - func getResult() -> Result + /// - Returns: The operation output. + /// - Throws: The latest error that occurred during execution, if present. + func getOutput() throws -> OutputType - /// Mutates the operation result. - /// - Parameter updated: The updated result. - func updateResult(updated: Result) + /// Mutates the operation output. + /// - Parameter updated: The updated output. + func updateOutput(updated: OutputType) } diff --git a/Sources/ClientRuntime/Interceptor/InterceptorProvider.swift b/Sources/ClientRuntime/Interceptor/InterceptorProvider.swift new file mode 100644 index 000000000..1ed37b0e2 --- /dev/null +++ b/Sources/ClientRuntime/Interceptor/InterceptorProvider.swift @@ -0,0 +1,27 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import protocol Smithy.RequestMessage +import protocol Smithy.ResponseMessage + +/// Provides implementations of `Interceptor` for any Request, Response, and Attributes types. +/// +/// This can be used to create `Interceptor`s that are generic on their Request/Response/Attributes +/// types, when you don't have access to the exact types until later. +public protocol InterceptorProvider { + + /// Creates an instance of an `Interceptor` implementation, specialized on the given + /// `RequestType` and `ResponseType` + /// + /// - Returns: The `Interceptor` implementation. + func create< + InputType, + OutputType, + RequestType: RequestMessage, + ResponseType: ResponseMessage + >() -> any Interceptor +} diff --git a/Sources/ClientRuntime/Interceptor/Interceptors.swift b/Sources/ClientRuntime/Interceptor/Interceptors.swift index 505944958..919414bce 100644 --- a/Sources/ClientRuntime/Interceptor/Interceptors.swift +++ b/Sources/ClientRuntime/Interceptor/Interceptors.swift @@ -5,9 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// Container for 0 or more interceptors that supports adding concrete interceptor /// implementations and closures that act as single-hook interceptors. @@ -15,101 +15,107 @@ public struct Interceptors< InputType, OutputType, RequestType: RequestMessage, - ResponseType: ResponseMessage, - AttributesType: HasAttributes + ResponseType: ResponseMessage > { internal typealias InterceptorType = AnyInterceptor< - InputType, OutputType, RequestType, ResponseType, AttributesType + InputType, OutputType, RequestType, ResponseType > internal var interceptors: [InterceptorType] = [] /// - Parameter interceptor: The Interceptor to add. public mutating func add( - _ interceptor: any Interceptor + _ interceptor: any Interceptor + ) { + self.interceptors.append(interceptor.erase()) + } + + /// - Parameter interceptor: The Interceptor to add. + public mutating func add( + _ interceptor: some Interceptor ) { self.interceptors.append(interceptor.erase()) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeExecution( - _ interceptorFn: @escaping (any BeforeSerialization) async throws -> Void + _ interceptorFn: @escaping (any BeforeSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeExecution: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeSerialization( - _ interceptorFn: @escaping (any MutableInput) async throws -> Void + _ interceptorFn: @escaping (any MutableInput) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeSerialization: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeSerialization( - _ interceptorFn: @escaping (any BeforeSerialization) async throws -> Void + _ interceptorFn: @escaping (any BeforeSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeSerialization: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterSerialization( - _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void + _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readAfterSerialization: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeRetryLoop( - _ interceptorFn: @escaping (any MutableRequest) async throws -> Void + _ interceptorFn: @escaping (any MutableRequest) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeRetryLoop: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeAttempt( - _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void + _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeAttempt: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeSigning( - _ interceptorFn: @escaping (any MutableRequest) async throws -> Void + _ interceptorFn: @escaping (any MutableRequest) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeSigning: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeSigning( - _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void + _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeSigning: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterSigning( - _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void + _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readAfterSigning: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeTransmit( - _ interceptorFn: @escaping (any MutableRequest) async throws -> Void + _ interceptorFn: @escaping (any MutableRequest) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeTransmit: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeTransmit( - _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void + _ interceptorFn: @escaping (any AfterSerialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeTransmit: interceptorFn)) } /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterTransmit( - _ interceptorFn: @escaping (any BeforeDeserialization) + _ interceptorFn: @escaping (any BeforeDeserialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readAfterTransmit: interceptorFn)) @@ -117,7 +123,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeDeserialization( - _ interceptorFn: @escaping (any MutableResponse) + _ interceptorFn: @escaping (any MutableResponse) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeDeserialization: interceptorFn)) @@ -125,7 +131,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadBeforeDeserialization( - _ interceptorFn: @escaping (any BeforeDeserialization) + _ interceptorFn: @escaping (any BeforeDeserialization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readBeforeDeserialization: interceptorFn)) @@ -134,7 +140,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterDeserialization( _ interceptorFn: @escaping ( - any AfterDeserialization + any AfterDeserialization ) async throws -> Void ) { @@ -144,7 +150,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeAttemptCompletion( _ interceptorFn: @escaping ( - any MutableOutputAfterAttempt + any MutableOutputAfterAttempt ) async throws -> Void ) { @@ -153,7 +159,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterAttempt( - _ interceptorFn: @escaping (any AfterAttempt) + _ interceptorFn: @escaping (any AfterAttempt) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readAfterAttempt: interceptorFn)) @@ -162,7 +168,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addModifyBeforeCompletion( _ interceptorFn: @escaping ( - any MutableOutputFinalization + any MutableOutputFinalization ) async throws -> Void ) { self.interceptors.append(AnyInterceptor(modifyBeforeCompletion: interceptorFn)) @@ -170,7 +176,7 @@ public struct Interceptors< /// - Parameter interceptorFn: The closure to use as the Interceptor hook. public mutating func addReadAfterExecution( - _ interceptorFn: @escaping (any Finalization) + _ interceptorFn: @escaping (any Finalization) async throws -> Void ) { self.interceptors.append(AnyInterceptor(readAfterExecution: interceptorFn)) @@ -320,7 +326,7 @@ extension Interceptors { } private func logError(error: Error, context: InterceptorType.InterceptorContextType) { - guard let logger = context.getAttributes().logger else { + guard let logger = context.getAttributes().getLogger() else { return } logger.error(error.localizedDescription) diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentLengthMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentLengthMiddleware.swift index 8679159ec..dffe2ad14 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentLengthMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentLengthMiddleware.swift @@ -54,7 +54,7 @@ public struct ContentLengthMiddleware // Only for HTTP/1.1 requests, will be removed in all HTTP/2 requests builder.updateHeader(name: "Transfer-Encoding", value: "chunked") } else { - let operation = attributes.get(key: AttributeKey(name: "Operation")) + let operation = attributes.getOperation() ?? "Error getting operation name" let errorMessage = (unsignedPayload ?? false) ? "Missing content-length for operation: \(operation)" : @@ -75,7 +75,7 @@ extension ContentLengthMiddleware: HttpInterceptor { public typealias OutputType = OperationStackOutput public func modifyBeforeTransmit( - context: some MutableRequest + context: some MutableRequest ) async throws { let builder = context.getRequest().toBuilder() try addHeaders(builder: builder, attributes: context.getAttributes()) diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentMD5Middleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentMD5Middleware.swift index c39b1e71a..99b8e6d3a 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentMD5Middleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentMD5Middleware.swift @@ -74,7 +74,7 @@ extension ContentMD5Middleware: HttpInterceptor { public typealias OutputType = OperationStackOutput public func modifyBeforeTransmit( - context: some MutableRequest + context: some MutableRequest ) async throws { let builder = context.getRequest().toBuilder() try await addHeaders(builder: builder, attributes: context.getAttributes()) diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentTypeMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentTypeMiddleware.swift index 9cd711b0c..5ceb91757 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/ContentTypeMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/ContentTypeMiddleware.swift @@ -39,7 +39,7 @@ extension ContentTypeMiddleware: HttpInterceptor { public typealias OutputType = OperationStackOutput public func modifyBeforeRetryLoop( - context: some MutableRequest + context: some MutableRequest ) async throws { let builder = context.getRequest().toBuilder() addHeaders(builder: builder) diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/DeserializeMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/DeserializeMiddleware.swift index b50cebf17..d7c23dd3f 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/DeserializeMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/DeserializeMiddleware.swift @@ -31,20 +31,8 @@ public struct DeserializeMiddleware: Middleware { var response = try await next.handle(context: context, input: input) // call handler to get http response - if let responseDateString = response.httpResponse.headers.value(for: "Date") { - let estimatedSkew = getEstimatedSkew(now: Date(), responseDateString: responseDateString) - context.estimatedSkew = estimatedSkew - } - - let result = try await deserialize(response: response.httpResponse, attributes: context) - - switch result { - case let .success(output): - response.output = output - case let .failure(error): - throw error - } - + let output = try await deserialize(response: response.httpResponse, attributes: context) + response.output = output return response } @@ -56,7 +44,12 @@ extension DeserializeMiddleware: ResponseMessageDeserializer { public func deserialize( response: HttpResponse, attributes: Context - ) async throws -> Result { + ) async throws -> OperationStackOutput { + if let responseDateString = response.headers.value(for: "Date") { + let estimatedSkew = getEstimatedSkew(now: Date(), responseDateString: responseDateString) + attributes.estimatedSkew = estimatedSkew + } + // check if the response body was effected by a previous middleware if let contextBody = attributes.httpResponse?.body { response.body = contextBody @@ -64,7 +57,7 @@ extension DeserializeMiddleware: ResponseMessageDeserializer { let copiedResponse = response if (200..<300).contains(response.statusCode.rawValue) { - return .success(try await wireResponseClosure(copiedResponse)) + return try await wireResponseClosure(copiedResponse) } else { // if the response is a stream, we need to cache the stream so that it can be read again // error deserialization reads the stream multiple times to first deserialize the protocol error @@ -72,7 +65,7 @@ extension DeserializeMiddleware: ResponseMessageDeserializer { // and then the service error eg. [AccountNotFoundException](https://github.com/awslabs/aws-sdk-swift/blob/d1d18eefb7457ed27d416b372573a1f815004eb1/Sources/Services/AWSCloudTrail/models/Models.swift#L62) let bodyData = try await copiedResponse.body.readData() copiedResponse.body = .data(bodyData) - return .failure(try await wireResponseErrorClosure(copiedResponse)) + throw try await wireResponseErrorClosure(copiedResponse) } } } diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/LoggerMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/LoggerMiddleware.swift index d3b1bf0f2..b6d64bae3 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/LoggerMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/LoggerMiddleware.swift @@ -65,7 +65,7 @@ extension LoggerMiddleware: HttpInterceptor { public typealias OutputType = OperationStackOutput public func readBeforeTransmit( - context: some AfterSerialization + context: some AfterSerialization ) async throws { guard let logger = context.getAttributes().getLogger() else { return @@ -75,7 +75,7 @@ extension LoggerMiddleware: HttpInterceptor { } public func readAfterTransmit( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws { guard let logger = context.getAttributes().getLogger() else { return diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/MutateHeadersMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/MutateHeadersMiddleware.swift index 20b04f5ac..5c3206391 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/MutateHeadersMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/MutateHeadersMiddleware.swift @@ -58,7 +58,7 @@ extension MutateHeadersMiddleware: HttpInterceptor { public typealias OutputType = OperationStackOutput public func modifyBeforeTransmit( - context: some MutableRequest + context: some MutableRequest ) async throws { let builder = context.getRequest().toBuilder() mutateHeaders(builder: builder) diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/URLHostMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/URLHostMiddleware.swift index 907519999..2b0d0e0b8 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/URLHostMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/URLHostMiddleware.swift @@ -46,7 +46,7 @@ extension URLHostMiddleware: HttpInterceptor { public typealias InputType = OperationStackInput public typealias OutputType = OperationStackOutput - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { // This is an interceptor and not a serializer because endpoints are used to resolve the host updateAttributes(attributes: context.getAttributes()) } diff --git a/Sources/ClientRuntime/Networking/Http/Middlewares/URLPathMiddleware.swift b/Sources/ClientRuntime/Networking/Http/Middlewares/URLPathMiddleware.swift index 2eabdd7ed..31308b6fe 100644 --- a/Sources/ClientRuntime/Networking/Http/Middlewares/URLPathMiddleware.swift +++ b/Sources/ClientRuntime/Networking/Http/Middlewares/URLPathMiddleware.swift @@ -49,7 +49,7 @@ extension URLPathMiddleware: HttpInterceptor { public typealias InputType = OperationStackInput public typealias OutputType = OperationStackOutput - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { // This is an interceptor and not a serializer because endpoints are used to resolve the host try updateAttributes(input: context.getInput(), attributes: context.getAttributes()) } diff --git a/Sources/ClientRuntime/Orchestrator/ApplyEndpoint.swift b/Sources/ClientRuntime/Orchestrator/ApplyEndpoint.swift index cb8f00dfd..73d6442c7 100644 --- a/Sources/ClientRuntime/Orchestrator/ApplyEndpoint.swift +++ b/Sources/ClientRuntime/Orchestrator/ApplyEndpoint.swift @@ -5,20 +5,17 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage -import protocol Smithy.HasAttributes import struct SmithyHTTPAuthAPI.SelectedAuthScheme /// Component used by an Orchestrator to modify a request message with the endpoint /// the request should be sent to. -public protocol ApplyEndpoint { +public protocol ApplyEndpoint { /// The type of the request message. associatedtype RequestType: RequestMessage - /// The type of the attributes the component requires. - associatedtype AttributesType: HasAttributes - /// Applies the endpoint to the request. /// - Parameters: /// - request: The request. @@ -28,18 +25,18 @@ public protocol ApplyEndpoint { func apply( request: RequestType, selectedAuthScheme: SelectedAuthScheme?, - attributes: AttributesType + attributes: Context ) async throws -> RequestType } /// Concrete ApplyEndpoint backed by a closure. -internal struct WrappedApplyEndpoint: ApplyEndpoint { - internal let closure: (RequestType, SelectedAuthScheme?, AttributesType) async throws -> RequestType +internal struct WrappedApplyEndpoint: ApplyEndpoint { + internal let closure: (RequestType, SelectedAuthScheme?, Context) async throws -> RequestType public func apply( request: RequestType, selectedAuthScheme: SelectedAuthScheme?, - attributes: AttributesType + attributes: Context ) async throws -> RequestType { return try await self.closure(request, selectedAuthScheme, attributes) } diff --git a/Sources/ClientRuntime/Orchestrator/ApplySigner.swift b/Sources/ClientRuntime/Orchestrator/ApplySigner.swift index 360c66db0..37c632318 100644 --- a/Sources/ClientRuntime/Orchestrator/ApplySigner.swift +++ b/Sources/ClientRuntime/Orchestrator/ApplySigner.swift @@ -5,19 +5,16 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage -import protocol Smithy.HasAttributes import struct SmithyHTTPAuthAPI.SelectedAuthScheme /// Component used by an Orchestrator to sign a request. -public protocol ApplySigner { +public protocol ApplySigner { /// The type of the request message. associatedtype RequestType: RequestMessage - /// The type of the attributes the component requires. - associatedtype AttributesType: HasAttributes - /// Applies the signer to the request. /// - Parameters: /// - request: The request to sign. @@ -27,18 +24,18 @@ public protocol ApplySigner { func apply( request: RequestType, selectedAuthScheme: SelectedAuthScheme?, - attributes: AttributesType + attributes: Context ) async throws -> RequestType } /// Concrete ApplySigner backed by a closure. -internal struct WrappedApplySigner: ApplySigner { - internal let closure: (RequestType, SelectedAuthScheme?, AttributesType) async throws -> RequestType +internal struct WrappedApplySigner: ApplySigner { + internal let closure: (RequestType, SelectedAuthScheme?, Context) async throws -> RequestType public func apply( request: RequestType, selectedAuthScheme: SelectedAuthScheme?, - attributes: AttributesType + attributes: Context ) async throws -> RequestType { return try await self.closure(request, selectedAuthScheme, attributes) } diff --git a/Sources/ClientRuntime/Orchestrator/AuthSchemeSelector.swift b/Sources/ClientRuntime/Orchestrator/AuthSchemeSelector.swift index 3421c5cad..eb54bee0d 100644 --- a/Sources/ClientRuntime/Orchestrator/AuthSchemeSelector.swift +++ b/Sources/ClientRuntime/Orchestrator/AuthSchemeSelector.swift @@ -5,26 +5,23 @@ // SPDX-License-Identifier: Apache-2.0 // -import protocol Smithy.HasAttributes +import class Smithy.Context import struct SmithyHTTPAuthAPI.SelectedAuthScheme /// Component used by an Orchestrator to select an auth scheme to use for the operation. -public protocol SelectAuthScheme { - - /// The type of the attributes the component requires. - associatedtype AttributesType: HasAttributes +public protocol SelectAuthScheme { /// Selects an auth scheme. /// - Parameter attributes: The attributes available. /// - Returns: The auth scheme to use, if available. - func select(attributes: AttributesType) async throws -> SelectedAuthScheme? + func select(attributes: Context) async throws -> SelectedAuthScheme? } /// Concrete SelectAuthScheme backed by a closure. -internal struct WrappedSelectAuthScheme: SelectAuthScheme { - internal let closure: (AttributesType) async throws -> SelectedAuthScheme? +internal struct WrappedSelectAuthScheme: SelectAuthScheme { + internal let closure: (Context) async throws -> SelectedAuthScheme? - public func select(attributes: AttributesType) async throws -> SelectedAuthScheme? { + public func select(attributes: Context) async throws -> SelectedAuthScheme? { return try await closure(attributes) } } diff --git a/Sources/ClientRuntime/Orchestrator/ExecuteRequest.swift b/Sources/ClientRuntime/Orchestrator/ExecuteRequest.swift index c75e91348..545ba147c 100644 --- a/Sources/ClientRuntime/Orchestrator/ExecuteRequest.swift +++ b/Sources/ClientRuntime/Orchestrator/ExecuteRequest.swift @@ -5,38 +5,34 @@ // SPDX-License-Identifier: Apache-2.0 // +import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes /// Component used by an Orchestrator to send a request to the service and receive a response. -public protocol ExecuteRequest { +public protocol ExecuteRequest { /// The type of the request message. associatedtype RequestType: RequestMessage /// The type of the response message. associatedtype ResponseType: ResponseMessage - /// The type of the attributes required by the component. - associatedtype AttributesType: HasAttributes - /// Sends the request and receives the response. /// - Parameters: /// - request: The request to send. /// - attributes: The attributes available. /// - Returns: The received response. - func execute(request: RequestType, attributes: AttributesType) async throws -> ResponseType + func execute(request: RequestType, attributes: Context) async throws -> ResponseType } /// Concrete ExecuteRequest backed by a closure. internal struct WrappedExecuteRequest< RequestType: RequestMessage, - ResponseType: ResponseMessage, - AttributesType: HasAttributes + ResponseType: ResponseMessage >: ExecuteRequest { - internal let closure: (RequestType, AttributesType) async throws -> ResponseType + internal let closure: (RequestType, Context) async throws -> ResponseType - public func execute(request: RequestType, attributes: AttributesType) async throws -> ResponseType { + public func execute(request: RequestType, attributes: Context) async throws -> ResponseType { return try await closure(request, attributes) } } diff --git a/Sources/ClientRuntime/Orchestrator/Orchestrator.swift b/Sources/ClientRuntime/Orchestrator/Orchestrator.swift index a1e0d65a5..db52e1dd2 100644 --- a/Sources/ClientRuntime/Orchestrator/Orchestrator.swift +++ b/Sources/ClientRuntime/Orchestrator/Orchestrator.swift @@ -9,7 +9,6 @@ import class Smithy.Context import enum Smithy.ClientError import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes import SmithyHTTPAPI import protocol SmithyRetriesAPI.RetryStrategy import struct SmithyRetriesAPI.RetryErrorInfo @@ -51,7 +50,7 @@ import struct SmithyRetriesAPI.RetryErrorInfo /// /// Error behavior works as follows: /// - If any Interceptors fail, the last error is stored and the rest are logged, meaning the last error that occurred will be thrown -/// - If the service response is deserialized as an error, it is stored as an error +/// - If the service response is deserialized as a modeled error, it is stored as an error /// - If an error occurs in steps 1 - 7b, store the error and go straight to 10 /// - If an error occurs in steps 7c - 7q, store the error and go straight to 7r /// - If an error occurs in step 7r, store the error and go to 7s @@ -65,19 +64,19 @@ public struct Orchestrator< ResponseType: ResponseMessage > { internal typealias InterceptorContextType = DefaultInterceptorContext< - InputType, OutputType, RequestType, ResponseType, Context + InputType, OutputType, RequestType, ResponseType > - private let interceptors: Interceptors + private let interceptors: Interceptors private let attributes: Context private let serialize: (InputType, RequestType.RequestBuilderType, Context) throws -> Void - private let deserialize: (ResponseType, Context) async throws -> Result + private let deserialize: (ResponseType, Context) async throws -> OutputType private let retryStrategy: (any RetryStrategy)? private let retryErrorInfoProvider: (Error) -> RetryErrorInfo? - private let selectAuthScheme: any SelectAuthScheme - private let applyEndpoint: any ApplyEndpoint - private let applySigner: any ApplySigner - private let executeRequest: any ExecuteRequest + private let selectAuthScheme: SelectAuthScheme + private let applyEndpoint: any ApplyEndpoint + private let applySigner: any ApplySigner + private let executeRequest: any ExecuteRequest internal init(builder: OrchestratorBuilder) { self.interceptors = builder.interceptors @@ -121,7 +120,7 @@ public struct Orchestrator< /// - Parameter input: Operation input /// - Returns: Presigned request public func presignRequest(input: InputType) async throws -> RequestType { - let context = DefaultInterceptorContext( + let context = DefaultInterceptorContext( input: input, attributes: attributes ) @@ -169,7 +168,7 @@ public struct Orchestrator< /// - Parameter input: Operation input /// - Returns: Operation output public func execute(input: InputType) async throws -> OutputType { - let context = DefaultInterceptorContext( + let context = DefaultInterceptorContext( input: input, attributes: attributes ) @@ -194,7 +193,7 @@ public struct Orchestrator< await attempt(context: context) } } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } return try await startCompletion(context: context) @@ -224,8 +223,11 @@ public struct Orchestrator< let copiedRequest = context.getRequest().toBuilder().build() await attempt(context: context) - - if case let .failure(error) = context.getResult() { + + do { + let _ = try context.getOutput() + await strategy.recordSuccess(token: token) + } catch let error { // If we can't get errorInfo, we definitely can't retry guard let errorInfo = retryErrorInfoProvider(error) else { return } @@ -238,8 +240,6 @@ public struct Orchestrator< context.updateRequest(updated: copiedRequest) await startAttempt(context: context, strategy: strategy, token: token) - } else { - await strategy.recordSuccess(token: token) } } @@ -281,31 +281,24 @@ public struct Orchestrator< try await interceptors.modifyBeforeDeserialization(context: context) try await interceptors.readBeforeDeserialization(context: context) - let result = try await deserialize(context.getResponse(), context.getAttributes()) - // Note: can't just directly pass 'result' to 'updateResult' because of variance: - // i.e. Result isn't a subtype of Result in swift. - switch result { - case let .success(output): - context.updateResult(updated: .success(output)) - case let .failure(error): - context.updateResult(updated: .failure(error)) - } - + let output = try await deserialize(context.getResponse(), context.getAttributes()) + context.updateOutput(updated: output) + try await interceptors.readAfterDeserialization(context: context) } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } // If modifyBeforeAttemptCompletion fails, we still want to let readAfterAttempt run do { try await interceptors.modifyBeforeAttemptCompletion(context: context) } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } do { try await interceptors.readAfterAttempt(context: context) } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } } @@ -314,20 +307,15 @@ public struct Orchestrator< do { try await interceptors.modifyBeforeCompletion(context: context) } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } do { try await interceptors.readAfterExecution(context: context) } catch let error { - context.updateResult(updated: .failure(error)) + context.setResult(result: .failure(error)) } // The last error that occurred, if any, is thrown - switch context.getResult() { - case let .success(output): - return output - case let .failure(error): - throw error - } + return try context.getOutput() } } diff --git a/Sources/ClientRuntime/Orchestrator/OrchestratorBuilder.swift b/Sources/ClientRuntime/Orchestrator/OrchestratorBuilder.swift index 9e8c9e709..a62dab45d 100644 --- a/Sources/ClientRuntime/Orchestrator/OrchestratorBuilder.swift +++ b/Sources/ClientRuntime/Orchestrator/OrchestratorBuilder.swift @@ -8,7 +8,6 @@ import class Smithy.Context import protocol Smithy.RequestMessage import protocol Smithy.ResponseMessage -import protocol Smithy.HasAttributes import protocol Smithy.RequestMessageSerializer import protocol Smithy.ResponseMessageDeserializer import struct SmithyHTTPAuthAPI.SelectedAuthScheme @@ -25,18 +24,18 @@ public class OrchestratorBuilder< ResponseType: ResponseMessage > { /// A mutable container of the interceptors the orchestrator will use - public var interceptors: Interceptors = + public var interceptors: Interceptors = Interceptors() internal var attributes: Smithy.Context? internal var serialize: (InputType, RequestType.RequestBuilderType, Context) throws -> Void = { _, _, _ in } - internal var deserialize: ((ResponseType, Context) async throws -> Result)? + internal var deserialize: ((ResponseType, Context) async throws -> OutputType)? internal var retryStrategy: (any RetryStrategy)? internal var retryErrorInfoProvider: ((Error) -> RetryErrorInfo?)? - internal var selectAuthScheme: (any SelectAuthScheme)? - internal var applyEndpoint: (any ApplyEndpoint)? - internal var applySigner: (any ApplySigner)? - internal var executeRequest: (any ExecuteRequest)? + internal var selectAuthScheme: SelectAuthScheme? + internal var applyEndpoint: (any ApplyEndpoint)? + internal var applySigner: (any ApplySigner)? + internal var executeRequest: (any ExecuteRequest)? public init() {} @@ -73,7 +72,7 @@ public class OrchestratorBuilder< /// - Returns: Builder @discardableResult public func deserialize( - _ deserializer: @escaping (ResponseType, Context) async throws -> Result + _ deserializer: @escaping (ResponseType, Context) async throws -> OutputType ) -> Self { self.deserialize = deserializer return self @@ -83,7 +82,7 @@ public class OrchestratorBuilder< /// - Returns: Builder @discardableResult public func deserialize( - _ deserializer: some ResponseMessageDeserializer + _ deserializer: some ResponseMessageDeserializer ) -> Self { return self.deserialize(deserializer.deserialize(response:attributes:)) } @@ -107,7 +106,7 @@ public class OrchestratorBuilder< /// - Parameter selectAuthScheme: Runtime component that selects the auth scheme /// - Returns: Builder @discardableResult - public func selectAuthScheme(_ selectAuthScheme: some SelectAuthScheme) -> Self { + public func selectAuthScheme(_ selectAuthScheme: SelectAuthScheme) -> Self { self.selectAuthScheme = selectAuthScheme return self } @@ -125,7 +124,7 @@ public class OrchestratorBuilder< /// - Parameter applyEndpoint: Runtime component that applies the endpoint to the request /// - Returns: Builder @discardableResult - public func applyEndpoint(_ applyEndpoint: some ApplyEndpoint) -> Self { + public func applyEndpoint(_ applyEndpoint: some ApplyEndpoint) -> Self { self.applyEndpoint = applyEndpoint return self } @@ -143,7 +142,7 @@ public class OrchestratorBuilder< /// - Parameter applySigner: Runtime component that applies the signer to the request /// - Returns: Builder @discardableResult - public func applySigner(_ applySigner: some ApplySigner) -> Self { + public func applySigner(_ applySigner: some ApplySigner) -> Self { self.applySigner = applySigner return self } @@ -162,7 +161,7 @@ public class OrchestratorBuilder< /// - Returns: Builder @discardableResult public func executeRequest( - _ executeRequest: some ExecuteRequest + _ executeRequest: some ExecuteRequest ) -> Self { self.executeRequest = executeRequest return self diff --git a/Sources/Smithy/Attribute.swift b/Sources/Smithy/Attribute.swift index 5b0c48c67..566063495 100644 --- a/Sources/Smithy/Attribute.swift +++ b/Sources/Smithy/Attribute.swift @@ -41,22 +41,3 @@ public struct Attributes { attributes.removeValue(forKey: key.name) } } - -/// A type that can be used as a type-safe property bag. -public protocol HasAttributes: AnyObject { - /// - Parameter key: The key of the attribute to get. - /// - Returns: The attribute, if it exists. - func get(key: AttributeKey) -> T? - - /// - Parameter key: The key of the attribute to get. - /// - Returns: `true` if the property bag contains a value for the specified `key`, otherwise `false`. - func contains(key: AttributeKey) -> Bool - - /// - Parameters: - /// - key: The key to associate with `value`. - /// - value: The value to set in the property bag. - func set(key: AttributeKey, value: T?) - - /// - Parameter key: The key of the attribute to remove from the property bag. - func remove(key: AttributeKey) -} diff --git a/Sources/Smithy/Context.swift b/Sources/Smithy/Context.swift index 41be93f5c..7ee95da77 100644 --- a/Sources/Smithy/Context.swift +++ b/Sources/Smithy/Context.swift @@ -23,8 +23,7 @@ public class Context { } } -extension Context: HasAttributes { - +extension Context { public func get(key: AttributeKey) -> T? { self.attributes.get(key: key) } diff --git a/Sources/Smithy/RequestMessageBuilder.swift b/Sources/Smithy/RequestMessageBuilder.swift index 24e2a2787..e5dc93b16 100644 --- a/Sources/Smithy/RequestMessageBuilder.swift +++ b/Sources/Smithy/RequestMessageBuilder.swift @@ -13,6 +13,10 @@ public protocol RequestMessageBuilder: AnyObject { init() + func withHost(_ host: String) -> Self + + func withBody(_ body: ByteStream) -> Self + /// - Returns: The built request. func build() -> RequestType } diff --git a/Sources/Smithy/ResponseMessageDeserializer.swift b/Sources/Smithy/ResponseMessageDeserializer.swift index 7edd0b5d7..5c5ec5846 100644 --- a/Sources/Smithy/ResponseMessageDeserializer.swift +++ b/Sources/Smithy/ResponseMessageDeserializer.swift @@ -6,20 +6,17 @@ // /// Deserializes service response messages into modeled operation output or error. -public protocol ResponseMessageDeserializer { +public protocol ResponseMessageDeserializer { /// The type of the modeled operation output. associatedtype OutputType - + /// The type of the serialized response message. associatedtype ResponseType: ResponseMessage - - /// The type of the attributes the deserializer requires. - associatedtype AttributesType: HasAttributes - + /// Applies the deserializer to the given response, returning the modeled operation output. /// - Parameters: /// - response: The response message. /// - attributes: The attributes available to the deserializer. /// - Returns: The deserialized modeled response or modeled error. Throws if deserialization fails. - func deserialize(response: ResponseType, attributes: AttributesType) async throws -> Result + func deserialize(response: ResponseType, attributes: Context) async throws -> OutputType } diff --git a/Sources/SmithyHTTPAPI/SdkHttpRequest.swift b/Sources/SmithyHTTPAPI/SdkHttpRequest.swift index 9ca985f02..e606c52d6 100644 --- a/Sources/SmithyHTTPAPI/SdkHttpRequest.swift +++ b/Sources/SmithyHTTPAPI/SdkHttpRequest.swift @@ -128,7 +128,7 @@ extension SdkHttpRequest: CustomDebugStringConvertible, CustomStringConvertible } } -public class SdkHttpRequestBuilder: RequestMessageBuilder { +public final class SdkHttpRequestBuilder: RequestMessageBuilder { required public init() {} diff --git a/Sources/WeatherSDK/WeatherClient.swift b/Sources/WeatherSDK/WeatherClient.swift index fa49d2bb6..1b7646395 100644 --- a/Sources/WeatherSDK/WeatherClient.swift +++ b/Sources/WeatherSDK/WeatherClient.swift @@ -15,7 +15,9 @@ import enum SmithyReadWrite.WritingClosures import protocol ClientRuntime.Client import protocol ClientRuntime.DefaultClientConfiguration import protocol ClientRuntime.DefaultHttpClientConfiguration +import protocol ClientRuntime.HttpInterceptorProvider import protocol ClientRuntime.IdempotencyTokenGenerator +import protocol ClientRuntime.InterceptorProvider import protocol ClientRuntime.TelemetryProvider import protocol Smithy.LogAgent import protocol SmithyHTTPAPI.HTTPClient @@ -77,9 +79,13 @@ extension WeatherClient { public var authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver + public private(set) var interceptorProviders: [ClientRuntime.InterceptorProvider] + + public private(set) var httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider] + internal let logger: Smithy.LogAgent - private init(_ telemetryProvider: ClientRuntime.TelemetryProvider, _ retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions, _ clientLogMode: ClientRuntime.ClientLogMode, _ endpoint: Swift.String?, _ idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator, _ httpClientEngine: SmithyHTTPAPI.HTTPClient, _ httpClientConfiguration: ClientRuntime.HttpClientConfiguration, _ authSchemes: SmithyHTTPAuthAPI.AuthSchemes?, _ authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver) { + private init(_ telemetryProvider: ClientRuntime.TelemetryProvider, _ retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions, _ clientLogMode: ClientRuntime.ClientLogMode, _ endpoint: Swift.String?, _ idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator, _ httpClientEngine: SmithyHTTPAPI.HTTPClient, _ httpClientConfiguration: ClientRuntime.HttpClientConfiguration, _ authSchemes: SmithyHTTPAuthAPI.AuthSchemes?, _ authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver, _ interceptorProviders: [ClientRuntime.InterceptorProvider], _ httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider]) { self.telemetryProvider = telemetryProvider self.retryStrategyOptions = retryStrategyOptions self.clientLogMode = clientLogMode @@ -89,20 +95,30 @@ extension WeatherClient { self.httpClientConfiguration = httpClientConfiguration self.authSchemes = authSchemes self.authSchemeResolver = authSchemeResolver + self.interceptorProviders = interceptorProviders + self.httpInterceptorProviders = httpInterceptorProviders self.logger = telemetryProvider.loggerProvider.getLogger(name: WeatherClient.clientName) } - public convenience init(telemetryProvider: ClientRuntime.TelemetryProvider? = nil, retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions? = nil, clientLogMode: ClientRuntime.ClientLogMode? = nil, endpoint: Swift.String? = nil, idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator? = nil, httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil, httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil, authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil) throws { - self.init(telemetryProvider ?? ClientRuntime.DefaultTelemetry.provider, retryStrategyOptions ?? ClientRuntime.ClientConfigurationDefaults.defaultRetryStrategyOptions, clientLogMode ?? ClientRuntime.ClientConfigurationDefaults.defaultClientLogMode, endpoint, idempotencyTokenGenerator ?? ClientRuntime.ClientConfigurationDefaults.defaultIdempotencyTokenGenerator, httpClientEngine ?? ClientRuntime.ClientConfigurationDefaults.makeClient(httpClientConfiguration: httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration), httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration, authSchemes, authSchemeResolver ?? ClientRuntime.ClientConfigurationDefaults.defaultAuthSchemeResolver) + public convenience init(telemetryProvider: ClientRuntime.TelemetryProvider? = nil, retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions? = nil, clientLogMode: ClientRuntime.ClientLogMode? = nil, endpoint: Swift.String? = nil, idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator? = nil, httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil, httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil, authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil, interceptorProviders: [ClientRuntime.InterceptorProvider]? = nil, httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider]? = nil) throws { + self.init(telemetryProvider ?? ClientRuntime.DefaultTelemetry.provider, retryStrategyOptions ?? ClientRuntime.ClientConfigurationDefaults.defaultRetryStrategyOptions, clientLogMode ?? ClientRuntime.ClientConfigurationDefaults.defaultClientLogMode, endpoint, idempotencyTokenGenerator ?? ClientRuntime.ClientConfigurationDefaults.defaultIdempotencyTokenGenerator, httpClientEngine ?? ClientRuntime.ClientConfigurationDefaults.makeClient(httpClientConfiguration: httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration), httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration, authSchemes, authSchemeResolver ?? ClientRuntime.ClientConfigurationDefaults.defaultAuthSchemeResolver, interceptorProviders ?? [], httpInterceptorProviders ?? []) } public convenience required init() async throws { - try await self.init(telemetryProvider: nil, retryStrategyOptions: nil, clientLogMode: nil, endpoint: nil, idempotencyTokenGenerator: nil, httpClientEngine: nil, httpClientConfiguration: nil, authSchemes: nil, authSchemeResolver: nil) + try await self.init(telemetryProvider: nil, retryStrategyOptions: nil, clientLogMode: nil, endpoint: nil, idempotencyTokenGenerator: nil, httpClientEngine: nil, httpClientConfiguration: nil, authSchemes: nil, authSchemeResolver: nil, interceptorProviders: nil, httpInterceptorProviders: nil) } public var partitionID: String? { return "" } + public func addInterceptorProvider(_ provider: ClientRuntime.InterceptorProvider) { + self.interceptorProviders.append(provider) + } + + public func addInterceptorProvider(_ provider: ClientRuntime.HttpInterceptorProvider) { + self.httpInterceptorProviders.append(provider) + } + } public static func builder() -> ClientRuntime.ClientBuilder { diff --git a/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift b/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift index 09d9cf51d..57d681ec3 100644 --- a/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift +++ b/Tests/ClientRuntimeTests/InterceptorTests/InterceptorTests.swift @@ -20,7 +20,7 @@ class InterceptorTests: XCTestCase { public var property: String? } - struct AddAttributeInterceptor: Interceptor { + struct AddAttributeInterceptor: Interceptor { private let key: AttributeKey private let value: T @@ -29,13 +29,13 @@ class InterceptorTests: XCTestCase { self.value = value } - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { let attributes = context.getAttributes() attributes.set(key: self.key, value: self.value) } } - struct ModifyInputInterceptor: Interceptor { + struct ModifyInputInterceptor: Interceptor { private let keyPath: WritableKeyPath private let value: String @@ -44,7 +44,7 @@ class InterceptorTests: XCTestCase { self.value = value } - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { var input = context.getInput() input[keyPath: keyPath] = value context.updateInput(updated: input) @@ -60,15 +60,16 @@ class InterceptorTests: XCTestCase { self.headerValue = headerValue } - public func modifyBeforeTransmit(context: some MutableRequest) async throws { + public func modifyBeforeTransmit(context: some MutableRequest) async throws { let builder = context.getRequest().toBuilder() builder.withHeader(name: headerName, value: headerValue) context.updateRequest(updated: builder.build()) } } - struct ModifyMultipleInterceptor: HttpInterceptor { + struct ModifyMultipleInterceptor: HttpInterceptor { public typealias InputType = TestInput + public typealias OutputType = TestOutput private let newInputValue: Int @@ -76,13 +77,13 @@ class InterceptorTests: XCTestCase { self.newInputValue = newInputValue } - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { var input = context.getInput() input.otherProperty = newInputValue context.updateInput(updated: input) } - public func modifyBeforeTransmit(context: some MutableRequest) async throws { + public func modifyBeforeTransmit(context: some MutableRequest) async throws { let input: TestInput = try XCTUnwrap(context.getInput()) let builder = context.getRequest().toBuilder() builder.withHeader(name: "otherProperty", value: "\(input.otherProperty)") @@ -93,13 +94,13 @@ class InterceptorTests: XCTestCase { func test_mutation() async throws { let httpContext = Context(attributes: Attributes()) let input = TestInput(property: "foo") - let interceptorContext = DefaultInterceptorContext(input: input, attributes: httpContext) - let addAttributeInterceptor = AddAttributeInterceptor(key: AttributeKey(name: "foo"), value: "bar") - let modifyInputInterceptor = ModifyInputInterceptor(keyPath: \.property, value: "bar") + let interceptorContext = DefaultInterceptorContext(input: input, attributes: httpContext) + let addAttributeInterceptor = AddAttributeInterceptor(key: AttributeKey(name: "foo"), value: "bar") + let modifyInputInterceptor = ModifyInputInterceptor(keyPath: \.property, value: "bar") let addHeaderInterceptor = AddHeaderInterceptor(headerName: "foo", headerValue: "bar") - let modifyMultipleInterceptor = ModifyMultipleInterceptor(newInputValue: 1) + let modifyMultipleInterceptor = ModifyMultipleInterceptor(newInputValue: 1) - let interceptors: [AnyInterceptor] = [ + let interceptors: [AnyInterceptor] = [ addAttributeInterceptor.erase(), modifyInputInterceptor.erase(), addHeaderInterceptor.erase(), @@ -116,8 +117,41 @@ class InterceptorTests: XCTestCase { let updatedInput = interceptorContext.getInput() XCTAssertEqual(updatedInput.property, "bar") XCTAssertEqual(updatedInput.otherProperty, 1) - XCTAssertEqual(interceptorContext.getAttributes().get(key: AttributeKey(name: "foo")), "bar") + XCTAssertEqual(interceptorContext.getAttributes().attributes.get(key: AttributeKey(name: "foo")), "bar") XCTAssertEqual(interceptorContext.getRequest().headers.value(for: "foo"), "bar") XCTAssertEqual(interceptorContext.getRequest().headers.value(for: "otherProperty"), "1") } + + struct ModifyHostInterceptor: Interceptor { + func modifyBeforeRetryLoop(context: some MutableRequest) async throws { + context.updateRequest(updated: context.getRequest().toBuilder().withHost("foo").build()) + } + } + + struct ModifyHostInterceptorProvider: InterceptorProvider { + func create() -> any Interceptor { + ModifyHostInterceptor() + } + } + + func test_providers() async throws { + let provider1 = ModifyHostInterceptorProvider() + var interceptors = Interceptors() + + interceptors.add(provider1.create()) + + let attributes = Context(attributes: Attributes()) + let input = TestInput() + + let context = DefaultInterceptorContext(input: input, attributes: attributes) + context.updateRequest(updated: SdkHttpRequestBuilder().build()) + + try await interceptors.modifyBeforeSerialization(context: context) + try await interceptors.modifyBeforeRetryLoop(context: context) + try await interceptors.modifyBeforeTransmit(context: context) + + let resultRequest = try XCTUnwrap(context.getRequest()) + + XCTAssertEqual(resultRequest.host, "foo") + } } diff --git a/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift b/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift index cb815817d..71d2e0e76 100644 --- a/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift +++ b/Tests/ClientRuntimeTests/OrchestratorTests/OrchestratorTests.swift @@ -65,102 +65,100 @@ class OrchestratorTests: XCTestCase { } } - class TraceInterceptor: - Interceptor - { + class TraceInterceptor: Interceptor { var trace: Trace init(trace: Trace) { self.trace = trace } - public func readBeforeExecution(context: some BeforeSerialization) async throws { + public func readBeforeExecution(context: some BeforeSerialization) async throws { trace.append("readBeforeExecution") } - public func modifyBeforeSerialization(context: some MutableInput) async throws { + public func modifyBeforeSerialization(context: some MutableInput) async throws { trace.append("modifyBeforeSerialization") } - public func readBeforeSerialization(context: some BeforeSerialization) async throws { + public func readBeforeSerialization(context: some BeforeSerialization) async throws { trace.append("readBeforeSerialization") } - public func readAfterSerialization(context: some AfterSerialization) async throws { + public func readAfterSerialization(context: some AfterSerialization) async throws { trace.append("readAfterSerialization") } - public func modifyBeforeRetryLoop(context: some MutableRequest) async throws { + public func modifyBeforeRetryLoop(context: some MutableRequest) async throws { trace.append("modifyBeforeRetryLoop") } - public func readBeforeAttempt(context: some AfterSerialization) async throws { + public func readBeforeAttempt(context: some AfterSerialization) async throws { trace.append("readBeforeAttempt") } - public func modifyBeforeSigning(context: some MutableRequest) async throws { + public func modifyBeforeSigning(context: some MutableRequest) async throws { trace.append("modifyBeforeSigning") } - public func readBeforeSigning(context: some AfterSerialization) async throws { + public func readBeforeSigning(context: some AfterSerialization) async throws { trace.append("readBeforeSigning") } - public func readAfterSigning(context: some AfterSerialization) async throws { + public func readAfterSigning(context: some AfterSerialization) async throws { trace.append("readAfterSigning") } - public func modifyBeforeTransmit(context: some MutableRequest) async throws { + public func modifyBeforeTransmit(context: some MutableRequest) async throws { trace.append("modifyBeforeTransmit") } - public func readBeforeTransmit(context: some AfterSerialization) async throws { + public func readBeforeTransmit(context: some AfterSerialization) async throws { trace.append("readBeforeTransmit") } public func readAfterTransmit( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws { trace.append("readAfterTransmit") } public func modifyBeforeDeserialization( - context: some MutableResponse + context: some MutableResponse ) async throws { trace.append("modifyBeforeDeserialization") } public func readBeforeDeserialization( - context: some BeforeDeserialization + context: some BeforeDeserialization ) async throws { trace.append("readBeforeDeserialization") } public func readAfterDeserialization( - context: some AfterDeserialization + context: some AfterDeserialization ) async throws { trace.append("readAfterDeserialization") } public func modifyBeforeAttemptCompletion( - context: some MutableOutputAfterAttempt + context: some MutableOutputAfterAttempt ) async throws { trace.append("modifyBeforeAttemptCompletion") } - public func readAfterAttempt(context: some AfterAttempt) async throws + public func readAfterAttempt(context: some AfterAttempt) async throws { trace.append("readAfterAttempt") } public func modifyBeforeCompletion( - context: some MutableOutputFinalization + context: some MutableOutputFinalization ) async throws { trace.append("modifyBeforeCompletion") } public func readAfterExecution( - context: some Finalization + context: some Finalization ) async throws { trace.append("readAfterExecution") } @@ -206,7 +204,7 @@ class OrchestratorTests: XCTestCase { .withPath(value: "/") .withOperation(value: "Test") .build() - let traceInterceptor = TraceInterceptor(trace: trace) + let traceInterceptor = TraceInterceptor(trace: trace) let builder = OrchestratorBuilder() .attributes(attributes) .serialize({ input, builder, _ in @@ -220,14 +218,14 @@ class OrchestratorTests: XCTestCase { trace.append("deserialize") if (200..<300).contains(response.statusCode.rawValue) { guard case let .data(data) = response.body else { - return .success(TestOutput(bar: "")) + return TestOutput(bar: "") } let bar = try! JSONDecoder().decode(String.self, from: data!) - return .success(TestOutput(bar: bar)) + return TestOutput(bar: bar) } else { let responseReader = try SmithyJSON.Reader.from(data: try await response.data()) let baseError = try TestBaseError(httpResponse: response, responseReader: responseReader, noErrorWrapping: true) - return .failure(try UnknownHTTPServiceError.makeError(baseError: baseError)) + throw try UnknownHTTPServiceError.makeError(baseError: baseError) } }) .retryStrategy(DefaultRetryStrategy(options: RetryStrategyOptions(backoffStrategy: ExponentialBackoffStrategy()))) @@ -328,7 +326,6 @@ class OrchestratorTests: XCTestCase { "modifyBeforeDeserialization", "readBeforeDeserialization", "deserialize", - "readAfterDeserialization", "modifyBeforeAttemptCompletion", "readAfterAttempt", "errorInfo", @@ -1275,7 +1272,7 @@ class OrchestratorTests: XCTestCase { let trace = Trace() let result = await asyncResult { let b = self.traceOrchestrator(trace: trace) - b.attributes?.logger = logger + b.attributes = b.attributes?.toBuilder().withLogger(value: logger).build() b.interceptors.addReadBeforeExecution({ _ in throw TestError(value: "firstError") }) b.interceptors.addReadBeforeExecution({ _ in throw TestError(value: "secondError") }) return try await b.build().execute(input: TestInput(foo: "")) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ClientConfiguration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ClientConfiguration.kt index d7086c1de..155c880f0 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ClientConfiguration.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ClientConfiguration.kt @@ -8,6 +8,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.lang.Function import software.amazon.smithy.swift.codegen.model.buildSymbol /** @@ -21,6 +22,11 @@ interface ClientConfiguration { fun getProperties(ctx: ProtocolGenerator.GenerationContext): Set + /** + * The methods to render in the generated client configuration + */ + fun getMethods(ctx: ProtocolGenerator.GenerationContext): Set = setOf() + companion object { fun runtimeSymbol( name: String, diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ConfigProperty.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ConfigProperty.kt index 5e436c02f..f81ce7b5c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ConfigProperty.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/config/ConfigProperty.kt @@ -7,12 +7,14 @@ package software.amazon.smithy.swift.codegen.config import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.lang.AccessModifier import software.amazon.smithy.swift.codegen.model.isOptional data class ConfigProperty( val name: String, val type: Symbol, - val default: DefaultProvider? = null + val default: DefaultProvider? = null, + val accessModifier: AccessModifier = AccessModifier.Public, ) { constructor( @@ -20,11 +22,16 @@ data class ConfigProperty( type: Symbol, default: (SwiftWriter) -> String, isThrowable: Boolean = false, - isAsync: Boolean = false - ) : this(name, type, DefaultProvider(default, isThrowable, isAsync)) + isAsync: Boolean = false, + accessModifier: AccessModifier = AccessModifier.Public + ) : this(name, type, DefaultProvider(default, isThrowable, isAsync), accessModifier) init { if (!type.isOptional() && default == null) throw RuntimeException("Non-optional client config property must have a default value") } + + fun render(writer: SwiftWriter) { + writer.write("${accessModifier.renderedRightPad()}var \$L: \$N", name, type) + } } 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 9cc862adf..2aaf5193c 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 @@ -7,6 +7,9 @@ package software.amazon.smithy.swift.codegen.config import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.lang.AccessModifier +import software.amazon.smithy.swift.codegen.lang.Function +import software.amazon.smithy.swift.codegen.lang.FunctionParameter import software.amazon.smithy.swift.codegen.model.toOptional import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyRetriesAPITypes @@ -37,5 +40,21 @@ class DefaultClientConfiguration : ClientConfiguration { ClientRuntimeTypes.Core.IdempotencyTokenGenerator, { it.format("\$N.defaultIdempotencyTokenGenerator", ClientRuntimeTypes.Core.ClientConfigurationDefaults) }, ), + ConfigProperty( + "interceptorProviders", + ClientRuntimeTypes.Core.InterceptorProviders, + { "[]" }, + accessModifier = AccessModifier.PublicPrivateSet + ) + ) + + override fun getMethods(ctx: ProtocolGenerator.GenerationContext): Set = setOf( + Function( + name = "addInterceptorProvider", + renderBody = { writer -> writer.write("self.interceptorProviders.append(provider)") }, + parameters = listOf( + FunctionParameter.NoLabel("provider", ClientRuntimeTypes.Core.InterceptorProvider) + ), + ) ) } 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 d090706ce..bba5b98ac 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 @@ -7,6 +7,9 @@ package software.amazon.smithy.swift.codegen.config import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.lang.AccessModifier +import software.amazon.smithy.swift.codegen.lang.Function +import software.amazon.smithy.swift.codegen.lang.FunctionParameter import software.amazon.smithy.swift.codegen.model.toOptional import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes @@ -35,6 +38,22 @@ class DefaultHttpClientConfiguration : ClientConfiguration { "authSchemeResolver", SmithyHTTPAuthAPITypes.AuthSchemeResolver, { it.format("\$N.defaultAuthSchemeResolver", ClientRuntimeTypes.Core.ClientConfigurationDefaults) }, + ), + ConfigProperty( + "httpInterceptorProviders", + ClientRuntimeTypes.Core.HttpInterceptorProviders, + { "[]" }, + accessModifier = AccessModifier.PublicPrivateSet + ) + ) + + override fun getMethods(ctx: ProtocolGenerator.GenerationContext): Set = setOf( + Function( + name = "addInterceptorProvider", + renderBody = { writer -> writer.write("self.httpInterceptorProviders.append(provider)") }, + parameters = listOf( + FunctionParameter.NoLabel("provider", ClientRuntimeTypes.Core.HttpInterceptorProvider) + ), ) ) } 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 febc7fa00..3141bc2a5 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 @@ -106,9 +106,11 @@ open class HttpProtocolServiceClient( serviceConfig.clientName.toUpperCamelCase(), clientConfigurationProtocols ) { - val properties: List = ctx.integrations - .flatMap { it.clientConfigurations(ctx).flatMap { it.getProperties(ctx) } } + val clientConfigs = ctx.integrations.flatMap { it.clientConfigurations(ctx) } + val properties: List = clientConfigs + .flatMap { it.getProperties(ctx) } .let { overrideConfigProperties(it) } + .sortedBy { it.accessModifier } renderConfigClassVariables(serviceSymbol, properties) @@ -123,6 +125,14 @@ open class HttpProtocolServiceClient( renderCustomConfigInitializer(properties) renderPartitionID() + + clientConfigs + .flatMap { it.getMethods(ctx) } + .sortedBy { it.accessModifier } + .forEach { + it.render(writer) + writer.write("") + } } writer.write("") } @@ -153,11 +163,10 @@ open class HttpProtocolServiceClient( * Declare class variables in client configuration class */ private fun renderConfigClassVariables(serviceSymbol: Symbol, properties: List) { - properties - .forEach { - writer.write("public var \$L: \$N", it.name, it.type) - writer.write("") - } + properties.forEach { + it.render(writer) + writer.write("") + } writer.injectSection(ConfigClassVariablesCustomization(serviceSymbol)) writer.write("") } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index 986bf2434..bd8ff661b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -112,15 +112,15 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B writer.write( """ let op = builder.attributes(context) - .deserialize({ (_, _) in - return .success($outputSymbol()) + .deserialize({ (_, _) in + return $outputSymbol() }) .executeRequest({ (actual, attributes) in ${'$'}{C|} return HttpResponse(body: .noStream, statusCode: .ok) }) .build() - + _ = try await op.execute(input: input) """.trimIndent(), Runnable { renderBodyAssert(test, inputSymbol, inputShape) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/RequestTestEndpointResolverMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/RequestTestEndpointResolverMiddleware.kt index c3f3e5901..c8cfa73bc 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/RequestTestEndpointResolverMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/RequestTestEndpointResolverMiddleware.kt @@ -29,7 +29,7 @@ class RequestTestEndpointResolverMiddleware(private val model: Model, private va .withHost("\(attributes.hostPrefix ?? "")\(attributes.host ?? "")") .build() }) - """.trimMargin() + """.trimIndent() ) } else { writer.openBlock( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/AccessModifier.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/AccessModifier.kt new file mode 100644 index 000000000..af0769702 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/AccessModifier.kt @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.swift.codegen.lang + +/** + * Swift access control modifiers. + * + * Note: This is an incomplete set - we can add more if we need. + */ +enum class AccessModifier { + Public, + PublicPrivateSet, + Internal, + Private, + None; + + /** + * Creates a string representation of the access control modifier. + */ + fun rendered(): String = when (this) { + Public -> "public" + PublicPrivateSet -> "public private(set)" + Internal -> "internal" + Private -> "private" + None -> "" + } + + /** + * Same as [rendered], but with a trailing space when not [None]. + */ + fun renderedRightPad(): String = when (this) { + None -> "" + else -> rendered().plus(" ") + } +} 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 new file mode 100644 index 000000000..2e872f343 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/Function.kt @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.swift.codegen.lang + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.swift.codegen.SwiftWriter +import java.util.function.Consumer + +/** + * Representation of a Swift function. + */ +data class Function( + val name: String, + val renderBody: Consumer, + val parameters: List = emptyList(), + val returnType: Symbol? = null, + val accessModifier: AccessModifier = AccessModifier.Public, + val isAsync: Boolean = false, + val isThrowing: Boolean = false, +) { + + /** + * Render this function using the given writer. + */ + fun render(writer: SwiftWriter) { + 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{", "}") { + renderBody.accept(writer) + } + } +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/FunctionParameter.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/FunctionParameter.kt new file mode 100644 index 000000000..787e047f8 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/lang/FunctionParameter.kt @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.swift.codegen.lang + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.swift.codegen.SwiftWriter + +/** + * Representation of a Swift function parameter. + */ +sealed class FunctionParameter(val name: String, val type: Symbol) { + + /** + * Function parameter with no label, i.e. `_ foo: String` + */ + class NoLabel(name: String, type: Symbol) : FunctionParameter(name, type) + + /** + * Function parameter with the default label, i.e. `foo: String` + */ + class DefaultLabel(val label: String, type: Symbol) : FunctionParameter(label, type) + + /** + * Function parameter with a custom label, i.e. `foo bar: String` + */ + class CustomLabel(val label: String, name: String, type: Symbol) : FunctionParameter(name, type) + + /** + * Creates a string representation of this function parameter. + */ + fun rendered(writer: SwiftWriter): String = when (this) { + is NoLabel -> writer.format("_ $name: \$N", type) + is DefaultLabel -> writer.format("$label: \$N", type) + is CustomLabel -> writer.format("$label $name: \$N", type) + } +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/middleware/MiddlewareExecutionGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/middleware/MiddlewareExecutionGenerator.kt index 511022886..e506e72a4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/middleware/MiddlewareExecutionGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/middleware/MiddlewareExecutionGenerator.kt @@ -56,12 +56,30 @@ class MiddlewareExecutionGenerator( } else { writer.write( "let builder = \$N<\$N, \$N, \$N, \$N>()", - ClientRuntimeTypes.Middleware.OrchestratorBuilder, + ClientRuntimeTypes.Core.OrchestratorBuilder, inputShape, outputShape, SmithyHTTPAPITypes.SdkHttpRequest, SmithyHTTPAPITypes.HttpResponse, ) + writer.write( + """ + config.interceptorProviders.forEach { provider in + builder.interceptors.add(provider.create()) + } + """.trimIndent() + ) + // Swift can't infer the generic arguments to `create` for some reason + writer.write( + """ + config.httpInterceptorProviders.forEach { provider in + let i: any HttpInterceptor<${'$'}N, ${'$'}N> = provider.create() + builder.interceptors.add(i) + } + """.trimIndent(), + inputShape, + outputShape, + ) } renderMiddlewares(ctx, op, operationStackName) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt index daea14718..0c8c6eb8f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt @@ -88,6 +88,11 @@ object ClientRuntimeTypes { val DefaultTelemetry = runtimeSymbol("DefaultTelemetry", SwiftDeclaration.ENUM) val splitHeaderListValues = runtimeSymbol("splitHeaderListValues", SwiftDeclaration.FUNC) val splitHttpDateHeaderListValues = runtimeSymbol("splitHttpDateHeaderListValues", SwiftDeclaration.FUNC) + val OrchestratorBuilder = runtimeSymbol("OrchestratorBuilder", SwiftDeclaration.CLASS) + val InterceptorProviders = runtimeSymbolWithoutNamespace("[ClientRuntime.InterceptorProvider]") + val InterceptorProvider = runtimeSymbol("InterceptorProvider", SwiftDeclaration.PROTOCOL) + val HttpInterceptorProviders = runtimeSymbolWithoutNamespace("[ClientRuntime.HttpInterceptorProvider]") + val HttpInterceptorProvider = runtimeSymbol("HttpInterceptorProvider", SwiftDeclaration.PROTOCOL) } } @@ -97,3 +102,10 @@ private fun runtimeSymbol(name: String, declaration: SwiftDeclaration? = null): SwiftDependency.CLIENT_RUNTIME, null, ) + +private fun runtimeSymbolWithoutNamespace(name: String, declaration: SwiftDeclaration? = null): Symbol = SwiftSymbol.make( + name, + declaration, + null, + null, +) diff --git a/smithy-swift-codegen/src/test/kotlin/HttpProtocolClientGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/HttpProtocolClientGeneratorTests.kt index a89ef4a3b..434b0750e 100644 --- a/smithy-swift-codegen/src/test/kotlin/HttpProtocolClientGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/HttpProtocolClientGeneratorTests.kt @@ -53,7 +53,11 @@ extension RestJsonProtocolClient { public var authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver - private init(_ telemetryProvider: ClientRuntime.TelemetryProvider, _ retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions, _ clientLogMode: ClientRuntime.ClientLogMode, _ endpoint: Swift.String?, _ idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator, _ httpClientEngine: SmithyHTTPAPI.HTTPClient, _ httpClientConfiguration: ClientRuntime.HttpClientConfiguration, _ authSchemes: SmithyHTTPAuthAPI.AuthSchemes?, _ authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver) { + public private(set) var interceptorProviders: [ClientRuntime.InterceptorProvider] + + public private(set) var httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider] + + private init(_ telemetryProvider: ClientRuntime.TelemetryProvider, _ retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions, _ clientLogMode: ClientRuntime.ClientLogMode, _ endpoint: Swift.String?, _ idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator, _ httpClientEngine: SmithyHTTPAPI.HTTPClient, _ httpClientConfiguration: ClientRuntime.HttpClientConfiguration, _ authSchemes: SmithyHTTPAuthAPI.AuthSchemes?, _ authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver, _ interceptorProviders: [ClientRuntime.InterceptorProvider], _ httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider]) { self.telemetryProvider = telemetryProvider self.retryStrategyOptions = retryStrategyOptions self.clientLogMode = clientLogMode @@ -63,19 +67,29 @@ extension RestJsonProtocolClient { self.httpClientConfiguration = httpClientConfiguration self.authSchemes = authSchemes self.authSchemeResolver = authSchemeResolver + self.interceptorProviders = interceptorProviders + self.httpInterceptorProviders = httpInterceptorProviders } - public convenience init(telemetryProvider: ClientRuntime.TelemetryProvider? = nil, retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions? = nil, clientLogMode: ClientRuntime.ClientLogMode? = nil, endpoint: Swift.String? = nil, idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator? = nil, httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil, httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil, authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil) throws { - self.init(telemetryProvider ?? ClientRuntime.DefaultTelemetry.provider, retryStrategyOptions ?? ClientRuntime.ClientConfigurationDefaults.defaultRetryStrategyOptions, clientLogMode ?? ClientRuntime.ClientConfigurationDefaults.defaultClientLogMode, endpoint, idempotencyTokenGenerator ?? ClientRuntime.ClientConfigurationDefaults.defaultIdempotencyTokenGenerator, httpClientEngine ?? ClientRuntime.ClientConfigurationDefaults.makeClient(httpClientConfiguration: httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration), httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration, authSchemes, authSchemeResolver ?? ClientRuntime.ClientConfigurationDefaults.defaultAuthSchemeResolver) + public convenience init(telemetryProvider: ClientRuntime.TelemetryProvider? = nil, retryStrategyOptions: SmithyRetriesAPI.RetryStrategyOptions? = nil, clientLogMode: ClientRuntime.ClientLogMode? = nil, endpoint: Swift.String? = nil, idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator? = nil, httpClientEngine: SmithyHTTPAPI.HTTPClient? = nil, httpClientConfiguration: ClientRuntime.HttpClientConfiguration? = nil, authSchemes: SmithyHTTPAuthAPI.AuthSchemes? = nil, authSchemeResolver: SmithyHTTPAuthAPI.AuthSchemeResolver? = nil, interceptorProviders: [ClientRuntime.InterceptorProvider]? = nil, httpInterceptorProviders: [ClientRuntime.HttpInterceptorProvider]? = nil) throws { + self.init(telemetryProvider ?? ClientRuntime.DefaultTelemetry.provider, retryStrategyOptions ?? ClientRuntime.ClientConfigurationDefaults.defaultRetryStrategyOptions, clientLogMode ?? ClientRuntime.ClientConfigurationDefaults.defaultClientLogMode, endpoint, idempotencyTokenGenerator ?? ClientRuntime.ClientConfigurationDefaults.defaultIdempotencyTokenGenerator, httpClientEngine ?? ClientRuntime.ClientConfigurationDefaults.makeClient(httpClientConfiguration: httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration), httpClientConfiguration ?? ClientRuntime.ClientConfigurationDefaults.defaultHttpClientConfiguration, authSchemes, authSchemeResolver ?? ClientRuntime.ClientConfigurationDefaults.defaultAuthSchemeResolver, interceptorProviders ?? [], httpInterceptorProviders ?? []) } public convenience required init() async throws { - try await self.init(telemetryProvider: nil, retryStrategyOptions: nil, clientLogMode: nil, endpoint: nil, idempotencyTokenGenerator: nil, httpClientEngine: nil, httpClientConfiguration: nil, authSchemes: nil, authSchemeResolver: nil) + try await self.init(telemetryProvider: nil, retryStrategyOptions: nil, clientLogMode: nil, endpoint: nil, idempotencyTokenGenerator: nil, httpClientEngine: nil, httpClientConfiguration: nil, authSchemes: nil, authSchemeResolver: nil, interceptorProviders: nil, httpInterceptorProviders: nil) } public var partitionID: String? { return "" } + public func addInterceptorProvider(_ provider: ClientRuntime.InterceptorProvider) { + self.interceptorProviders.append(provider) + } + + public func addInterceptorProvider(_ provider: ClientRuntime.HttpInterceptorProvider) { + self.httpInterceptorProviders.append(provider) + } + } public static func builder() -> ClientRuntime.ClientBuilder {