1package httpmock
2
3import (
4	"bytes"
5	"encoding/json"
6	"encoding/xml"
7	"fmt"
8	"io"
9	"net/http"
10	"strconv"
11	"strings"
12)
13
14// Responder is a callback that receives and http request and returns
15// a mocked response.
16type Responder func(*http.Request) (*http.Response, error)
17
18func (r Responder) times(name string, n int, fn ...func(...interface{})) Responder {
19	count := 0
20	return func(req *http.Request) (*http.Response, error) {
21		count++
22		if count > n {
23			err := stackTracer{
24				err: fmt.Errorf("Responder not found for %s %s (coz %s and already called %d times)", req.Method, req.URL, name, count),
25			}
26			if len(fn) > 0 {
27				err.customFn = fn[0]
28			}
29			return nil, err
30		}
31		return r(req)
32	}
33}
34
35// Times returns a Responder callable n times before returning an
36// error. If the Responder is called more than n times and fn is
37// passed and non-nil, it acts as the fn parameter of
38// NewNotFoundResponder, allowing to dump the stack trace to localize
39// the origin of the call.
40func (r Responder) Times(n int, fn ...func(...interface{})) Responder {
41	return r.times("Times", n, fn...)
42}
43
44// Once returns a new Responder callable once before returning an
45// error. If the Responder is called 2 or more times and fn is passed
46// and non-nil, it acts as the fn parameter of NewNotFoundResponder,
47// allowing to dump the stack trace to localize the origin of the
48// call.
49func (r Responder) Once(fn ...func(...interface{})) Responder {
50	return r.times("Once", 1, fn...)
51}
52
53// Trace returns a new Responder that allow to easily trace the calls
54// of the original Responder using fn. It can be used in conjunction
55// with the testing package as in the example below with the help of
56// (*testing.T).Log method:
57//   import "testing"
58//   ...
59//   func TestMyApp(t *testing.T) {
60//   	...
61//   	httpmock.RegisterResponder("GET", "/foo/bar",
62//    	httpmock.NewStringResponder(200, "{}").Trace(t.Log),
63//   	)
64func (r Responder) Trace(fn func(...interface{})) Responder {
65	return func(req *http.Request) (*http.Response, error) {
66		resp, err := r(req)
67		return resp, stackTracer{
68			customFn: fn,
69			err:      err,
70		}
71	}
72}
73
74// ResponderFromResponse wraps an *http.Response in a Responder
75func ResponderFromResponse(resp *http.Response) Responder {
76	return func(req *http.Request) (*http.Response, error) {
77		res := new(http.Response)
78		*res = *resp
79		res.Request = req
80		return res, nil
81	}
82}
83
84// NewErrorResponder creates a Responder that returns an empty request and the
85// given error. This can be used to e.g. imitate more deep http errors for the
86// client.
87func NewErrorResponder(err error) Responder {
88	return func(req *http.Request) (*http.Response, error) {
89		return nil, err
90	}
91}
92
93// NewNotFoundResponder creates a Responder typically used in
94// conjunction with RegisterNoResponder() function and testing
95// package, to be proactive when a Responder is not found. fn is
96// called with a unique string parameter containing the name of the
97// missing route and the stack trace to localize the origin of the
98// call. If fn returns (= if it does not panic), the responder returns
99// an error of the form: "Responder not found for GET http://foo.bar/path".
100// Note that fn can be nil.
101//
102// It is useful when writing tests to ensure that all routes have been
103// mocked.
104//
105// Example of use:
106//   import "testing"
107//   ...
108//   func TestMyApp(t *testing.T) {
109//   	...
110//   	// Calls testing.Fatal with the name of Responder-less route and
111//   	// the stack trace of the call.
112//   	httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal))
113//
114// Will abort the current test and print something like:
115//   transport_test.go:735: Called from net/http.Get()
116//         at /go/src/github.com/jarcoal/httpmock/transport_test.go:714
117//       github.com/jarcoal/httpmock.TestCheckStackTracer()
118//         at /go/src/testing/testing.go:865
119//       testing.tRunner()
120//         at /go/src/runtime/asm_amd64.s:1337
121func NewNotFoundResponder(fn func(...interface{})) Responder {
122	return func(req *http.Request) (*http.Response, error) {
123		return nil, stackTracer{
124			customFn: fn,
125			err:      fmt.Errorf("Responder not found for %s %s", req.Method, req.URL),
126		}
127	}
128}
129
130// NewStringResponse creates an *http.Response with a body based on the given string.  Also accepts
131// an http status code.
132func NewStringResponse(status int, body string) *http.Response {
133	return &http.Response{
134		Status:        strconv.Itoa(status),
135		StatusCode:    status,
136		Body:          NewRespBodyFromString(body),
137		Header:        http.Header{},
138		ContentLength: -1,
139	}
140}
141
142// NewStringResponder creates a Responder from a given body (as a string) and status code.
143func NewStringResponder(status int, body string) Responder {
144	return ResponderFromResponse(NewStringResponse(status, body))
145}
146
147// NewBytesResponse creates an *http.Response with a body based on the given bytes.  Also accepts
148// an http status code.
149func NewBytesResponse(status int, body []byte) *http.Response {
150	return &http.Response{
151		Status:        strconv.Itoa(status),
152		StatusCode:    status,
153		Body:          NewRespBodyFromBytes(body),
154		Header:        http.Header{},
155		ContentLength: -1,
156	}
157}
158
159// NewBytesResponder creates a Responder from a given body (as a byte slice) and status code.
160func NewBytesResponder(status int, body []byte) Responder {
161	return ResponderFromResponse(NewBytesResponse(status, body))
162}
163
164// NewJsonResponse creates an *http.Response with a body that is a json encoded representation of
165// the given interface{}.  Also accepts an http status code.
166func NewJsonResponse(status int, body interface{}) (*http.Response, error) { // nolint: golint
167	encoded, err := json.Marshal(body)
168	if err != nil {
169		return nil, err
170	}
171	response := NewBytesResponse(status, encoded)
172	response.Header.Set("Content-Type", "application/json")
173	return response, nil
174}
175
176// NewJsonResponder creates a Responder from a given body (as an interface{} that is encoded to
177// json) and status code.
178func NewJsonResponder(status int, body interface{}) (Responder, error) { // nolint: golint
179	resp, err := NewJsonResponse(status, body)
180	if err != nil {
181		return nil, err
182	}
183	return ResponderFromResponse(resp), nil
184}
185
186// NewJsonResponderOrPanic is like NewJsonResponder but panics in case of error.
187//
188// It simplifies the call of RegisterResponder, avoiding the use of a
189// temporary variable and an error check, and so can be used as
190// NewStringResponder or NewBytesResponder in such context:
191//   RegisterResponder(
192//     "GET",
193//     "/test/path",
194//     NewJSONResponderOrPanic(200, &MyBody),
195//   )
196func NewJsonResponderOrPanic(status int, body interface{}) Responder { // nolint: golint
197	responder, err := NewJsonResponder(status, body)
198	if err != nil {
199		panic(err)
200	}
201	return responder
202}
203
204// NewXmlResponse creates an *http.Response with a body that is an xml encoded representation
205// of the given interface{}.  Also accepts an http status code.
206func NewXmlResponse(status int, body interface{}) (*http.Response, error) { // nolint: golint
207	encoded, err := xml.Marshal(body)
208	if err != nil {
209		return nil, err
210	}
211	response := NewBytesResponse(status, encoded)
212	response.Header.Set("Content-Type", "application/xml")
213	return response, nil
214}
215
216// NewXmlResponder creates a Responder from a given body (as an interface{} that is encoded to xml)
217// and status code.
218func NewXmlResponder(status int, body interface{}) (Responder, error) { // nolint: golint
219	resp, err := NewXmlResponse(status, body)
220	if err != nil {
221		return nil, err
222	}
223	return ResponderFromResponse(resp), nil
224}
225
226// NewXmlResponderOrPanic is like NewXmlResponder but panics in case of error.
227//
228// It simplifies the call of RegisterResponder, avoiding the use of a
229// temporary variable and an error check, and so can be used as
230// NewStringResponder or NewBytesResponder in such context:
231//   RegisterResponder(
232//     "GET",
233//     "/test/path",
234//     NewXmlResponderOrPanic(200, &MyBody),
235//   )
236func NewXmlResponderOrPanic(status int, body interface{}) Responder { // nolint: golint
237	responder, err := NewXmlResponder(status, body)
238	if err != nil {
239		panic(err)
240	}
241	return responder
242}
243
244// NewRespBodyFromString creates an io.ReadCloser from a string that is suitable for use as an
245// http response body.
246func NewRespBodyFromString(body string) io.ReadCloser {
247	return &dummyReadCloser{strings.NewReader(body)}
248}
249
250// NewRespBodyFromBytes creates an io.ReadCloser from a byte slice that is suitable for use as an
251// http response body.
252func NewRespBodyFromBytes(body []byte) io.ReadCloser {
253	return &dummyReadCloser{bytes.NewReader(body)}
254}
255
256type dummyReadCloser struct {
257	body io.ReadSeeker
258}
259
260func (d *dummyReadCloser) Read(p []byte) (n int, err error) {
261	n, err = d.body.Read(p)
262	if err == io.EOF {
263		d.body.Seek(0, 0) // nolint: errcheck
264	}
265	return n, err
266}
267
268func (d *dummyReadCloser) Close() error {
269	d.body.Seek(0, 0) // nolint: errcheck
270	return nil
271}
272