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 } } } @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, with 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() ) } ) } } 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 _ReceiveOnTriggerReducer< Parent: Reducer, TriggerAction, Value >: 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 @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, .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) } } }