@@ -2,11 +2,14 @@ package httpmock
22
33import (
44 "bytes"
5+ "context"
56 "encoding/json"
67 "encoding/xml"
8+ "errors"
79 "fmt"
810 "io"
911 "net/http"
12+ "reflect"
1013 "strconv"
1114 "strings"
1215 "sync"
@@ -122,6 +125,79 @@ func (r Responder) Delay(d time.Duration) Responder {
122125 }
123126}
124127
128+ type fromThenKeyType struct {}
129+
130+ var fromThenKey = fromThenKeyType {}
131+
132+ var errThenDone = errors .New ("ThenDone" )
133+
134+ // similar is simple but a bit tricky. Here we consider two Responder
135+ // are similar if they share the same function, but not necessarily
136+ // the same environment. It is only used by Then below.
137+ func (r Responder ) similar (other Responder ) bool {
138+ return reflect .ValueOf (r ).Pointer () == reflect .ValueOf (other ).Pointer ()
139+ }
140+
141+ // Then returns a new Responder that calls r on first invocation, then
142+ // next on following ones, except when Then is chained, in this case
143+ // next is called only once:
144+ // A := httpmock.NewStringResponder(200, "A")
145+ // B := httpmock.NewStringResponder(200, "B")
146+ // C := httpmock.NewStringResponder(200, "C")
147+ //
148+ // httpmock.RegisterResponder("GET", "/pipo", A.Then(B).Then(C))
149+ //
150+ // http.Get("http://foo.bar/pipo") // A is called
151+ // http.Get("http://foo.bar/pipo") // B is called
152+ // http.Get("http://foo.bar/pipo") // C is called
153+ // http.Get("http://foo.bar/pipo") // C is called, and so on
154+ //
155+ // A panic occurs if next is the result of another Then call (because
156+ // allowing it could cause inextricable problems at runtime). Then
157+ // calls can be chained, but cannot call each other by
158+ // parameter. Example:
159+ // A.Then(B).Then(C) // is OK
160+ // A.Then(B.Then(C)) // panics as A.Then() parameter is another Then() call
161+ func (r Responder ) Then (next Responder ) (x Responder ) {
162+ var done int
163+ var mu sync.Mutex
164+ x = func (req * http.Request ) (* http.Response , error ) {
165+ mu .Lock ()
166+ defer mu .Unlock ()
167+
168+ ctx := req .Context ()
169+ thenCalledUs , _ := ctx .Value (fromThenKey ).(bool )
170+ if ! thenCalledUs {
171+ req = req .WithContext (context .WithValue (ctx , fromThenKey , true ))
172+ }
173+
174+ switch done {
175+ case 0 :
176+ resp , err := r (req )
177+ if err != errThenDone {
178+ if ! x .similar (r ) { // r is NOT a Then
179+ done = 1
180+ }
181+ return resp , err
182+ }
183+ fallthrough
184+
185+ case 1 :
186+ done = 2 // next is NEVER a Then, as it is forbidden by design
187+ return next (req )
188+ }
189+ if thenCalledUs {
190+ return nil , errThenDone
191+ }
192+ return next (req )
193+ }
194+
195+ if next .similar (x ) {
196+ panic ("Then() does not accept another Then() Responder as parameter" )
197+ }
198+ return
199+ }
200+
125201// ResponderFromResponse wraps an *http.Response in a Responder.
126202//
127203// Be careful, except for responses generated by httpmock
0 commit comments