diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Reducer.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Reducer.xcscheme
index 46117df..c81fbb9 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Reducer.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Reducer.xcscheme
@@ -77,6 +77,16 @@
ReferencedContainer = "container:">
+
+
+
+
State
@@ -29,6 +30,8 @@ public extension Reduce {
func start() async throws { }
+ func error(_ error: any Error, action: Action) async { }
+
func shouldCancel(_ current: Action, _ upcoming: Action) -> Bool {
false
}
@@ -77,6 +80,7 @@ open class ProxyReduce: Reduce {
public var initialState: State
private let _start: ((@escaping (Mutation) -> Void) async throws -> Void)?
+ private let _error: ((@escaping (Mutation) -> Void, any Error, Action) async -> Void)?
private let _mutate: ((State, Action, @escaping (Mutation) -> Void) async throws -> Void)?
private let _reduce: ((State, Mutation) -> State)?
private let _shouldCancel: ((Action, Action) -> Bool)?
@@ -85,6 +89,7 @@ open class ProxyReduce: Reduce {
public init(
initialState: State,
start: ((@escaping (Mutation) -> Void) async throws -> Void)? = nil,
+ error: ((@escaping (Mutation) -> Void, any Error, Action) async -> Void)? = nil,
mutate: ((State, Action, @escaping (Mutation) -> Void) async throws -> Void)? = nil,
reduce: ((State, Mutation) -> State)? = nil,
shouldCancel: ((Action, Action) -> Bool)? = nil
@@ -92,6 +97,7 @@ open class ProxyReduce: Reduce {
self.initialState = initialState
self._start = start
+ self._error = error
self._mutate = mutate
self._reduce = reduce
self._shouldCancel = shouldCancel
@@ -103,6 +109,9 @@ open class ProxyReduce: Reduce {
start: { _ in
try await reduce.start()
},
+ error: { _, error, action in
+ await reduce.error(error, action: action)
+ },
mutate: { _, action, _ in
try await reduce.mutate(action: action)
},
@@ -126,6 +135,10 @@ open class ProxyReduce: Reduce {
try await _start?({ [weak self] mutation in self?.mutator?(mutation) })
}
+ public func error(_ error: any Error, action: Action) async {
+ await _error?({ [weak self] mutation in self?.mutator?(mutation) }, error, action)
+ }
+
open func mutate(action: Action) async throws {
try await _mutate?(currentState, action, { [weak self] mutation in self?.mutator?(mutation) })
}
diff --git a/Sources/Reducer/Reducer.swift b/Sources/Reducer/Reducer.swift
index ed0c85a..268c769 100644
--- a/Sources/Reducer/Reducer.swift
+++ b/Sources/Reducer/Reducer.swift
@@ -115,7 +115,11 @@ open class Reducer: ObservableObject, Mutable {
action,
with: Task { @MainActor in
// Mutate state from action.
- try? await reduce.mutate(action: action)
+ do {
+ try await reduce.mutate(action: action)
+ } catch {
+ await reduce.error(error, action: action)
+ }
}
))
}
diff --git a/Tests/ReducerTests/Model/ErrorReduce.swift b/Tests/ReducerTests/Model/ErrorReduce.swift
new file mode 100644
index 0000000..119a962
--- /dev/null
+++ b/Tests/ReducerTests/Model/ErrorReduce.swift
@@ -0,0 +1,54 @@
+//
+// ErrorReduce.swift
+// Reducer
+//
+// Created by JSilver on 11/3/24.
+//
+
+import Foundation
+import Reducer
+
+@Reduce
+@MainActor
+class ErrorReduce {
+ enum Action {
+ case occurError(any Error)
+ }
+
+ enum Mutation {
+ case setCount(Int)
+ }
+
+ struct State {
+ var count: Int
+ }
+
+ let initialState: State
+
+ init() {
+ self.initialState = State(
+ count: 0
+ )
+ }
+
+ func error(_ error: any Error, action: Action) async {
+ mutate(.setCount(currentState.count + 1))
+ }
+
+ func mutate(action: Action) async throws {
+ switch action {
+ case let .occurError(error):
+ throw error
+ }
+ }
+
+ func reduce(state: State, mutation: Mutation) -> State {
+ var state = state
+
+ switch mutation {
+ case let .setCount(count):
+ state.count = count
+ return state
+ }
+ }
+}
diff --git a/Tests/ReducerTests/Model/TestError.swift b/Tests/ReducerTests/Model/TestError.swift
new file mode 100644
index 0000000..421c4c4
--- /dev/null
+++ b/Tests/ReducerTests/Model/TestError.swift
@@ -0,0 +1,16 @@
+//
+// TestError.swift
+// Reducer
+//
+// Created by JSilver on 11/3/24.
+//
+
+import Foundation
+
+struct TestError: Error {
+ let description: String
+
+ init(_ description: String) {
+ self.description = description
+ }
+}
diff --git a/Tests/ReducerTests/ReducerTests.swift b/Tests/ReducerTests/ReducerTests.swift
index e1393ce..1bf1797 100644
--- a/Tests/ReducerTests/ReducerTests.swift
+++ b/Tests/ReducerTests/ReducerTests.swift
@@ -9,6 +9,7 @@ import XCTest
@testable import Reducer
import Combine
+@MainActor
final class ReducerTests: XCTestCase {
// MARK: - Property
@@ -22,7 +23,6 @@ final class ReducerTests: XCTestCase {
}
// MARK: - Test
- @MainActor
func test_that_count_increases_when_receiving_increase_action() async throws {
// Given
let reducer = Reducer(
@@ -46,7 +46,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 1, 2])
}
- @MainActor
func test_that_count_does_not_mutate_when_receiving_same_action_twice() async throws {
// Given
let reducer = Reducer(
@@ -70,7 +69,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 1])
}
- @MainActor
func test_that_all_action_cancelled_when_reducer_deinit() async throws {
// Given
var reducer: Reducer? = Reducer(
@@ -96,7 +94,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0])
}
- @MainActor
func test_that_count_increases_when_mutate_in_start() async throws {
// Given
let reducer = Reducer(TimerReduce(
@@ -113,7 +110,6 @@ final class ReducerTests: XCTestCase {
XCTAssertGreaterThan(result.last ?? 0, 0)
}
- @MainActor
func test_that_count_increases_when_await_mutate_in_start() async throws {
// Given
let reducer = Reducer(AwaitStartReduce(
@@ -130,7 +126,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 1])
}
- @MainActor
func test_that_reducer_should_be_able_to_assign_proxy_reduce() async throws {
// Given
var reducer = Reducer(CountIncreaseReduce(
@@ -170,7 +165,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 10])
}
- @MainActor
func test_that_initial_count_is_100_when_proxy_set_initial_count() async throws {
// Given
let reducer = Reducer(proxy: .init(
@@ -189,7 +183,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [100])
}
- @MainActor
func test_that_count_increases_when_proxy_receiving_increase_action() async throws {
// Given
let reducer = Reducer(proxy: .init(
@@ -226,7 +219,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 1])
}
- @MainActor
func test_that_count_increases_10_when_proxy_receiving_increase_action() async throws {
// Given
let reducer = Reducer(proxy: .init(
@@ -264,7 +256,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 10, 20])
}
- @MainActor
func test_that_count_does_not_mutate_when_proxy_same_action_twice() async throws {
// Given
let reducer = Reducer(proxy: .init(
@@ -304,7 +295,6 @@ final class ReducerTests: XCTestCase {
XCTAssertEqual(result, [0, 1])
}
- @MainActor
func test_that_count_increases_when_proxy_mutate_in_start() async throws {
// Given
var cancellable: AnyCancellable? = nil
@@ -336,7 +326,6 @@ final class ReducerTests: XCTestCase {
XCTAssertGreaterThan(result.last ?? 0, 0)
}
- @MainActor
func test_that_reducer_can_assign_proxy_inherited_reduce() async throws {
// Given
let reducer = Reducer(
@@ -357,4 +346,22 @@ final class ReducerTests: XCTestCase {
// Then
XCTAssertEqual(result, [0, 10])
}
+
+ func test_that_count_increases_when_error_is_thrown_in_mutation() async throws {
+ // Given
+ let sut = Reducer(ErrorReduce())
+
+ // When
+ Task {
+ sut.action(.occurError(TestError("increase count")))
+ }
+
+ let result = try await wait(
+ sut.$state.map(\.count),
+ timeout: 1
+ )
+
+ // Then
+ XCTAssertEqual(result, [0, 1])
+ }
}