import ComposableArchitecture import OSLog extension Reducer { @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set setAction: @escaping (inout State, V) -> Effect ) -> _ReceiveReducer { .init( parent: self, receiveAction: { AnyCasePath(toReceiveAction).extract(from: $0) }, setAction: setAction ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set setAction: @escaping (inout State, V) -> Void ) -> _ReceiveReducer { .init( parent: self, receiveAction: { AnyCasePath(toReceiveAction).extract(from: $0) }, setAction: { state, value in setAction(&state, value) return .none } ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set toStateKeyPath: WritableKeyPath ) -> _ReceiveReducer { self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:)) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set toStateKeyPath: WritableKeyPath ) -> _ReceiveReducer { self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:)) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, onFail: OnFailAction? = nil, onSuccess setAction: @escaping (inout State, V) -> Void ) -> _ReceiveReducer> { self.onReceive(action: toReceiveAction) { state, result in switch result { case let .failure(error): if let onFail { return onFail(state: &state, error: error) } return .none case let .success(value): setAction(&state, value) return .none } } } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, set toStateKeyPath: WritableKeyPath, onFail: OnFailAction? = nil ) -> _ReceiveReducer> { self.onReceive(action: toReceiveAction) { state, result in switch result { case let .failure(error): if let onFail { return onFail(state: &state, error: error) } return .none case let .success(value): toStateKeyPath(root: &state, value: value) return .none } } } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, set toStateKeyPath: WritableKeyPath, onFail: OnFailAction? = nil ) -> _ReceiveReducer> { self.onReceive(action: toReceiveAction) { state, result in switch result { case let .failure(error): if let onFail { return onFail(state: &state, error: error) } return .none case let .success(value): toStateKeyPath(root: &state, value: value) return .none } } } } public enum OnFailAction { case fail(prefix: String? = nil, log: ((String) -> Void)? = nil) case handle((inout State, Error) -> Void) @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) @inlinable static func fail(prefix: String? = nil, logger: Logger) -> Self { .fail(prefix: prefix, log: { logger.error("\($0)") }) } @usableFromInline func callAsFunction(state: inout State, error: Error) -> Effect { switch self { case let .fail(prefix, log): if let prefix { return .fail(prefix: prefix, error: error, log: log) } else { return .fail(error: error, log: log) } case let .handle(handler): handler(&state, error) return .none } } } extension WritableKeyPath { @usableFromInline func callAsFunction(root: inout Root, value: Value) { root[keyPath: self] = value } } public struct _ReceiveReducer: Reducer { @usableFromInline let parent: Parent @usableFromInline let receiveAction: (Parent.Action) -> Value? @usableFromInline let setAction: (inout Parent.State, Value) -> Effect @usableFromInline init( parent: Parent, receiveAction: @escaping (Parent.Action) -> Value?, setAction: @escaping (inout Parent.State, Value) -> Effect ) { self.parent = parent self.receiveAction = receiveAction self.setAction = setAction } @inlinable public func reduce( into state: inout Parent.State, action: Parent.Action ) -> Effect { let baseEffects = parent.reduce(into: &state, action: action) var setEffects = Effect.none if let value = receiveAction(action) { setEffects = setAction(&state, value) } return .merge(baseEffects, setEffects) } } public struct _OnRecieveReducer: Reducer { @usableFromInline let parent: Parent @usableFromInline let triggerAction: (Parent.Action) -> TriggerAction? @usableFromInline let toReceiveAction: (TaskResult) -> Parent.Action @usableFromInline let resultHandler: () async throws -> Value @usableFromInline init( parent: Parent, triggerAction: @escaping (Parent.Action) -> TriggerAction?, toReceiveAction: @escaping (TaskResult) -> Parent.Action, resultHandler: @escaping () -> Value ) { self.parent = parent self.triggerAction = triggerAction self.toReceiveAction = toReceiveAction self.resultHandler = resultHandler } @inlinable public func reduce( into state: inout Parent.State, action: Parent.Action) -> Effect { let baseEffects = parent.reduce(into: &state, action: action) guard triggerAction(action) != nil else { return baseEffects } return .merge( baseEffects, .run { send in await send(toReceiveAction( TaskResult { try await resultHandler() } )) } ) } } public struct ReceiveReducer: Reducer { @usableFromInline let toResult: (Action) -> TaskResult? @usableFromInline let onFail: OnFailAction? @usableFromInline let onSuccess: (inout State, Action.ReceiveAction) -> Effect @inlinable init( internal toResult: @escaping (Action) -> TaskResult?, onFail: OnFailAction?, onSuccess: @escaping(inout State, Action.ReceiveAction) -> Effect ) { self.toResult = toResult self.onFail = onFail self.onSuccess = onSuccess } @inlinable public init( onFail: OnFailAction? = nil, onSuccess: @escaping (inout State, Action.ReceiveAction) -> Effect ) { self.init( internal: { AnyCasePath(unsafe: Action.receive).extract(from: $0) }, onFail: onFail, onSuccess: onSuccess ) } @inlinable public func reduce(into state: inout State, action: Action) -> Effect { guard let result = toResult(action) else { return .none } switch result { case let .failure(error): guard let onFail else { return .none } return onFail(state: &state, error: error) case let .success(value): return onSuccess(&state, value) } } }