Skip to content
This repository was archived by the owner on May 13, 2020. It is now read-only.

Commit b92e1e0

Browse files
author
Thibault Wittemberg
committed
Add exhaustivity for substate reducers
1 parent 1c250e2 commit b92e1e0

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

RxReduce/Store.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,45 @@ import RxCocoa
2020
/// A Store is dedicated to a State Type
2121
public final class Store<State: Equatable> {
2222

23-
public typealias ReducerFunction = (State, Action) -> State
23+
private typealias ReducerFunction = (State, Action) -> State
2424

2525
private var state: State
26-
private var reducers = [ReducerFunction]()
27-
// private let reducers: ContiguousArray<Reducer<StateType>>
26+
private var reducers = [String: ReducerFunction]()
27+
28+
private var subStateTypes = [String]()
2829
// private let middlewares: ContiguousArray<Middleware<StateType>>?
2930

3031
public init(withState state: State) {
3132
self.state = state
33+
34+
let stateMirror = Mirror(reflecting: self.state)
35+
for child in stateMirror.children {
36+
let childMirror = Mirror(reflecting: child.value)
37+
subStateTypes.append("\(type(of: childMirror.subjectType))")
38+
}
39+
3240
// self.middlewares = middlewares
3341
}
3442

35-
public func register<SubState: Equatable> (reducer: Reducer<State, SubState>) {
36-
self.reducers.append(reducer.apply)
43+
public func register<SubState: Equatable> (reducer: Reducer<State, SubState>) throws {
44+
let key = "\(type(of: SubState.self))"
45+
46+
if let index = self.subStateTypes.index(of: key) {
47+
self.subStateTypes.remove(at: index)
48+
}
49+
50+
guard self.reducers[key] == nil else {
51+
throw NSError(domain: "ReducerAlreadyExists", code: -1)
52+
}
53+
self.reducers[key] = reducer.apply
3754
}
3855

3956
public func dispatch(action: Action) -> Observable<State> {
57+
58+
guard self.subStateTypes.isEmpty else {
59+
fatalError("All substate must be handled")
60+
}
61+
4062
// every received action is converted to an async action
4163
return action
4264
.toAsync()
@@ -47,7 +69,7 @@ public final class Store<State: Equatable> {
4769
// })
4870
.map { (action) -> State in
4971

50-
return self.reducers.reduce(self.state, { (currentState, reducer) -> State in
72+
return self.reducers.values.reduce(self.state, { (currentState, reducer) -> State in
5173
return reducer(currentState, action)
5274
})
5375
}.do(onNext: { [unowned self] (newState) in

RxReduceTests/StoreTests.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ class StoreTests: XCTestCase {
3232
let counterReducer = Reducer<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
3333
let userReducer = Reducer<TestState, UserState>(lens: userLens, reducer: userReduce)
3434

35-
store.register(reducer: counterReducer)
36-
store.register(reducer: userReducer)
35+
do {
36+
try store.register(reducer: counterReducer)
37+
try store.register(reducer: userReducer)
38+
} catch {
39+
XCTFail("Reducer registering failure")
40+
}
3741

3842
return store
3943
}()
@@ -146,6 +150,17 @@ class StoreTests: XCTestCase {
146150
}
147151
}
148152

153+
func testStoreRegistering () {
154+
do {
155+
let counterReducer = Reducer<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
156+
try self.store.register(reducer: counterReducer)
157+
XCTFail("Counter Reducer has already been registered, it should not be possible to register a new one")
158+
} catch {
159+
let error = error as NSError
160+
XCTAssertEqual("ReducerAlreadyExists", error.domain)
161+
}
162+
}
163+
149164
// func testMiddleware () {
150165
//
151166
// let exp = expectation(description: "Middleware subscription")

0 commit comments

Comments
 (0)