diff --git a/Sources/ComposableSubscriber/ReceiveReducer.swift b/Sources/ComposableSubscriber/ReceiveReducer.swift new file mode 100644 index 0000000..37111fc --- /dev/null +++ b/Sources/ComposableSubscriber/ReceiveReducer.swift @@ -0,0 +1,125 @@ +import ComposableArchitecture + +extension Reducer { + + public func onReceive( + action toReceiveAction: CaseKeyPath, + set setAction: @escaping (inout State, V) -> Void + ) -> _ReceiveReducer { + .init( + parent: self, + receiveAction: AnyCasePath(toReceiveAction), + setAction: setAction + ) + } + + public func onReceive( + action toReceiveAction: CaseKeyPath, + set toStateKeyPath: WritableKeyPath + ) -> _ReceiveReducer { + self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:)) + } + + public func onReceive( + action toReceiveAction: CaseKeyPath, + set toStateKeyPath: WritableKeyPath + ) -> _ReceiveReducer { + self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:)) + } + + public func onReceive( + action toReceiveAction: CaseKeyPath>, + onSuccess setAction: @escaping (inout State, V) -> Void, + onFail: OnFailAction? = nil + ) -> _ReceiveReducer> { + self.onReceive(action: toReceiveAction) { state, result in + switch result { + case let .failure(error): + if let onFail { + onFail(state: &state, error: error) + } + case let .success(value): + setAction(&state, value) + } + } + } + + public func onReceive( + action toReceiveAction: CaseKeyPath>, + onSuccess toStateKeyPath: WritableKeyPath, + onFail: OnFailAction? = nil + ) -> _ReceiveReducer> { + self.onReceive(action: toReceiveAction) { state, result in + switch result { + case let .failure(error): + if let onFail { + onFail(state: &state, error: error) + } + case let .success(value): + toStateKeyPath(root: &state, value: value) + } + } + } + + public func onReceive( + action toReceiveAction: CaseKeyPath>, + onSuccess toStateKeyPath: WritableKeyPath, + onFail: OnFailAction? = nil + ) -> _ReceiveReducer> { + self.onReceive(action: toReceiveAction) { state, result in + switch result { + case let .failure(error): + if let onFail { + onFail(state: &state, error: error) + } + case let .success(value): + toStateKeyPath(root: &state, value: value) + } + } + } +} + +public enum OnFailAction { + case xctFail + case handle((inout State, Error) -> Void) + + @usableFromInline + func callAsFunction(state: inout State, error: Error) { + switch self { + case .xctFail: + XCTFail("\(error)") + case let .handle(handler): + handler(&state, error) + } + } +} + +fileprivate extension WritableKeyPath { + + func callAsFunction(root: inout Root, value: Value) { + root[keyPath: self] = value + } + +} + +public struct _ReceiveReducer: Reducer { + + @usableFromInline + let parent: Parent + + @usableFromInline + let receiveAction: AnyCasePath + + @usableFromInline + let setAction: (inout Parent.State, Value) -> Void + + public func reduce(into state: inout Parent.State, action: Parent.Action) -> Effect { + let baseEffects = parent.reduce(into: &state, action: action) + + if let value = receiveAction.extract(from: action) { + setAction(&state, value) + } + + return baseEffects + } +} diff --git a/Tests/swift-composable-subscriberTests/swift_composable_subscriberTests.swift b/Tests/swift-composable-subscriberTests/swift_composable_subscriberTests.swift index f3e5c4b..b8fa9d9 100644 --- a/Tests/swift-composable-subscriberTests/swift_composable_subscriberTests.swift +++ b/Tests/swift-composable-subscriberTests/swift_composable_subscriberTests.swift @@ -57,21 +57,15 @@ struct ReducerWithArg { @Dependency(\.numberClient) var numberClient var body: some Reducer { - Reduce { state, action in - switch action { - case let .receive(number): - state.currentNumber = number - return .none - case .task: - return .none - } - } - .subscribe( - using: \.number, - to: numberClient.numberStreamWithArg, - on: \.task, - with: \.receive - ) + + EmptyReducer() + .onReceive(action: \.receive, set: \.currentNumber) + .subscribe( + to: numberClient.numberStreamWithArg, + using: \.number, + on: \.task, + with: \.receive + ) } } @@ -84,23 +78,16 @@ struct ReducerWithTransform { @Dependency(\.numberClient) var numberClient var body: some Reducer { - Reduce { state, action in - switch action { - case let .receive(number): - state.currentNumber = number - return .none - case .task: - return .none + EmptyReducer() + .onReceive(action: \.receive, set: \.currentNumber) + .subscribe( + to: numberClient.numberStreamWithArg, + using: \.number, + on: \.task, + with: \.receive + ) { + $0 * 2 } - } - .subscribe( - using: \.number, - to: numberClient.numberStreamWithArg, - on: \.task, - with: \.receive - ) { - $0 * 2 - } } }