import ComposableArchitecture import OSLog extension Reducer { @usableFromInline func onReceive( action toReceiveAction: CaseKeyPath, set setAction: SetAction ) -> _OnReceiveReducer { .init( parent: self, receiveAction: { AnyCasePath(toReceiveAction).extract(from: $0) }, setAction: setAction ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set setAction: @escaping (inout State, V) -> Effect ) -> _OnReceiveReducer { self.onReceive( action: toReceiveAction, set: .operation(f: setAction) ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set setAction: @escaping (inout State, V) -> Void ) -> _OnReceiveReducer { self.onReceive( action: toReceiveAction, set: .operation(f: { state, value in setAction(&state, value) return .none }) ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set toStateKeyPath: WritableKeyPath, effect: Effect = .none ) -> _OnReceiveReducer { self.onReceive( action: toReceiveAction, set: .keyPath(toStateKeyPath, effect: effect) ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath, set toStateKeyPath: WritableKeyPath, effect: Effect = .none ) -> _OnReceiveReducer { self.onReceive( action: toReceiveAction, set: .optionalKeyPath(toStateKeyPath, effect: effect) ) } @usableFromInline func onReceive( action toReceiveAction: CaseKeyPath>, onFail: OnFailAction? = nil, onSuccess setAction: SetAction ) -> _OnReceiveReducer> { 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): return setAction(state: &state, value: value) } } } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, onSuccess setAction: @escaping (inout State, V) -> Void, onFail: OnFailAction? = nil ) -> _OnReceiveReducer> { self.onReceive( action: toReceiveAction, onFail: onFail, onSuccess: .operation(setAction) ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, set toStateKeyPath: WritableKeyPath, onFail: OnFailAction? = nil, effect: Effect = .none ) -> _OnReceiveReducer> { self.onReceive( action: toReceiveAction, onFail: onFail, onSuccess: .keyPath(toStateKeyPath, effect: effect) ) } @inlinable public func onReceive( action toReceiveAction: CaseKeyPath>, set toStateKeyPath: WritableKeyPath, onFail: OnFailAction? = nil, effect: Effect = .none ) -> _OnReceiveReducer> { self.onReceive( action: toReceiveAction, onFail: onFail, onSuccess: .optionalKeyPath(toStateKeyPath, effect: effect) ) } @inlinable public func receive( on triggerAction: CaseKeyPath, with receiveAction: CaseKeyPath>, result resultHandler: @escaping @Sendable () async throws -> Value ) -> _ReceiveOnTriggerReducer { .init( parent: self, triggerAction: { AnyCasePath(triggerAction).extract(from: $0) }, toReceiveAction: { AnyCasePath(receiveAction).embed($0) }, resultHandler: resultHandler ) } } extension Reducer where Action: ReceiveAction { @inlinable public func receive( on triggerAction: CaseKeyPath, case embedCasePath: CaseKeyPath, result resultHandler: @escaping @Sendable () async throws -> Value ) -> _ReceiveOnTriggerReducer { .init( parent: self, triggerAction: { AnyCasePath(triggerAction).extract(from: $0) }, toReceiveAction: { AnyCasePath(unsafe: Action.receive).embed($0) }, resultHandler: { try await AnyCasePath(embedCasePath).embed( resultHandler() ) } ) } } public struct _OnReceiveReducer: Reducer { @usableFromInline let parent: Parent @usableFromInline let receiveAction: (Parent.Action) -> Value? @usableFromInline let setAction: SetAction @usableFromInline init( parent: Parent, receiveAction: @escaping (Parent.Action) -> Value?, setAction: SetAction ) { 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) guard let value = receiveAction(action) else { return baseEffects } return .merge( baseEffects, setAction(state: &state, value: value) ) } } public struct _ReceiveOnTriggerReducer< Parent: Reducer, TriggerAction, Value >: Reducer { @usableFromInline let parent: Parent @usableFromInline let triggerAction: @Sendable (Parent.Action) -> TriggerAction? @usableFromInline let toReceiveAction: @Sendable (TaskResult) -> Parent.Action @usableFromInline let resultHandler: @Sendable () async throws -> Value @usableFromInline init( parent: Parent, triggerAction: @escaping @Sendable (Parent.Action) -> TriggerAction?, toReceiveAction: @escaping @Sendable (TaskResult) -> Parent.Action, resultHandler: @escaping @Sendable () async throws -> 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, .receive(operation: .init(embed: toReceiveAction, operation: 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) } } }