@@ -125,6 +125,7 @@ extension LoggerMiddleware {
125125 public enum StateDiffTransform {
126126 case diff( linesOfContext: Int = 2 , prefixLines: String = " π " )
127127 case newStateOnly
128+ case recursive( prefixLines: String = " π " , stateName: String )
128129 case custom( ( StateType ? , StateType ) -> String ? )
129130
130131 func transform( oldState: StateType ? , newState: StateType ) -> String ? {
@@ -136,11 +137,86 @@ extension LoggerMiddleware {
136137 ?? " \( prefixLines) No state mutation "
137138 case . newStateOnly:
138139 return dumpToString ( newState)
140+ case let . recursive( prefixLines, stateName) :
141+ return recursiveDiff ( prefixLines: prefixLines, stateName: stateName, before: oldState, after: newState)
139142 case let . custom( closure) :
140143 return closure ( oldState, newState)
141144 }
142145 }
143146 }
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+ let leftMirror = Mirror ( reflecting: lhs)
155+ let rightMirror = Mirror ( reflecting: rhs)
156+
157+ // special handling for Dictionaries
158+ if let left = lhs as? Dictionary < AnyHashable , Any > , let right = rhs as? Dictionary < AnyHashable , Any > {
159+
160+ let leftSorted = left. sorted { a, b in " \( a. key) " < " \( b. key) " }
161+ let rightSorted = right. sorted { a, b in " \( a. key) " < " \( b. key) " }
162+
163+ let leftPrintable = leftSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
164+ let rightPrintable = rightSorted. map { key, value in " \( key) : \( value) " } . joined ( separator: " , " )
165+
166+ // .difference(from:) gives unpleasant results
167+ if leftPrintable == rightPrintable {
168+ return nil
169+ }
170+
171+ return " \( prefix) . \( name) : π¦ [ \( leftPrintable) ] β [ \( rightPrintable) ] "
172+ }
173+
174+ // special handling for sets as well: order the contents, compare as strings
175+ if let left = lhs as? Set < AnyHashable > , let right = rhs as? Set < AnyHashable > {
176+ let leftSorted = left. map { " \( $0) " } . sorted { a, b in a < b }
177+ let rightSorted = right. map { " \( $0) " } . sorted { a, b in a < b }
178+
179+ let leftPrintable = leftSorted. joined ( separator: " , " )
180+ let rightPrintable = rightSorted. joined ( separator: " , " )
181+
182+ // .difference(from:) gives unpleasant results
183+ if leftPrintable == rightPrintable {
184+ return nil
185+ }
186+ return " \( prefix) . \( name) : π¦ < \( leftPrintable) > β < \( rightPrintable) > "
187+ }
188+
189+ // if there are no children, compare lhs and rhs directly
190+ if 0 == leftMirror. children. count {
191+ if " \( lhs) " == " \( rhs) " {
192+ return nil
193+ } else {
194+ return " \( prefix) . \( name) : \( lhs) β \( rhs) "
195+ }
196+ }
197+
198+ // there are children -> diff the object graph recursively
199+ let strings : [ String ] = leftMirror. children. map ( { leftChild in
200+ guard let rightChild = rightMirror. children. first ( where: { $0. label == leftChild. label } ) else {
201+ return nil
202+ }
203+
204+ let leftValue = leftChild. value
205+ let rightValue = rightChild. value
206+
207+ let dot = ( level > 0 ) ? " . " : " "
208+ return Self . diff ( prefix: " \( prefix) \( dot) \( name) " ,
209+ name: leftChild. label ?? " " ,
210+ level: level + 1 ,
211+ lhs: leftValue,
212+ rhs: rightValue)
213+ } ) . compactMap { $0 }
214+
215+ if strings. count > 0 {
216+ return strings. joined ( separator: " \n " )
217+ }
218+ return nil
219+ }
144220}
145221
146222// MARK: - Action
0 commit comments