1package httpmock
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"fmt"
8	"net/http"
9	"net/url"
10	"regexp"
11	"runtime"
12	"sort"
13	"strconv"
14	"strings"
15	"sync"
16)
17
18const regexpPrefix = "=~"
19
20// NoResponderFound is returned when no responders are found for a given HTTP method and URL.
21var NoResponderFound = errors.New("no responder found") // nolint: golint
22
23type routeKey struct {
24	Method string
25	URL    string
26}
27
28var noResponder routeKey
29
30func (r routeKey) String() string {
31	if r == noResponder {
32		return "NO_RESPONDER"
33	}
34	return r.Method + " " + r.URL
35}
36
37// ConnectionFailure is a responder that returns a connection failure.  This is the default
38// responder, and is called when no other matching responder is found.
39func ConnectionFailure(*http.Request) (*http.Response, error) {
40	return nil, NoResponderFound
41}
42
43// NewMockTransport creates a new *MockTransport with no responders.
44func NewMockTransport() *MockTransport {
45	return &MockTransport{
46		responders:    make(map[routeKey]Responder),
47		callCountInfo: make(map[routeKey]int),
48	}
49}
50
51type regexpResponder struct {
52	origRx    string
53	method    string
54	rx        *regexp.Regexp
55	responder Responder
56}
57
58// MockTransport implements http.RoundTripper, which fulfills single http requests issued by
59// an http.Client.  This implementation doesn't actually make the call, instead deferring to
60// the registered list of responders.
61type MockTransport struct {
62	mu               sync.RWMutex
63	responders       map[routeKey]Responder
64	regexpResponders []regexpResponder
65	noResponder      Responder
66	callCountInfo    map[routeKey]int
67	totalCallCount   int
68}
69
70// RoundTrip receives HTTP requests and routes them to the appropriate responder.  It is required to
71// implement the http.RoundTripper interface.  You will not interact with this directly, instead
72// the *http.Client you are using will call it for you.
73func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
74	url := req.URL.String()
75
76	method := req.Method
77	if method == "" {
78		// http.Request.Method is documented to default to GET:
79		method = http.MethodGet
80	}
81
82	var (
83		responder  Responder
84		respKey    routeKey
85		submatches []string
86	)
87	key := routeKey{
88		Method: method,
89	}
90	for _, getResponder := range []func(routeKey) (Responder, routeKey, []string){
91		m.responderForKey,       // Exact match
92		m.regexpResponderForKey, // Regexp match
93	} {
94		// try and get a responder that matches the method and URL with
95		// query params untouched: http://z.tld/path?q...
96		key.URL = url
97		responder, respKey, submatches = getResponder(key)
98		if responder != nil {
99			break
100		}
101
102		// if we weren't able to find a responder, try with the URL *and*
103		// sorted query params
104		query := sortedQuery(req.URL.Query())
105		if query != "" {
106			// Replace unsorted query params by sorted ones:
107			//   http://z.tld/path?sorted_q...
108			key.URL = strings.Replace(url, req.URL.RawQuery, query, 1)
109			responder, respKey, submatches = getResponder(key)
110			if responder != nil {
111				break
112			}
113		}
114
115		// if we weren't able to find a responder, try without any query params
116		strippedURL := *req.URL
117		strippedURL.RawQuery = ""
118		strippedURL.Fragment = ""
119
120		// go1.6 does not handle URL.ForceQuery, so in case it is set in go>1.6,
121		// remove the "?" manually if present.
122		surl := strings.TrimSuffix(strippedURL.String(), "?")
123
124		hasQueryString := url != surl
125
126		// if the URL contains a querystring then we strip off the
127		// querystring and try again: http://z.tld/path
128		if hasQueryString {
129			key.URL = surl
130			responder, respKey, submatches = getResponder(key)
131			if responder != nil {
132				break
133			}
134		}
135
136		// if we weren't able to find a responder for the full URL, try with
137		// the path part only
138		pathAlone := req.URL.Path
139
140		// First with unsorted querystring: /path?q...
141		if hasQueryString {
142			key.URL = pathAlone + strings.TrimPrefix(url, surl) // concat after-path part
143			responder, respKey, submatches = getResponder(key)
144			if responder != nil {
145				break
146			}
147
148			// Then with sorted querystring: /path?sorted_q...
149			key.URL = pathAlone + "?" + sortedQuery(req.URL.Query())
150			if req.URL.Fragment != "" {
151				key.URL += "#" + req.URL.Fragment
152			}
153			responder, respKey, submatches = getResponder(key)
154			if responder != nil {
155				break
156			}
157		}
158
159		// Then using path alone: /path
160		key.URL = pathAlone
161		responder, respKey, submatches = getResponder(key)
162		if responder != nil {
163			break
164		}
165	}
166
167	m.mu.Lock()
168	// if we found a responder, call it
169	if responder != nil {
170		m.callCountInfo[key]++
171		if key != respKey {
172			m.callCountInfo[respKey]++
173		}
174		m.totalCallCount++
175	} else {
176		// we didn't find a responder, so fire the 'no responder' responder
177		if m.noResponder != nil {
178			m.callCountInfo[noResponder]++
179			m.totalCallCount++
180			responder = m.noResponder
181		}
182	}
183	m.mu.Unlock()
184
185	if responder == nil {
186		return ConnectionFailure(req)
187	}
188	return runCancelable(responder, setSubmatches(req, submatches))
189}
190
191func runCancelable(responder Responder, req *http.Request) (*http.Response, error) {
192	ctx := req.Context()
193	if req.Cancel == nil && ctx.Done() == nil { // nolint: staticcheck
194		resp, err := responder(req)
195		return resp, checkStackTracer(req, err)
196	}
197
198	// Set up a goroutine that translates a close(req.Cancel) into a
199	// "request canceled" error, and another one that runs the
200	// responder. Then race them: first to the result channel wins.
201
202	type result struct {
203		response *http.Response
204		err      error
205	}
206	resultch := make(chan result, 1)
207	done := make(chan struct{}, 1)
208
209	go func() {
210		select {
211		case <-req.Cancel: // nolint: staticcheck
212			resultch <- result{
213				response: nil,
214				err:      errors.New("request canceled"),
215			}
216		case <-ctx.Done():
217			resultch <- result{
218				response: nil,
219				err:      ctx.Err(),
220			}
221		case <-done:
222		}
223	}()
224
225	go func() {
226		defer func() {
227			if err := recover(); err != nil {
228				resultch <- result{
229					response: nil,
230					err:      fmt.Errorf("panic in responder: got %q", err),
231				}
232			}
233		}()
234
235		response, err := responder(req)
236		resultch <- result{
237			response: response,
238			err:      err,
239		}
240	}()
241
242	r := <-resultch
243
244	// if a cancel() issued from context.WithCancel() or a
245	// close(req.Cancel) are never coming, we'll need to unblock the
246	// first goroutine.
247	done <- struct{}{}
248
249	return r.response, checkStackTracer(req, r.err)
250}
251
252type stackTracer struct {
253	customFn func(...interface{})
254	err      error
255}
256
257func (n stackTracer) Error() string {
258	if n.err == nil {
259		return ""
260	}
261	return n.err.Error()
262}
263
264// checkStackTracer checks for specific error returned by
265// NewNotFoundResponder function or Debug Responder method.
266func checkStackTracer(req *http.Request, err error) error {
267	if nf, ok := err.(stackTracer); ok {
268		if nf.customFn != nil {
269			pc := make([]uintptr, 128)
270			npc := runtime.Callers(2, pc)
271			pc = pc[:npc]
272
273			var mesg bytes.Buffer
274			var netHTTPBegin, netHTTPEnd bool
275
276			// Start recording at first net/http call if any...
277			for {
278				frames := runtime.CallersFrames(pc)
279
280				var lastFn string
281				for {
282					frame, more := frames.Next()
283
284					if !netHTTPEnd {
285						if netHTTPBegin {
286							netHTTPEnd = !strings.HasPrefix(frame.Function, "net/http.")
287						} else {
288							netHTTPBegin = strings.HasPrefix(frame.Function, "net/http.")
289						}
290					}
291
292					if netHTTPEnd {
293						if lastFn != "" {
294							if mesg.Len() == 0 {
295								if nf.err != nil {
296									mesg.WriteString(nf.err.Error())
297								} else {
298									fmt.Fprintf(&mesg, "%s %s", req.Method, req.URL)
299								}
300								mesg.WriteString("\nCalled from ")
301							} else {
302								mesg.WriteString("\n  ")
303							}
304							fmt.Fprintf(&mesg, "%s()\n    at %s:%d", lastFn, frame.File, frame.Line)
305						}
306					}
307					lastFn = frame.Function
308
309					if !more {
310						break
311					}
312				}
313
314				// At least one net/http frame found
315				if mesg.Len() > 0 {
316					break
317				}
318				netHTTPEnd = true // retry without looking at net/http frames
319			}
320
321			nf.customFn(mesg.String())
322		}
323		err = nf.err
324	}
325	return err
326}
327
328// responderForKey returns a responder for a given key.
329func (m *MockTransport) responderForKey(key routeKey) (Responder, routeKey, []string) {
330	m.mu.RLock()
331	defer m.mu.RUnlock()
332	return m.responders[key], key, nil
333}
334
335// responderForKeyUsingRegexp returns the first responder matching a given key using regexps.
336func (m *MockTransport) regexpResponderForKey(key routeKey) (Responder, routeKey, []string) {
337	m.mu.RLock()
338	defer m.mu.RUnlock()
339	for _, regInfo := range m.regexpResponders {
340		if regInfo.method == key.Method {
341			if sm := regInfo.rx.FindStringSubmatch(key.URL); sm != nil {
342				if len(sm) == 1 {
343					sm = nil
344				} else {
345					sm = sm[1:]
346				}
347				return regInfo.responder, routeKey{
348					Method: key.Method,
349					URL:    regInfo.origRx,
350				}, sm
351			}
352		}
353	}
354	return nil, key, nil
355}
356
357func isRegexpURL(url string) bool {
358	return strings.HasPrefix(url, regexpPrefix)
359}
360
361// RegisterResponder adds a new responder, associated with a given
362// HTTP method and URL (or path).
363//
364// When a request comes in that matches, the responder will be called
365// and the response returned to the client.
366//
367// If url contains query parameters, their order matters as well as
368// their content. All following URLs are here considered as different:
369//   http://z.tld?a=1&b=1
370//   http://z.tld?b=1&a=1
371//   http://z.tld?a&b
372//   http://z.tld?a=&b=
373//
374// If url begins with "=~", the following chars are considered as a
375// regular expression. If this regexp can not be compiled, it panics.
376// Note that the "=~" prefix remains in statistics returned by
377// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp
378// responders are tested in the order they are registered. Registering
379// an already existing regexp responder (same method & same regexp
380// string) replaces its responder but does not change its position.
381//
382// See RegisterRegexpResponder() to directly pass a *regexp.Regexp.
383//
384// Example:
385// 		func TestFetchArticles(t *testing.T) {
386// 			httpmock.Activate()
387// 			defer httpmock.DeactivateAndReset()
388//
389// 			httpmock.RegisterResponder("GET", "http://example.com/",
390// 				httpmock.NewStringResponder(200, "hello world"))
391//
392// 			httpmock.RegisterResponder("GET", "/path/only",
393// 				httpmock.NewStringResponder("any host hello world", 200))
394//
395// 			httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`,
396// 				httpmock.NewStringResponder("any item get", 200))
397//
398// 			// requests to http://example.com/ will now return "hello world" and
399// 			// requests to any host with path /path/only will return "any host hello world"
400// 			// requests to any host with path matching ^/item/id/\d+\z regular expression will return "any item get"
401// 		}
402func (m *MockTransport) RegisterResponder(method, url string, responder Responder) {
403	if isRegexpURL(url) {
404		m.registerRegexpResponder(regexpResponder{
405			origRx:    url,
406			method:    method,
407			rx:        regexp.MustCompile(url[2:]),
408			responder: responder,
409		})
410		return
411	}
412
413	key := routeKey{
414		Method: method,
415		URL:    url,
416	}
417
418	m.mu.Lock()
419	m.responders[key] = responder
420	m.callCountInfo[key] = 0
421	m.mu.Unlock()
422}
423
424func (m *MockTransport) registerRegexpResponder(regexpResponder regexpResponder) {
425	m.mu.Lock()
426	defer m.mu.Unlock()
427
428found:
429	for {
430		for i, rr := range m.regexpResponders {
431			if rr.method == regexpResponder.method && rr.origRx == regexpResponder.origRx {
432				m.regexpResponders[i] = regexpResponder
433				break found
434			}
435		}
436		m.regexpResponders = append(m.regexpResponders, regexpResponder)
437		break // nolint: staticcheck
438	}
439
440	m.callCountInfo[routeKey{
441		Method: regexpResponder.method,
442		URL:    regexpResponder.origRx,
443	}] = 0
444}
445
446// RegisterRegexpResponder adds a new responder, associated with a given
447// HTTP method and URL (or path) regular expression.
448//
449// When a request comes in that matches, the responder will be called
450// and the response returned to the client.
451//
452// As 2 regexps can match the same URL, the regexp responders are
453// tested in the order they are registered. Registering an already
454// existing regexp responder (same method & same regexp string)
455// replaces its responder but does not change its position.
456//
457// A "=~" prefix is added to the stringified regexp in the statistics
458// returned by GetCallCountInfo().
459//
460// See RegisterResponder function and the "=~" prefix in its url
461// parameter to avoid compiling the regexp by yourself.
462func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) {
463	m.registerRegexpResponder(regexpResponder{
464		origRx:    regexpPrefix + urlRegexp.String(),
465		method:    method,
466		rx:        urlRegexp,
467		responder: responder,
468	})
469}
470
471// RegisterResponderWithQuery is same as RegisterResponder, but it
472// doesn't depend on query items order.
473//
474// If query is non-nil, its type can be:
475//   url.Values
476//   map[string]string
477//   string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function)
478//
479// If the query type is not recognized or the string cannot be parsed
480// using net/url.ParseQuery, a panic() occurs.
481//
482// Unlike RegisterResponder, path cannot be prefixed by "=~" to say it
483// is a regexp. If it is, a panic occurs.
484func (m *MockTransport) RegisterResponderWithQuery(method, path string, query interface{}, responder Responder) {
485	if isRegexpURL(path) {
486		panic(`path begins with "=~", RegisterResponder should be used instead of RegisterResponderWithQuery`)
487	}
488
489	var mapQuery url.Values
490	switch q := query.(type) {
491	case url.Values:
492		mapQuery = q
493
494	case map[string]string:
495		mapQuery = make(url.Values, len(q))
496		for key, e := range q {
497			mapQuery[key] = []string{e}
498		}
499
500	case string:
501		var err error
502		mapQuery, err = url.ParseQuery(q)
503		if err != nil {
504			panic("RegisterResponderWithQuery bad query string: " + err.Error())
505		}
506
507	default:
508		if query != nil {
509			panic(fmt.Sprintf("RegisterResponderWithQuery bad query type %T. Only url.Values, map[string]string and string are allowed", query))
510		}
511	}
512
513	if queryString := sortedQuery(mapQuery); queryString != "" {
514		path += "?" + queryString
515	}
516	m.RegisterResponder(method, path, responder)
517}
518
519func sortedQuery(m url.Values) string {
520	if len(m) == 0 {
521		return ""
522	}
523
524	keys := make([]string, 0, len(m))
525	for k := range m {
526		keys = append(keys, k)
527	}
528	sort.Strings(keys)
529
530	var b bytes.Buffer
531	var values []string // nolint: prealloc
532
533	for _, k := range keys {
534		// Do not alter the passed url.Values
535		values = append(values, m[k]...)
536		sort.Strings(values)
537
538		k = url.QueryEscape(k)
539
540		for _, v := range values {
541			if b.Len() > 0 {
542				b.WriteByte('&')
543			}
544			fmt.Fprintf(&b, "%v=%v", k, url.QueryEscape(v))
545		}
546
547		values = values[:0]
548	}
549
550	return b.String()
551}
552
553// RegisterNoResponder is used to register a responder that will be called if no other responder is
554// found.  The default is ConnectionFailure.
555func (m *MockTransport) RegisterNoResponder(responder Responder) {
556	m.mu.Lock()
557	m.noResponder = responder
558	m.mu.Unlock()
559}
560
561// Reset removes all registered responders (including the no responder) from the MockTransport
562func (m *MockTransport) Reset() {
563	m.mu.Lock()
564	m.responders = make(map[routeKey]Responder)
565	m.regexpResponders = nil
566	m.noResponder = nil
567	m.callCountInfo = make(map[routeKey]int)
568	m.totalCallCount = 0
569	m.mu.Unlock()
570}
571
572// GetCallCountInfo gets the info on all the calls httpmock has caught
573// since it was activated or reset. The info is returned as a map of
574// the calling keys with the number of calls made to them as their
575// value. The key is the method, a space, and the url all concatenated
576// together.
577//
578// As a special case, regexp responders generate 2 entries for each
579// call. One for the call caught and the other for the rule that
580// matched. For example:
581//   RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
582//   http.Get("http://z.com")
583//
584// will generate the following result:
585//   map[string]int{
586//     `GET http://z.com`:  1,
587//     `GET =~z\.com\z`: 1,
588//   }
589func (m *MockTransport) GetCallCountInfo() map[string]int {
590	res := make(map[string]int, len(m.callCountInfo))
591	m.mu.RLock()
592	for k, v := range m.callCountInfo {
593		res[k.String()] = v
594	}
595	m.mu.RUnlock()
596	return res
597}
598
599// GetTotalCallCount returns the totalCallCount.
600func (m *MockTransport) GetTotalCallCount() int {
601	m.mu.RLock()
602	count := m.totalCallCount
603	m.mu.RUnlock()
604	return count
605}
606
607// DefaultTransport is the default mock transport used by Activate, Deactivate, Reset,
608// DeactivateAndReset, RegisterResponder, and RegisterNoResponder.
609var DefaultTransport = NewMockTransport()
610
611// InitialTransport is a cache of the original transport used so we can put it back
612// when Deactivate is called.
613var InitialTransport = http.DefaultTransport
614
615// Used to handle custom http clients (i.e clients other than http.DefaultClient)
616var oldClients = map[*http.Client]http.RoundTripper{}
617
618// Activate starts the mock environment.  This should be called before your tests run.  Under the
619// hood this replaces the Transport on the http.DefaultClient with DefaultTransport.
620//
621// To enable mocks for a test, simply activate at the beginning of a test:
622// 		func TestFetchArticles(t *testing.T) {
623// 			httpmock.Activate()
624// 			// all http requests will now be intercepted
625// 		}
626//
627// If you want all of your tests in a package to be mocked, just call Activate from init():
628// 		func init() {
629// 			httpmock.Activate()
630// 		}
631func Activate() {
632	if Disabled() {
633		return
634	}
635
636	// make sure that if Activate is called multiple times it doesn't overwrite the InitialTransport
637	// with a mock transport.
638	if http.DefaultTransport != DefaultTransport {
639		InitialTransport = http.DefaultTransport
640	}
641
642	http.DefaultTransport = DefaultTransport
643}
644
645// ActivateNonDefault starts the mock environment with a non-default http.Client.
646// This emulates the Activate function, but allows for custom clients that do not use
647// http.DefaultTransport
648//
649// To enable mocks for a test using a custom client, activate at the beginning of a test:
650// 		client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}}
651// 		httpmock.ActivateNonDefault(client)
652func ActivateNonDefault(client *http.Client) {
653	if Disabled() {
654		return
655	}
656
657	// save the custom client & it's RoundTripper
658	if _, ok := oldClients[client]; !ok {
659		oldClients[client] = client.Transport
660	}
661	client.Transport = DefaultTransport
662}
663
664// GetCallCountInfo gets the info on all the calls httpmock has caught
665// since it was activated or reset. The info is returned as a map of
666// the calling keys with the number of calls made to them as their
667// value. The key is the method, a space, and the url all concatenated
668// together.
669//
670// As a special case, regexp responders generate 2 entries for each
671// call. One for the call caught and the other for the rule that
672// matched. For example:
673//   RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
674//   http.Get("http://z.com")
675//
676// will generate the following result:
677//   map[string]int{
678//     `GET http://z.com`:  1,
679//     `GET =~z\.com\z`: 1,
680//   }
681func GetCallCountInfo() map[string]int {
682	return DefaultTransport.GetCallCountInfo()
683}
684
685// GetTotalCallCount gets the total number of calls httpmock has taken since it was activated or
686// reset.
687func GetTotalCallCount() int {
688	return DefaultTransport.GetTotalCallCount()
689}
690
691// Deactivate shuts down the mock environment.  Any HTTP calls made after this will use a live
692// transport.
693//
694// Usually you'll call it in a defer right after activating the mock environment:
695// 		func TestFetchArticles(t *testing.T) {
696// 			httpmock.Activate()
697// 			defer httpmock.Deactivate()
698//
699// 			// when this test ends, the mock environment will close
700// 		}
701func Deactivate() {
702	if Disabled() {
703		return
704	}
705	http.DefaultTransport = InitialTransport
706
707	// reset the custom clients to use their original RoundTripper
708	for oldClient, oldTransport := range oldClients {
709		oldClient.Transport = oldTransport
710		delete(oldClients, oldClient)
711	}
712}
713
714// Reset will remove any registered mocks and return the mock environment to it's initial state.
715func Reset() {
716	DefaultTransport.Reset()
717}
718
719// DeactivateAndReset is just a convenience method for calling Deactivate() and then Reset()
720// Happy deferring!
721func DeactivateAndReset() {
722	Deactivate()
723	Reset()
724}
725
726// RegisterResponder adds a new responder, associated with a given
727// HTTP method and URL (or path).
728//
729// When a request comes in that matches, the responder will be called
730// and the response returned to the client.
731//
732// If url contains query parameters, their order matters as well as
733// their content. All following URLs are here considered as different:
734//   http://z.tld?a=1&b=1
735//   http://z.tld?b=1&a=1
736//   http://z.tld?a&b
737//   http://z.tld?a=&b=
738//
739// If url begins with "=~", the following chars are considered as a
740// regular expression. If this regexp can not be compiled, it panics.
741// Note that the "=~" prefix remains in statistics returned by
742// GetCallCountInfo(). As 2 regexps can match the same URL, the regexp
743// responders are tested in the order they are registered. Registering
744// an already existing regexp responder (same method & same regexp
745// string) replaces its responder but does not change its position.
746//
747// See RegisterRegexpResponder() to directly pass a *regexp.Regexp.
748//
749// Example:
750// 		func TestFetchArticles(t *testing.T) {
751// 			httpmock.Activate()
752// 			defer httpmock.DeactivateAndReset()
753//
754// 			httpmock.RegisterResponder("GET", "http://example.com/",
755// 				httpmock.NewStringResponder(200, "hello world"))
756//
757// 			httpmock.RegisterResponder("GET", "/path/only",
758// 				httpmock.NewStringResponder("any host hello world", 200))
759//
760// 			httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`,
761// 				httpmock.NewStringResponder("any item get", 200))
762//
763// 			// requests to http://example.com/ will now return "hello world" and
764// 			// requests to any host with path /path/only will return "any host hello world"
765// 			// requests to any host with path matching ^/item/id/\d+\z regular expression will return "any item get"
766// 		}
767func RegisterResponder(method, url string, responder Responder) {
768	DefaultTransport.RegisterResponder(method, url, responder)
769}
770
771// RegisterRegexpResponder adds a new responder, associated with a given
772// HTTP method and URL (or path) regular expression.
773//
774// When a request comes in that matches, the responder will be called
775// and the response returned to the client.
776//
777// As 2 regexps can match the same URL, the regexp responders are
778// tested in the order they are registered. Registering an already
779// existing regexp responder (same method & same regexp string)
780// replaces its responder but does not change its position.
781//
782// A "=~" prefix is added to the stringified regexp in the statistics
783// returned by GetCallCountInfo().
784//
785// See RegisterResponder function and the "=~" prefix in its url
786// parameter to avoid compiling the regexp by yourself.
787func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) {
788	DefaultTransport.RegisterRegexpResponder(method, urlRegexp, responder)
789}
790
791// RegisterResponderWithQuery it is same as RegisterResponder, but
792// doesn't depends on query items order.
793//
794// query type can be:
795//   url.Values
796//   map[string]string
797//   string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function)
798//
799// If the query type is not recognized or the string cannot be parsed
800// using net/url.ParseQuery, a panic() occurs.
801//
802// Example using a net/url.Values:
803// 		func TestFetchArticles(t *testing.T) {
804// 			httpmock.Activate()
805// 			defer httpmock.DeactivateAndReset()
806//
807// 			expectedQuery := net.Values{
808// 				"a": []string{"3", "1", "8"},
809//				"b": []string{"4", "2"},
810//			}
811// 			httpmock.RegisterResponderWithQueryValues("GET", "http://example.com/", expectedQuery,
812// 				httpmock.NewStringResponder("hello world", 200))
813//
814//			// requests to http://example.com?a=1&a=3&a=8&b=2&b=4
815//			//      and to http://example.com?b=4&a=2&b=2&a=8&a=1
816//			// will now return 'hello world'
817// 		}
818//
819// or using a map[string]string:
820// 		func TestFetchArticles(t *testing.T) {
821// 			httpmock.Activate()
822// 			defer httpmock.DeactivateAndReset()
823//
824// 			expectedQuery := map[string]string{
825//				"a": "1",
826//				"b": "2"
827//			}
828// 			httpmock.RegisterResponderWithQuery("GET", "http://example.com/", expectedQuery,
829// 				httpmock.NewStringResponder("hello world", 200))
830//
831//			// requests to http://example.com?a=1&b=2 and http://example.com?b=2&a=1 will now return 'hello world'
832// 		}
833//
834// or using a query string:
835// 		func TestFetchArticles(t *testing.T) {
836// 			httpmock.Activate()
837// 			defer httpmock.DeactivateAndReset()
838//
839// 			expectedQuery := "a=3&b=4&b=2&a=1&a=8"
840// 			httpmock.RegisterResponderWithQueryValues("GET", "http://example.com/", expectedQuery,
841// 				httpmock.NewStringResponder("hello world", 200))
842//
843//			// requests to http://example.com?a=1&a=3&a=8&b=2&b=4
844//			//      and to http://example.com?b=4&a=2&b=2&a=8&a=1
845//			// will now return 'hello world'
846// 		}
847func RegisterResponderWithQuery(method, path string, query interface{}, responder Responder) {
848	DefaultTransport.RegisterResponderWithQuery(method, path, query, responder)
849}
850
851// RegisterNoResponder adds a mock that will be called whenever a request for an unregistered URL
852// is received.  The default behavior is to return a connection error.
853//
854// In some cases you may not want all URLs to be mocked, in which case you can do this:
855// 		func TestFetchArticles(t *testing.T) {
856// 			httpmock.Activate()
857// 			defer httpmock.DeactivateAndReset()
858//			httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip)
859//
860// 			// any requests that don't have a registered URL will be fetched normally
861// 		}
862func RegisterNoResponder(responder Responder) {
863	DefaultTransport.RegisterNoResponder(responder)
864}
865
866type submatchesKeyType struct{}
867
868var submatchesKey submatchesKeyType
869
870func setSubmatches(req *http.Request, submatches []string) *http.Request {
871	if len(submatches) > 0 {
872		return req.WithContext(context.WithValue(req.Context(), submatchesKey, submatches))
873	}
874	return req
875}
876
877// ErrSubmatchNotFound is the error returned by GetSubmatch* functions
878// when the given submatch index cannot be found.
879var ErrSubmatchNotFound = errors.New("submatch not found")
880
881// GetSubmatch has to be used in Responders installed by
882// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
883// allows to retrieve the n-th submatch of the matching regexp, as a
884// string. Example:
885// 	RegisterResponder("GET", `=~^/item/name/([^/]+)\z`,
886// 		func(req *http.Request) (*http.Response, error) {
887// 			name, err := GetSubmatch(req, 1) // 1=first regexp submatch
888// 			if err != nil {
889// 				return nil, err
890// 			}
891// 			return NewJsonResponse(200, map[string]interface{}{
892// 				"id":   123,
893// 				"name": name,
894// 			})
895// 		})
896//
897// It panics if n < 1. See MustGetSubmatch to avoid testing the
898// returned error.
899func GetSubmatch(req *http.Request, n int) (string, error) {
900	if n <= 0 {
901		panic(fmt.Sprintf("getting submatches starts at 1, not %d", n))
902	}
903	n--
904
905	submatches, ok := req.Context().Value(submatchesKey).([]string)
906	if !ok || n >= len(submatches) {
907		return "", ErrSubmatchNotFound
908	}
909	return submatches[n], nil
910}
911
912// GetSubmatchAsInt has to be used in Responders installed by
913// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
914// allows to retrieve the n-th submatch of the matching regexp, as an
915// int64. Example:
916// 	RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
917// 		func(req *http.Request) (*http.Response, error) {
918// 			id, err := GetSubmatchAsInt(req, 1) // 1=first regexp submatch
919// 			if err != nil {
920// 				return nil, err
921// 			}
922// 			return NewJsonResponse(200, map[string]interface{}{
923// 				"id":   id,
924// 				"name": "The beautiful name",
925// 			})
926// 		})
927//
928// It panics if n < 1. See MustGetSubmatchAsInt to avoid testing the
929// returned error.
930func GetSubmatchAsInt(req *http.Request, n int) (int64, error) {
931	sm, err := GetSubmatch(req, n)
932	if err != nil {
933		return 0, err
934	}
935	return strconv.ParseInt(sm, 10, 64)
936}
937
938// GetSubmatchAsUint has to be used in Responders installed by
939// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
940// allows to retrieve the n-th submatch of the matching regexp, as a
941// uint64. Example:
942// 	RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
943// 		func(req *http.Request) (*http.Response, error) {
944// 			id, err := GetSubmatchAsUint(req, 1) // 1=first regexp submatch
945// 			if err != nil {
946// 				return nil, err
947// 			}
948// 			return NewJsonResponse(200, map[string]interface{}{
949// 				"id":   id,
950// 				"name": "The beautiful name",
951// 			})
952// 		})
953//
954// It panics if n < 1. See MustGetSubmatchAsUint to avoid testing the
955// returned error.
956func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) {
957	sm, err := GetSubmatch(req, n)
958	if err != nil {
959		return 0, err
960	}
961	return strconv.ParseUint(sm, 10, 64)
962}
963
964// GetSubmatchAsFloat has to be used in Responders installed by
965// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
966// allows to retrieve the n-th submatch of the matching regexp, as a
967// float64. Example:
968// 	RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`,
969// 		func(req *http.Request) (*http.Response, error) {
970// 			height, err := GetSubmatchAsFloat(req, 1) // 1=first regexp submatch
971// 			if err != nil {
972// 				return nil, err
973// 			}
974// 			return NewJsonResponse(200, map[string]interface{}{
975// 				"id":     id,
976// 				"name":   "The beautiful name",
977// 				"height": height,
978// 			})
979// 		})
980//
981// It panics if n < 1. See MustGetSubmatchAsFloat to avoid testing the
982// returned error.
983func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) {
984	sm, err := GetSubmatch(req, n)
985	if err != nil {
986		return 0, err
987	}
988	return strconv.ParseFloat(sm, 64)
989}
990
991// MustGetSubmatch works as GetSubmatch except that it panics in case
992// of error (submatch not found). It has to be used in Responders
993// installed by RegisterRegexpResponder or RegisterResponder + "=~"
994// URL prefix. It allows to retrieve the n-th submatch of the matching
995// regexp, as a string. Example:
996// 	RegisterResponder("GET", `=~^/item/name/([^/]+)\z`,
997// 		func(req *http.Request) (*http.Response, error) {
998// 			name := MustGetSubmatch(req, 1) // 1=first regexp submatch
999// 			return NewJsonResponse(200, map[string]interface{}{
1000// 				"id":   123,
1001// 				"name": name,
1002// 			})
1003// 		})
1004//
1005// It panics if n < 1.
1006func MustGetSubmatch(req *http.Request, n int) string {
1007	s, err := GetSubmatch(req, n)
1008	if err != nil {
1009		panic("GetSubmatch failed: " + err.Error())
1010	}
1011	return s
1012}
1013
1014// MustGetSubmatchAsInt works as GetSubmatchAsInt except that it
1015// panics in case of error (submatch not found or invalid int64
1016// format). It has to be used in Responders installed by
1017// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
1018// allows to retrieve the n-th submatch of the matching regexp, as an
1019// int64. Example:
1020// 	RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
1021// 		func(req *http.Request) (*http.Response, error) {
1022// 			id := MustGetSubmatchAsInt(req, 1) // 1=first regexp submatch
1023// 			return NewJsonResponse(200, map[string]interface{}{
1024// 				"id":   id,
1025// 				"name": "The beautiful name",
1026// 			})
1027// 		})
1028//
1029// It panics if n < 1.
1030func MustGetSubmatchAsInt(req *http.Request, n int) int64 {
1031	i, err := GetSubmatchAsInt(req, n)
1032	if err != nil {
1033		panic("GetSubmatchAsInt failed: " + err.Error())
1034	}
1035	return i
1036}
1037
1038// MustGetSubmatchAsUint works as GetSubmatchAsUint except that it
1039// panics in case of error (submatch not found or invalid uint64
1040// format). It has to be used in Responders installed by
1041// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
1042// allows to retrieve the n-th submatch of the matching regexp, as a
1043// uint64. Example:
1044// 	RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
1045// 		func(req *http.Request) (*http.Response, error) {
1046// 			id, err := MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
1047// 			return NewJsonResponse(200, map[string]interface{}{
1048// 				"id":   id,
1049// 				"name": "The beautiful name",
1050// 			})
1051// 		})
1052//
1053// It panics if n < 1.
1054func MustGetSubmatchAsUint(req *http.Request, n int) uint64 {
1055	u, err := GetSubmatchAsUint(req, n)
1056	if err != nil {
1057		panic("GetSubmatchAsUint failed: " + err.Error())
1058	}
1059	return u
1060}
1061
1062// MustGetSubmatchAsFloat works as GetSubmatchAsFloat except that it
1063// panics in case of error (submatch not found or invalid float64
1064// format). It has to be used in Responders installed by
1065// RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
1066// allows to retrieve the n-th submatch of the matching regexp, as a
1067// float64. Example:
1068// 	RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`,
1069// 		func(req *http.Request) (*http.Response, error) {
1070// 			height := MustGetSubmatchAsFloat(req, 1) // 1=first regexp submatch
1071// 			return NewJsonResponse(200, map[string]interface{}{
1072// 				"id":     id,
1073// 				"name":   "The beautiful name",
1074// 				"height": height,
1075// 			})
1076// 		})
1077//
1078// It panics if n < 1.
1079func MustGetSubmatchAsFloat(req *http.Request, n int) float64 {
1080	f, err := GetSubmatchAsFloat(req, n)
1081	if err != nil {
1082		panic("GetSubmatchAsFloat failed: " + err.Error())
1083	}
1084	return f
1085}
1086