@@ -67,7 +67,9 @@ public final class LoggerMiddleware<M: Middleware>: Middleware where M.StateType
67
67
self . queue. async {
68
68
let actionMessage = self . actionTransform. transform ( action: action, source: dispatcher)
69
69
self . actionPrinter. log ( action: actionMessage)
70
- self . stateDiffPrinter. log ( state: self . stateDiffTransform. transform ( oldState: stateBefore, newState: stateAfter) )
70
+ if let diffString = self . stateDiffTransform. transform ( oldState: stateBefore, newState: stateAfter) {
71
+ self . stateDiffPrinter. log ( state: diffString)
72
+ }
71
73
}
72
74
}
73
75
}
@@ -123,9 +125,10 @@ extension LoggerMiddleware {
123
125
public enum StateDiffTransform {
124
126
case diff( linesOfContext: Int = 2 , prefixLines: String = " 🏛 " )
125
127
case newStateOnly
126
- case custom( ( StateType ? , StateType ) -> String )
128
+ case recursive( prefixLines: String = " 🏛 " , stateName: String )
129
+ case custom( ( StateType ? , StateType ) -> String ? )
127
130
128
- func transform( oldState: StateType ? , newState: StateType ) -> String {
131
+ func transform( oldState: StateType ? , newState: StateType ) -> String ? {
129
132
switch self {
130
133
case let . diff( linesOfContext, prefixLines) :
131
134
let stateBefore = dumpToString ( oldState)
@@ -134,11 +137,93 @@ extension LoggerMiddleware {
134
137
?? " \( prefixLines) No state mutation "
135
138
case . newStateOnly:
136
139
return dumpToString ( newState)
140
+ case let . recursive( prefixLines, stateName) :
141
+ return recursiveDiff ( prefixLines: prefixLines, stateName: stateName, before: oldState, after: newState)
137
142
case let . custom( closure) :
138
143
return closure ( oldState, newState)
139
144
}
140
145
}
141
146
}
147
+
148
+ public static func recursiveDiff( prefixLines: String , stateName: String , before: StateType ? , after: StateType ) -> String ? {
149
+ // cuts the redundant newline character from the output
150
+ diff ( prefix: prefixLines, name: stateName, lhs: before, rhs: after) ? . trimmingCharacters ( in: . whitespacesAndNewlines)
151
+ }
152
+
153
+ private static func diff< A> ( prefix: String , name: String , level: Int = 0 , lhs: A ? , rhs: A ? ) -> String ? {
154
+
155
+ guard let rightHandSide = rhs, let leftHandSide = lhs else {
156
+ if let rightHandSide = rhs {
157
+ return " \( prefix) . \( name) : nil → \( rightHandSide) "
158
+ }
159
+
160
+ if let leftHandSide = lhs {
161
+ return " \( prefix) . \( name) : \( leftHandSide) → nil "
162
+ }
163
+
164
+ // nil == lhs == rhs
165
+ return nil
166
+ }
167
+
168
+ // special handling for Dictionaries: stringify and order the keys before comparing
169
+ if let left = leftHandSide as? Dictionary < AnyHashable , Any > , let right = rightHandSide as? Dictionary < AnyHashable , Any > {
170
+
171
+ let leftSorted = left. sorted { a, b in " \( a. key) " < " \( b. key) " }
172
+ let rightSorted = right. sorted { a, b in " \( a. key) " < " \( b. key) " }
173
+
174
+ let leftPrintable = leftSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
175
+ let rightPrintable = rightSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
176
+
177
+ // .difference(from:) gives unpleasant results
178
+ if leftPrintable == rightPrintable {
179
+ return nil
180
+ }
181
+
182
+ return " \( prefix) . \( name) : 📦 [ \( leftPrintable) ] → [ \( rightPrintable) ] "
183
+ }
184
+
185
+ // special handling for sets as well: order the contents, compare as strings
186
+ if let left = leftHandSide as? Set < AnyHashable > , let right = rightHandSide as? Set < AnyHashable > {
187
+ let leftSorted = left. map { " \( $0) " } . sorted { a, b in a < b }
188
+ let rightSorted = right. map { " \( $0) " } . sorted { a, b in a < b }
189
+
190
+ let leftPrintable = leftSorted. joined ( separator: " , " )
191
+ let rightPrintable = rightSorted. joined ( separator: " , " )
192
+
193
+ // .difference(from:) gives unpleasant results
194
+ if leftPrintable == rightPrintable {
195
+ return nil
196
+ }
197
+ return " \( prefix) . \( name) : 📦 < \( leftPrintable) > → < \( rightPrintable) > "
198
+ }
199
+
200
+ let leftMirror = Mirror ( reflecting: leftHandSide)
201
+ let rightMirror = Mirror ( reflecting: rightHandSide)
202
+
203
+ // if there are no children, compare leftHandSide and rightHandSide directly
204
+ if 0 == leftMirror. children. count {
205
+ if " \( leftHandSide) " == " \( rightHandSide) " {
206
+ return nil
207
+ } else {
208
+ return " \( prefix) . \( name) : \( leftHandSide) → \( rightHandSide) "
209
+ }
210
+ }
211
+
212
+ // there are children -> diff the object graph recursively
213
+ let strings : [ String ] = leftMirror. children. map ( { leftChild in
214
+ let toDotOrNotToDot = ( level > 0 ) ? " . " : " "
215
+ return Self . diff ( prefix: " \( prefix) \( toDotOrNotToDot) \( name) " ,
216
+ name: leftChild. label ?? " # " , // label might be missing for items in collections, # represents a collection element
217
+ level: level + 1 ,
218
+ lhs: leftChild. value,
219
+ rhs: rightMirror. children. first ( where: { $0. label == leftChild. label } ) ? . value)
220
+ } ) . compactMap { $0 }
221
+
222
+ if strings. count > 0 {
223
+ return strings. joined ( separator: " \n " )
224
+ }
225
+ return nil
226
+ }
142
227
}
143
228
144
229
// MARK: - Action
0 commit comments