feat: Refactors / moves into individual files, wip.

This commit is contained in:
2024-05-30 19:52:15 -04:00
parent 11ddfb04c3
commit bd0f90894f
6 changed files with 202 additions and 104 deletions

View File

@@ -1,33 +1,66 @@
import ComposableArchitecture
import OSLog
extension Effect where Action: ReceiveAction {
extension Effect {
public static func receive(
_ operation: @escaping () async throws -> Action.ReceiveAction
@usableFromInline
static func receive<T>(
_ casePath: AnyCasePath<Action, TaskResult<T>>,
_ operation: @escaping @Sendable () async throws -> T
) -> Self {
.run { send in
await send(.receive(
await send(casePath.embed(
TaskResult { try await operation() }
))
}
}
public static func receive<T>(
_ operation: @escaping () async throws -> T,
transform: @escaping (T) -> Action.ReceiveAction
@usableFromInline
static func receive<T, V>(
_ casePath: AnyCasePath<Action, TaskResult<V>>,
_ operation: @escaping @Sendable () async throws -> T,
_ transform: @escaping @Sendable (T) -> V
) -> Self {
.run { send in
await send(.receive(
await send(casePath.embed(
TaskResult { try await operation() }
.map(transform)
))
}
}
@inlinable
public static func receive<T>(
action toResult: CaseKeyPath<Action, TaskResult<T>>,
operation: @escaping @Sendable () async throws -> T
) -> Self {
.receive(AnyCasePath(toResult), operation)
}
@inlinable
public static func receive<T, V>(
action toResult: CaseKeyPath<Action, TaskResult<V>>,
operation: @escaping @Sendable () async throws -> T,
transform: @escaping @Sendable (T) -> V
) -> Self {
.receive(AnyCasePath(toResult), operation, transform)
}
}
extension Effect where Action: ReceiveAction {
@inlinable
public static func receive<T>(
_ operation: @escaping @Sendable () async throws -> T,
transform: @escaping @Sendable (T) -> Action.ReceiveAction
) -> Self {
.receive(AnyCasePath(unsafe: Action.receive), operation, transform)
}
@inlinable
public static func receive<T>(
_ toReceiveAction: CaseKeyPath<Action.ReceiveAction, T>,
_ operation: @escaping () async throws -> T
_ operation: @escaping @Sendable () async throws -> T
) -> Self {
return .receive(operation) {
AnyCasePath(toReceiveAction).embed($0)

View File

@@ -0,0 +1,32 @@
import ComposableArchitecture
import Foundation
@usableFromInline
enum SetAction<State, Action, Value> {
case operation(f: (inout State, Value) -> Effect<Action>)
case keyPath(WritableKeyPath<State, Value>, effect: Effect<Action>)
case optionalKeyPath(WritableKeyPath<State, Value?>, effect: Effect<Action>)
@usableFromInline
func callAsFunction(state: inout State, value: Value) -> Effect<Action> {
switch self {
case let .operation(f: f):
return f(&state, value)
case let .keyPath(keyPath, effect):
state[keyPath: keyPath] = value
return effect
case let .optionalKeyPath(keyPath, effect):
state[keyPath: keyPath] = value
return effect
}
}
@usableFromInline
static func operation(_ f: @escaping (inout State, Value) -> Void) -> Self {
.operation(f: { state, value in
f(&state, value)
return .none
})
}
}

View File

@@ -4,13 +4,7 @@ import OSLog
public enum OnFailAction<State, Action> {
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)") })
}
case operation((inout State, Error) -> Effect<Action>)
@usableFromInline
func callAsFunction(state: inout State, error: Error) -> Effect<Action> {
@@ -21,10 +15,45 @@ public enum OnFailAction<State, Action> {
} else {
return .fail(error: error, log: log)
}
case let .handle(handler):
handler(&state, error)
return .none
case let .operation(handler):
return handler(&state, error)
}
}
@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)") })
}
@inlinable
public static func set(
_ operation: @escaping @Sendable (inout State, Error) -> Void
) -> Self {
.operation(
SetAction.operation(operation).callAsFunction(state:value:)
)
}
@inlinable
public static func set(
_ operation: @escaping @Sendable (inout State, Error) -> Effect<Action>
) -> Self {
.operation(
SetAction.operation(f: operation).callAsFunction(state:value:)
)
}
@inlinable
public static func set(
keyPath: WritableKeyPath<State, Error?>,
effect: Effect<Action> = .none
) -> Self {
.operation(
SetAction.optionalKeyPath(
keyPath,
effect: effect
).callAsFunction(state:value:)
)
}
}

View File

@@ -3,11 +3,11 @@ import OSLog
extension Reducer {
@inlinable
public func onReceive<V>(
@usableFromInline
func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, V>,
set setAction: @escaping (inout State, V) -> Effect<Action>
) -> _ReceiveReducer<Self, V> {
set setAction: SetAction<State, Action, V>
) -> _OnReceiveReducer<Self, V> {
.init(
parent: self,
receiveAction: { AnyCasePath(toReceiveAction).extract(from: $0) },
@@ -18,40 +18,58 @@ extension Reducer {
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, V>,
set setAction: @escaping (inout State, V) -> Void
) -> _ReceiveReducer<Self, V> {
.init(
parent: self,
receiveAction: { AnyCasePath(toReceiveAction).extract(from: $0) },
setAction: { state, value in
setAction(&state, value)
return .none
}
set setAction: @escaping (inout State, V) -> Effect<Action>
) -> _OnReceiveReducer<Self, V> {
self.onReceive(
action: toReceiveAction,
set: .operation(f: setAction)
)
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, V>,
set toStateKeyPath: WritableKeyPath<State, V>
) -> _ReceiveReducer<Self, V> {
self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:))
set setAction: @escaping (inout State, V) -> Void
) -> _OnReceiveReducer<Self, V> {
self.onReceive(
action: toReceiveAction,
set: .operation(f: { state, value in
setAction(&state, value)
return .none
})
)
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, V>,
set toStateKeyPath: WritableKeyPath<State, V?>
) -> _ReceiveReducer<Self, V> {
self.onReceive(action: toReceiveAction, set: toStateKeyPath.callAsFunction(root:value:))
set toStateKeyPath: WritableKeyPath<State, V>,
effect: Effect<Action> = .none
) -> _OnReceiveReducer<Self, V> {
self.onReceive(
action: toReceiveAction,
set: .keyPath(toStateKeyPath, effect: effect)
)
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, V>,
set toStateKeyPath: WritableKeyPath<State, V?>,
effect: Effect<Action> = .none
) -> _OnReceiveReducer<Self, V> {
self.onReceive(
action: toReceiveAction,
set: .optionalKeyPath(toStateKeyPath, effect: effect)
)
}
@usableFromInline
func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, TaskResult<V>>,
onFail: OnFailAction<State, Action>? = nil,
onSuccess setAction: @escaping (inout State, V) -> Void
) -> _ReceiveReducer<Self, TaskResult<V>> {
onSuccess setAction: SetAction<State, Action, V>
) -> _OnReceiveReducer<Self, TaskResult<V>> {
self.onReceive(action: toReceiveAction) { state, result in
switch result {
case let .failure(error):
@@ -60,50 +78,50 @@ extension Reducer {
}
return .none
case let .success(value):
setAction(&state, value)
return .none
return setAction(state: &state, value: value)
}
}
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, TaskResult<V>>,
onSuccess setAction: @escaping (inout State, V) -> Void,
onFail: OnFailAction<State, Action>? = nil
) -> _OnReceiveReducer<Self, TaskResult<V>> {
self.onReceive(
action: toReceiveAction,
onFail: onFail,
onSuccess: .operation(setAction)
)
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, TaskResult<V>>,
set toStateKeyPath: WritableKeyPath<State, V>,
onFail: OnFailAction<State, Action>? = nil
) -> _ReceiveReducer<Self, TaskResult<V>> {
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
}
}
onFail: OnFailAction<State, Action>? = nil,
effect: Effect<Action> = .none
) -> _OnReceiveReducer<Self, TaskResult<V>> {
self.onReceive(
action: toReceiveAction,
onFail: onFail,
onSuccess: .keyPath(toStateKeyPath, effect: effect)
)
}
@inlinable
public func onReceive<V>(
action toReceiveAction: CaseKeyPath<Action, TaskResult<V>>,
set toStateKeyPath: WritableKeyPath<State, V?>,
onFail: OnFailAction<State, Action>? = nil
) -> _ReceiveReducer<Self, TaskResult<V>> {
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
}
}
onFail: OnFailAction<State, Action>? = nil,
effect: Effect<Action> = .none
) -> _OnReceiveReducer<Self, TaskResult<V>> {
self.onReceive(
action: toReceiveAction,
onFail: onFail,
onSuccess: .optionalKeyPath(toStateKeyPath, effect: effect)
)
}
@inlinable
@@ -126,7 +144,7 @@ extension Reducer where Action: ReceiveAction {
@inlinable
public func receive<TriggerAction, Value>(
on triggerAction: CaseKeyPath<Action, TriggerAction>,
with embedCasePath: CaseKeyPath<Action.ReceiveAction, Value>,
case embedCasePath: CaseKeyPath<Action.ReceiveAction, Value>,
result resultHandler: @escaping @Sendable () async throws -> Value
) -> _ReceiveOnTriggerReducer<Self, TriggerAction, Action.ReceiveAction> {
.init(
@@ -142,16 +160,7 @@ extension Reducer where Action: ReceiveAction {
}
}
extension WritableKeyPath {
@usableFromInline
func callAsFunction(root: inout Root, value: Value) {
root[keyPath: self] = value
}
}
public struct _ReceiveReducer<Parent: Reducer, Value>: Reducer {
public struct _OnReceiveReducer<Parent: Reducer, Value>: Reducer {
@usableFromInline
let parent: Parent
@@ -160,13 +169,13 @@ public struct _ReceiveReducer<Parent: Reducer, Value>: Reducer {
let receiveAction: (Parent.Action) -> Value?
@usableFromInline
let setAction: (inout Parent.State, Value) -> Effect<Parent.Action>
let setAction: SetAction<Parent.State, Parent.Action, Value>
@usableFromInline
init(
parent: Parent,
receiveAction: @escaping (Parent.Action) -> Value?,
setAction: @escaping (inout Parent.State, Value) -> Effect<Parent.Action>
setAction: SetAction<Parent.State, Parent.Action, Value>
) {
self.parent = parent
self.receiveAction = receiveAction
@@ -182,7 +191,7 @@ public struct _ReceiveReducer<Parent: Reducer, Value>: Reducer {
var setEffects = Effect<Action>.none
if let value = receiveAction(action) {
setEffects = setAction(&state, value)
setEffects = setAction(state: &state, value: value)
}
return .merge(baseEffects, setEffects)

View File

@@ -286,9 +286,14 @@ extension Reducer {
/// return .none
/// }
/// }
/// .subscribe(using: \.number, to: numberFactStream, on: \.task, with: \.receive) { numberFact in
/// "\(numberFact) Appended with my custom transformation."
/// }
/// .subscribe(
/// to: numberFactStream,
/// using: \.number,
/// on: \.task,
/// with: \.receiveNumberFact
/// ) { numberFact in
/// "\(numberFact) Appended with my custom transformation."
/// }
/// }
/// }
/// ```
@@ -513,6 +518,7 @@ public struct _SubscribeReducer<Parent: Reducer, TriggerAction, StreamElement, V
@usableFromInline
let transform: (StreamElement) -> Value
@usableFromInline
init(
parent: Parent,
on triggerAction: CaseKeyPath<Parent.Action, TriggerAction>,

View File

@@ -127,29 +127,16 @@ struct ReducerWithReceiveAction {
return .none
}
}
.receive(on: \.task, with: \.currentNumber) {
.receive(on: \.task, case: \.currentNumber) {
try await numberClient.currentNumber()
}
// Reduce<State, Action> { state, action in
// switch action {
//
// case .receive:
// return .none
//
// case .task:
// return .receive(\.currentNumber) {
// try await numberClient.currentNumber()
// }
// }
// }
}
}
@MainActor
final class TCAExtrasTests: XCTestCase {
@MainActor
func testSubscribeWithArg() async throws {
let store = TestStore(
initialState: ReducerWithArg.State(number: 19),
@@ -167,6 +154,7 @@ final class TCAExtrasTests: XCTestCase {
await store.finish()
}
@MainActor
func testSubscribeWithArgAndTransform() async throws {
let store = TestStore(
initialState: ReducerWithTransform.State(number: 10),
@@ -184,6 +172,7 @@ final class TCAExtrasTests: XCTestCase {
await store.finish()
}
@MainActor
func testReceiveAction() async throws {
let store = TestStore(
initialState: ReducerWithReceiveAction.State(number: 19),
@@ -193,7 +182,7 @@ final class TCAExtrasTests: XCTestCase {
}
let task = await store.send(.task)
await store.receive(\.receive) {
await store.receive(\.receive.success.currentNumber) {
$0.currentNumber = 69420
}