Skip to content

Commit d9575b3

Browse files
committed
README and Bool controls
1 parent c512326 commit d9575b3

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,92 @@
11
# GatedMiddleware
22
Turn SwiftRex middlewares on or off dynamically
3+
4+
Gated middleware is a middleware that holds an inner middleware that could be either active or not. The gated middleware has an internal state,
5+
called `gate state`, that determines whether or not the inner middleware should be in `active` or `bypass` mode. This can be changed dynamically.
6+
7+
Every gated middleware starts with an initial gate state, called "default gate state". From that point, it will evaluate all incoming actions to
8+
detect a "control action", which is an action for switching on or off the gate state. This control action is detected thanks to a control action
9+
map closure or a control action map KeyPath configured in the GatedMiddleware's init, which from a given input action allows the user to inform
10+
or not either this is a control action returning an Optional instance of that ControlAction (or nil in case it's a regular action).
11+
12+
The init also requires some comparison values, for turnOn or turnOff the gate. If it's a control action, and it's equals to turn on, it will set
13+
the inner middleware to active. If it's a control action, and it's equals to turn off, it will set the inner middleware to bypass. If it's not a
14+
control action, or it's not equals to any of the comparison values, the gate will remain untouched.
15+
16+
There one last important topic. The gated middleware will ALWAYS forward control actions to inner middlewares, regardless of their gate state
17+
(active or bypass) and regardless of the turn on/turn off comparison result. This will allow important actions like disabling or enabling the
18+
inner middleware for control actions, so for example, even for when we close the gate we still want to tell the inner middleware that it's gonna
19+
be bypassed and it should kill all of its timers or async side-effects.
20+
21+
Example:
22+
23+
```
24+
// sourcery: Prism
25+
enum AppAction {
26+
case something
27+
case anotherSomething
28+
case dynamicMiddlewares(DynamicMiddlewareAction)
29+
}
30+
31+
// sourcery: Prism
32+
enum DynamicMiddlewareAction: Equatable {
33+
case toggleCrashReportsMiddleware(enable: Bool)
34+
}
35+
36+
let gatedCrashReportsMiddleware =
37+
CrashReportsMiddleware
38+
.init()
39+
.gated(
40+
controlAction: \AppAction.dynamicMiddlewares?.toggleCrashReportsMiddleware?.enable,
41+
default: .active
42+
)
43+
```
44+
45+
With custom comparison:
46+
47+
```
48+
// sourcery: Prism
49+
enum AppAction {
50+
case something
51+
case anotherSomething
52+
case dynamicMiddlewares(DynamicMiddlewareAction)
53+
}
54+
55+
// sourcery: Prism
56+
enum DynamicMiddlewareAction: Equatable {
57+
case controlCrashReportsMiddleware(controlAction: MiddlewareControlAction)
58+
}
59+
60+
enum MiddlewareControlAction: Equatable {
61+
case activate
62+
case bypass
63+
case sayHello
64+
}
65+
66+
let gatedCrashReportsMiddleware =
67+
CrashReportsMiddleware
68+
.init()
69+
.gated(
70+
controlAction: \AppAction.dynamicMiddlewares?.controlCrashReportsMiddleware,
71+
turnOn: MiddlewareControlAction.activate,
72+
turnOff: MiddlewareControlAction.bypass,
73+
default: .active
74+
)
75+
// in this example, MiddlewareControlAction.activate will activate the crash reports,
76+
// MiddlewareControlAction.bypass will disable the crash reports, and
77+
// MiddlewareControlAction.sayHello won't change the gate state, but will be forwarded to the
78+
// crash reports middleware regardless of its current state.
79+
```
80+
81+
You can also lift the inner middleware before gating it, in case the AppActions or AppStates don't match. Evidently lift can also be done
82+
after the gated middleware if this is what you need.
83+
84+
Gating composed middlewares will disable or enable all of them at once, and the control action will be the same and be forwarded to all
85+
the inner middlewares all the times. If this is not what you need, you need disabling them individually, you can first gate them and with
86+
the gated collection you compose them.
87+
88+
This has no interference on Reducers and this doesn't change the AppState in any way. GatedMiddleware only matches AppState with its inner
89+
middleware to allow proxying the `getState` context.
90+
91+
All examples above use Sourcery Prism templates to simplify traversing action trees, but the `GatedMiddleware` offers closures in case you
92+
prefer switch/case approach or other custom functions.

Sources/GatedMiddleware/GatedMiddleware.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public enum GateState: String, Codable, Equatable, Hashable {
1616
///
1717
/// Every gated middleware starts with an initial gate state, called "default gate state". From that point, it will evaluate all incoming actions to
1818
/// detect a "control action", which is an action for switching on or off the gate state. This control action is detected thanks to a control action
19-
/// map or a control map KeyPath configured in the GatedMiddleware's init, which from a given input action allows the user to inform either or not
20-
/// this is a control action returning an Optional instance of that ControlAction (or nil in case it's a regular action).
19+
/// map closure or a control action map KeyPath configured in the GatedMiddleware's init, which from a given input action allows the user to inform
20+
/// or not either this is a control action returning an Optional instance of that ControlAction (or nil in case it's a regular action).
2121
///
2222
/// The init also requires some comparison values, for turnOn or turnOff the gate. If it's a control action, and it's equals to turn on, it will set
2323
/// the inner middleware to active. If it's a control action, and it's equals to turn off, it will set the inner middleware to bypass. If it's not a
@@ -197,4 +197,33 @@ extension Middleware {
197197
) -> GatedMiddleware<Self> {
198198
GatedMiddleware(middleware: self, controlActionMap: controlActionMap, turnOn: turnOn, turnOff: turnOff, default: gateState)
199199
}
200+
201+
/// Gated middleware is a middleware that holds an inner middleware that could be either active or not. The gated middleware has an internal state,
202+
/// called `gate state`, that determines whether or not the inner middleware should be in `active` or `bypass` mode. This can be changed dynamically.
203+
///
204+
/// Every gated middleware starts with an initial gate state, called "default gate state". From that point, it will evaluate all incoming actions to
205+
/// detect a "control action", which is an action for switching on or off the gate state. This control action is detected thanks to a control action
206+
/// map or a control map KeyPath configured in the GatedMiddleware's init, which from a given input action allows the user to inform either or not
207+
/// this is a control action returning an Optional instance of that ControlAction (or nil in case it's a regular action).
208+
///
209+
/// The init also requires some comparison values, for turnOn or turnOff the gate. If it's a control action, and it's equals to turn on, it will set
210+
/// the inner middleware to active. If it's a control action, and it's equals to turn off, it will set the inner middleware to bypass. If it's not a
211+
/// control action, or it's not equals to any of the comparison values, the gate will remain untouched.
212+
///
213+
/// There one last important topic. The gated middleware will ALWAYS forward control actions to inner middlewares, regardless of their gate state
214+
/// (active or bypass) and regardless of the turn on/turn off comparison result. This will allow important actions like disabling or enabling the
215+
/// inner middleware for control actions, so for example, even for when we close the gate we still want to tell the inner middleware that it's gonna
216+
/// be bypassed and it should kill all of its timers or async side-effects.
217+
/// - Parameters:
218+
/// - controlAction: a key-path that goes from incoming action to an optional Bool. It result is nil, it means this is not a control action.
219+
/// In case it has a non-nil Bool, this will enable (for `true`) or bypass (for `false`) the inner middleware. The inner
220+
/// middleware will also receive that control action regardless of its gate state.
221+
/// - gateState: initial `gateState`, either `active` or `bypass`
222+
/// - Returns: a `GatedMiddleware` containing internally this current middleware, allowing it to be bypassed or not.
223+
public func gated(
224+
controlActionMap: @escaping (InputActionType) -> Bool?,
225+
default gateState: GateState
226+
) -> GatedMiddleware<Self> {
227+
GatedMiddleware(middleware: self, controlActionMap: controlActionMap, turnOn: true, turnOff: false, default: gateState)
228+
}
200229
}

0 commit comments

Comments
 (0)