import Foundation public protocol Chain { associatedtype Input associatedtype Output func callLogic(_ input: Input, callbackManagers: [ChainCallbackManager]) async throws -> Output func parseOutput(_ output: Output) -> String } public extension Chain { func run(_ input: Input, callbackManagers: [ChainCallbackManager] = []) async throws -> String { let output = try await call(input, callbackManagers: callbackManagers) return parseOutput(output) } func call(_ input: Input, callbackManagers: [ChainCallbackManager]) async throws -> Output { for callbackManager in callbackManagers { callbackManager.onChainStart(type: Self.self, input: input) } return try await callLogic(input, callbackManagers: callbackManagers) } } public struct SimpleChain: Chain { let block: (Input) async throws -> Output let parseOutputBlock: (Output) -> String public init( block: @escaping (Input) async throws -> Output, parseOutput: @escaping (Output) -> String = { String(describing: $0) } ) { self.block = block parseOutputBlock = parseOutput } public func callLogic( _ input: Input, callbackManagers: [ChainCallbackManager] ) async throws -> Output { return try await block(input) } public func parseOutput(_ output: Output) -> String { return parseOutputBlock(output) } } public struct ConnectedChain: Chain where B.Input == A.Output { public typealias Input = A.Input public typealias Output = (B.Output, A.Output) public let chainA: A public let chainB: B public func callLogic( _ input: Input, callbackManagers: [ChainCallbackManager] = [] ) async throws -> Output { let a = try await chainA.call(input, callbackManagers: callbackManagers) let b = try await chainB.call(a, callbackManagers: callbackManagers) return (b, a) } public func parseOutput(_ output: Output) -> String { chainB.parseOutput(output.0) } } public struct PairedChain: Chain { public typealias Input = (A.Input, B.Input) public typealias Output = (A.Output, B.Output) public let chainA: A public let chainB: B public func callLogic( _ input: Input, callbackManagers: [ChainCallbackManager] = [] ) async throws -> Output { async let a = chainA.call(input.0, callbackManagers: callbackManagers) async let b = chainB.call(input.1, callbackManagers: callbackManagers) return try await (a, b) } public func parseOutput(_ output: (A.Output, B.Output)) -> String { [chainA.parseOutput(output.0), chainB.parseOutput(output.1)].joined(separator: "\n") } } public struct MappedChain: Chain { public typealias Input = A.Input public typealias Output = NewOutput public let chain: A public let map: (A.Output) -> NewOutput public func callLogic( _ input: Input, callbackManagers: [ChainCallbackManager] ) async throws -> Output { let output = try await chain.call(input, callbackManagers: callbackManagers) return map(output) } public func parseOutput(_ output: Output) -> String { String(describing: output) } } public extension Chain { func pair(with another: C) -> PairedChain { PairedChain(chainA: self, chainB: another) } func chain(to another: C) -> ConnectedChain { ConnectedChain(chainA: self, chainB: another) } func map(_ map: @escaping (Output) -> NewOutput) -> MappedChain { MappedChain(chain: self, map: map) } }