Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add operation orchestrator #694

Merged
merged 5 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Sources/ClientRuntime/Idempotency/IdempotencyTokenMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,30 @@ public struct IdempotencyTokenMiddleware<OperationStackInput, OperationStackOutp
input: MInput,
next: H) async throws -> MOutput
where H: Handler, Self.MInput == H.Input, Self.MOutput == H.Output, Self.Context == H.Context {
let withToken = addToken(input: input, attributes: context)
return try await next.handle(context: context, input: withToken)
}

private func addToken(input: OperationStackInput, attributes: HttpContext) -> OperationStackInput {
var copiedInput = input
if input[keyPath: keyPath] == nil {
let idempotencyTokenGenerator = context.getIdempotencyTokenGenerator()
let idempotencyTokenGenerator = attributes.getIdempotencyTokenGenerator()
copiedInput[keyPath: keyPath] = idempotencyTokenGenerator.generateToken()
}
return try await next.handle(context: context, input: copiedInput)
return copiedInput
}

public typealias MInput = OperationStackInput
public typealias MOutput = OperationOutput<OperationStackOutput>
public typealias Context = HttpContext
}

extension IdempotencyTokenMiddleware: HttpInterceptor {
public typealias InputType = OperationStackInput
public typealias OutputType = OperationStackOutput

public func modifyBeforeSerialization(context: some MutableInput<InputType, AttributesType>) async throws {
let withToken = addToken(input: context.getInput(), attributes: context.getAttributes())
context.updateInput(updated: withToken)
}
}
130 changes: 91 additions & 39 deletions Sources/ClientRuntime/Interceptor/AnyInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,41 @@
/// In order to have multiple interceptors hooked into a single operation, we
/// need a concrete type, not a protocol. This stores references to the closures
/// of interceptor implementations and delegates to them for each interceptor hook.
internal struct AnyInterceptor<InputType, OutputType, RequestType, ResponseType, AttributesType: HasAttributes> {
///
/// This doesn't conform to Interceptor because the stored closures accept a concrete
/// DefaultInterceptorContext, not the boxed `some InterceptorContext`, which can't be
/// used in closure types.
internal struct AnyInterceptor<
InputType,
OutputType,
RequestType: RequestMessage,
ResponseType: ResponseMessage,
AttributesType: HasAttributes
> {
internal typealias InterceptorContextType = DefaultInterceptorContext<
InputType, OutputType, RequestType, ResponseType, AttributesType
>
internal typealias InterceptorFn = (InterceptorContextType) async throws -> Void

private var readBeforeExecution: InterceptorFn
private var modifyBeforeSerialization: InterceptorFn
private var readBeforeSerialization: InterceptorFn
private var readAfterSerialization: InterceptorFn
private var modifyBeforeRetryLoop: InterceptorFn
private var readBeforeAttempt: InterceptorFn
private var modifyBeforeSigning: InterceptorFn
private var readBeforeSigning: InterceptorFn
private var readAfterSigning: InterceptorFn
private var modifyBeforeTransmit: InterceptorFn
private var readBeforeTransmit: InterceptorFn
private var readAfterTransmit: InterceptorFn
private var modifyBeforeDeserialization: InterceptorFn
private var readBeforeDeserialization: InterceptorFn
private var readAfterDeserialization: InterceptorFn
private var modifyBeforeAttemptCompletion: InterceptorFn
private var readAfterAttempt: InterceptorFn
private var modifyBeforeCompletion: InterceptorFn
private var readAfterExecution: InterceptorFn
private var readBeforeExecution: InterceptorFn?
private var modifyBeforeSerialization: InterceptorFn?
private var readBeforeSerialization: InterceptorFn?
private var readAfterSerialization: InterceptorFn?
private var modifyBeforeRetryLoop: InterceptorFn?
private var readBeforeAttempt: InterceptorFn?
private var modifyBeforeSigning: InterceptorFn?
private var readBeforeSigning: InterceptorFn?
private var readAfterSigning: InterceptorFn?
private var modifyBeforeTransmit: InterceptorFn?
private var readBeforeTransmit: InterceptorFn?
private var readAfterTransmit: InterceptorFn?
private var modifyBeforeDeserialization: InterceptorFn?
private var readBeforeDeserialization: InterceptorFn?
private var readAfterDeserialization: InterceptorFn?
private var modifyBeforeAttemptCompletion: InterceptorFn?
private var readAfterAttempt: InterceptorFn?
private var modifyBeforeCompletion: InterceptorFn?
private var readAfterExecution: InterceptorFn?

internal init<I: Interceptor>(interceptor: I)
where
Expand Down Expand Up @@ -64,79 +74,121 @@ internal struct AnyInterceptor<InputType, OutputType, RequestType, ResponseType,
self.readAfterExecution = interceptor.readAfterExecution(context:)
}

internal init(
readBeforeExecution: InterceptorFn? = nil,
modifyBeforeSerialization: InterceptorFn? = nil,
readBeforeSerialization: InterceptorFn? = nil,
readAfterSerialization: InterceptorFn? = nil,
modifyBeforeRetryLoop: InterceptorFn? = nil,
readBeforeAttempt: InterceptorFn? = nil,
modifyBeforeSigning: InterceptorFn? = nil,
readBeforeSigning: InterceptorFn? = nil,
readAfterSigning: InterceptorFn? = nil,
modifyBeforeTransmit: InterceptorFn? = nil,
readBeforeTransmit: InterceptorFn? = nil,
readAfterTransmit: InterceptorFn? = nil,
modifyBeforeDeserialization: InterceptorFn? = nil,
readBeforeDeserialization: InterceptorFn? = nil,
readAfterDeserialization: InterceptorFn? = nil,
modifyBeforeAttemptCompletion: InterceptorFn? = nil,
readAfterAttempt: InterceptorFn? = nil,
modifyBeforeCompletion: InterceptorFn? = nil,
readAfterExecution: InterceptorFn? = nil
) {
self.readBeforeExecution = readBeforeExecution
self.modifyBeforeSerialization = modifyBeforeSerialization
self.readBeforeSerialization = readBeforeSerialization
self.readAfterSerialization = readAfterSerialization
self.modifyBeforeRetryLoop = modifyBeforeRetryLoop
self.readBeforeAttempt = readBeforeAttempt
self.modifyBeforeSigning = modifyBeforeSigning
self.readBeforeSigning = readBeforeSigning
self.readAfterSigning = readAfterSigning
self.modifyBeforeTransmit = modifyBeforeTransmit
self.readBeforeTransmit = readBeforeTransmit
self.readAfterTransmit = readAfterTransmit
self.modifyBeforeDeserialization = modifyBeforeDeserialization
self.readBeforeDeserialization = readBeforeDeserialization
self.readAfterDeserialization = readAfterDeserialization
self.modifyBeforeAttemptCompletion = modifyBeforeAttemptCompletion
self.readAfterAttempt = readAfterAttempt
self.modifyBeforeCompletion = modifyBeforeCompletion
self.readAfterExecution = readAfterExecution
}

internal func readBeforeExecution(context: InterceptorContextType) async throws {
try await self.readBeforeExecution(context)
try await self.readBeforeExecution?(context)
}

internal func modifyBeforeSerialization(context: InterceptorContextType) async throws {
try await self.modifyBeforeSerialization(context)
try await self.modifyBeforeSerialization?(context)
}

internal func readBeforeSerialization(context: InterceptorContextType) async throws {
try await self.readBeforeSerialization(context)
try await self.readBeforeSerialization?(context)
}

internal func readAfterSerialization(context: InterceptorContextType) async throws {
try await self.readAfterSerialization(context)
try await self.readAfterSerialization?(context)
}

internal func modifyBeforeRetryLoop(context: InterceptorContextType) async throws {
try await self.modifyBeforeRetryLoop(context)
try await self.modifyBeforeRetryLoop?(context)
}

internal func readBeforeAttempt(context: InterceptorContextType) async throws {
try await self.readBeforeAttempt(context)
try await self.readBeforeAttempt?(context)
}

internal func modifyBeforeSigning(context: InterceptorContextType) async throws {
try await self.modifyBeforeSigning(context)
try await self.modifyBeforeSigning?(context)
}

internal func readBeforeSigning(context: InterceptorContextType) async throws {
try await self.readBeforeSigning(context)
try await self.readBeforeSigning?(context)
}

internal func readAfterSigning(context: InterceptorContextType) async throws {
try await self.readAfterSigning(context)
try await self.readAfterSigning?(context)
}

internal func modifyBeforeTransmit(context: InterceptorContextType) async throws {
try await self.modifyBeforeTransmit(context)
try await self.modifyBeforeTransmit?(context)
}

internal func readBeforeTransmit(context: InterceptorContextType) async throws {
try await self.readBeforeTransmit(context)
try await self.readBeforeTransmit?(context)
}

internal func readAfterTransmit(context: InterceptorContextType) async throws {
try await self.readAfterTransmit(context)
try await self.readAfterTransmit?(context)
}

internal func modifyBeforeDeserialization(context: InterceptorContextType) async throws {
try await self.modifyBeforeDeserialization(context)
try await self.modifyBeforeDeserialization?(context)
}

internal func readBeforeDeserialization(context: InterceptorContextType) async throws {
try await self.readBeforeDeserialization(context)
try await self.readBeforeDeserialization?(context)
}

internal func readAfterDeserialization(context: InterceptorContextType) async throws {
try await self.readAfterDeserialization(context)
try await self.readAfterDeserialization?(context)
}

internal func modifyBeforeAttemptCompletion(context: InterceptorContextType) async throws {
try await self.modifyBeforeAttemptCompletion(context)
try await self.modifyBeforeAttemptCompletion?(context)
}

internal func readAfterAttempt(context: InterceptorContextType) async throws {
try await self.readAfterAttempt(context)
try await self.readAfterAttempt?(context)
}

internal func modifyBeforeCompletion(context: InterceptorContextType) async throws {
try await self.modifyBeforeCompletion(context)
try await self.modifyBeforeCompletion?(context)
}

internal func readAfterExecution(context: InterceptorContextType) async throws {
try await self.readAfterExecution(context)
try await self.readAfterExecution?(context)
}
}
19 changes: 12 additions & 7 deletions Sources/ClientRuntime/Interceptor/DefaultInterceptorContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
///
/// This object will be created before operation execution, and passed through each interceptor
/// hook in the execution pipeline.
public class DefaultInterceptorContext<InputType, OutputType, RequestType, ResponseType, AttributesType: HasAttributes>:
InterceptorContext {
public class DefaultInterceptorContext<
InputType,
OutputType,
RequestType: RequestMessage,
ResponseType: ResponseMessage,
AttributesType: HasAttributes
>: InterceptorContext {
private var attributes: AttributesType
private var input: InputType
private var request: RequestType?
private var response: ResponseType?
private var output: OutputType?
private var result: Result<OutputType, Error>?

public init(input: InputType, attributes: AttributesType) {
self.input = input
Expand Down Expand Up @@ -64,8 +69,8 @@ extension DefaultInterceptorContext: MutableResponse {
}

extension DefaultInterceptorContext: AfterDeserialization {
public func getOutput() -> OutputType {
self.output!
public func getResult() -> Result<OutputType, Error> {
self.result!
}
}

Expand All @@ -76,8 +81,8 @@ extension DefaultInterceptorContext: AfterAttempt {
}

extension DefaultInterceptorContext: MutableOutputAfterAttempt {
public func updateOutput(updated: OutputType) {
self.output = updated
public func updateResult(updated: Result<OutputType, Error>) {
self.result = updated
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/ClientRuntime/Interceptor/Interceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public protocol Interceptor<InputType, OutputType, RequestType, ResponseType, At
associatedtype OutputType

/// The type of the request transport messages sent by operations this interceptor can hook into.
associatedtype RequestType
associatedtype RequestType: RequestMessage

/// The type of the response transport message received by operations this interceptor can hook into.
associatedtype ResponseType
associatedtype ResponseType: ResponseMessage

/// The type of the attributes that will be available to the interceptor.
associatedtype AttributesType: HasAttributes
Expand Down
36 changes: 18 additions & 18 deletions Sources/ClientRuntime/Interceptor/InterceptorContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public protocol InterceptorContext: AnyObject {
associatedtype OutputType

/// The type of the transport message that will be transmitted by the operation being invoked.
associatedtype RequestType
associatedtype RequestType: RequestMessage

/// The type of the transport message that will be received by the operation being invoked.
associatedtype ResponseType
associatedtype ResponseType: ResponseMessage

/// The type of the attributes that will be available to all interceptors.
associatedtype AttributesType: HasAttributes
Expand Down Expand Up @@ -101,8 +101,8 @@ public protocol AfterDeserialization<InputType, OutputType, RequestType, Respons
/// - Returns: The serialized response.
func getResponse() -> ResponseType

/// - Returns: The operation output.
func getOutput() -> OutputType
/// - Returns: The operation result.
func getResult() -> Result<OutputType, Error>
}

/// Context given to interceptor hooks called after each attempt at sending the request.
Expand All @@ -115,8 +115,8 @@ public protocol AfterAttempt<InputType, OutputType, RequestType, ResponseType, A
/// - Returns: The serialized response, if one was received.
func getResponse() -> ResponseType?

/// - Returns: The operation output.
func getOutput() -> OutputType
/// - Returns: The operation result.
func getResult() -> Result<OutputType, Error>
}

/// Context given to interceptor hooks that can mutate the operation output, called after each attempt at sending the request.
Expand All @@ -131,12 +131,12 @@ public protocol MutableOutputAfterAttempt<InputType, OutputType, RequestType, Re
/// - Returns: The serialized response, if one was received.
func getResponse() -> ResponseType?

/// - Returns: The operation output.
func getOutput() -> OutputType
/// - Returns: The operation result.
func getResult() -> Result<OutputType, Error>

/// Mutates the operation output.
/// - Parameter updated: The updated output.
func updateOutput(updated: OutputType)
/// Mutates the operation result.
/// - Parameter updated: The updated result.
func updateResult(updated: Result<OutputType, Error>)
}

/// Context given to interceptor hooks called after execution.
Expand All @@ -150,8 +150,8 @@ public protocol Finalization<InputType, OutputType, RequestType, ResponseType, A
/// - Returns: The serialized response, if one was received.
func getResponse() -> ResponseType?

/// - Returns: The operation output.
func getOutput() -> OutputType
/// - Returns: The operation result.
func getResult() -> Result<OutputType, Error>
}

/// Context given to interceptor hooks that can mutate the operation output, called after execution.
Expand All @@ -167,10 +167,10 @@ public protocol MutableOutputFinalization<InputType, OutputType, RequestType, Re
/// - Returns: The serialized response, if one was received.
func getResponse() -> ResponseType?

/// - Returns: The operation output.
func getOutput() -> OutputType
/// - Returns: The operation result.
func getResult() -> Result<OutputType, Error>

/// Mutates the operation output.
/// - Parameter updated: The updated output.
func updateOutput(updated: OutputType)
/// Mutates the operation result.
/// - Parameter updated: The updated result.
func updateResult(updated: Result<OutputType, Error>)
}
Loading
Loading