@@ -67,7 +67,9 @@ public final class LoggerMiddleware<M: Middleware>: Middleware where M.StateType
6767            self . queue. async { 
6868                let  actionMessage  =  self . actionTransform. transform ( action:  action,  source:  dispatcher) 
6969                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+                 } 
7173            } 
7274        } 
7375    } 
@@ -123,9 +125,10 @@ extension LoggerMiddleware {
123125    public  enum  StateDiffTransform  { 
124126        case  diff( linesOfContext:  Int  =  2 ,  prefixLines:  String  =  " 🏛  " ) 
125127        case  newStateOnly
126-         case  custom( ( StateType ? ,  StateType )  ->  String ) 
128+         case  recursive( prefixLines:  String  =  " 🏛  " ,  stateName:  String ) 
129+         case  custom( ( StateType ? ,  StateType )  ->  String ? ) 
127130
128-         func  transform( oldState:  StateType ? ,  newState:  StateType )  ->  String  { 
131+         func  transform( oldState:  StateType ? ,  newState:  StateType )  ->  String ? { 
129132            switch  self  { 
130133            case  let  . diff( linesOfContext,  prefixLines) : 
131134                let  stateBefore  =  dumpToString ( oldState) 
@@ -134,11 +137,93 @@ extension LoggerMiddleware {
134137                ??  " \( prefixLines)  No state mutation " 
135138            case  . newStateOnly: 
136139                return  dumpToString ( newState) 
140+             case  let  . recursive( prefixLines,  stateName) : 
141+                 return  recursiveDiff ( prefixLines:  prefixLines,  stateName:  stateName,  before:  oldState,  after:  newState) 
137142            case  let  . custom( closure) : 
138143                return  closure ( oldState,  newState) 
139144            } 
140145        } 
141146    } 
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+     } 
142227} 
143228
144229// MARK: - Action
0 commit comments