...

Source file src/github.com/jarcoal/httpmock/transport.go

Documentation: github.com/jarcoal/httpmock

     1  package httpmock
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  
    16  	"github.com/jarcoal/httpmock/internal"
    17  )
    18  
    19  const regexpPrefix = "=~"
    20  
    21  // NoResponderFound is returned when no responders are found for a
    22  // given HTTP method and URL.
    23  var NoResponderFound = internal.NoResponderFound
    24  
    25  var stdMethods = map[string]bool{
    26  	"CONNECT": true, // Section 9.9
    27  	"DELETE":  true, // Section 9.7
    28  	"GET":     true, // Section 9.3
    29  	"HEAD":    true, // Section 9.4
    30  	"OPTIONS": true, // Section 9.2
    31  	"POST":    true, // Section 9.5
    32  	"PUT":     true, // Section 9.6
    33  	"TRACE":   true, // Section 9.8
    34  }
    35  
    36  // methodProbablyWrong returns true if method has probably wrong case.
    37  func methodProbablyWrong(method string) bool {
    38  	return !stdMethods[method] && stdMethods[strings.ToUpper(method)]
    39  }
    40  
    41  // ConnectionFailure is a responder that returns a connection failure.
    42  // This is the default responder, and is called when no other matching
    43  // responder is found.
    44  func ConnectionFailure(*http.Request) (*http.Response, error) {
    45  	return nil, NoResponderFound
    46  }
    47  
    48  // NewMockTransport creates a new *MockTransport with no responders.
    49  func NewMockTransport() *MockTransport {
    50  	return &MockTransport{
    51  		responders:    make(map[internal.RouteKey]Responder),
    52  		callCountInfo: make(map[internal.RouteKey]int),
    53  	}
    54  }
    55  
    56  type regexpResponder struct {
    57  	origRx    string
    58  	method    string
    59  	rx        *regexp.Regexp
    60  	responder Responder
    61  }
    62  
    63  // MockTransport implements http.RoundTripper, which fulfills single
    64  // http requests issued by an http.Client.  This implementation
    65  // doesn't actually make the call, instead deferring to the registered
    66  // list of responders.
    67  type MockTransport struct {
    68  	// DontCheckMethod disables standard methods check. By default, if
    69  	// a responder is registered using a lower-cased method among CONNECT,
    70  	// DELETE, GET, HEAD, OPTIONS, POST, PUT and TRACE, a panic occurs
    71  	// as it is probably a mistake.
    72  	DontCheckMethod  bool
    73  	mu               sync.RWMutex
    74  	responders       map[internal.RouteKey]Responder
    75  	regexpResponders []regexpResponder
    76  	noResponder      Responder
    77  	callCountInfo    map[internal.RouteKey]int
    78  	totalCallCount   int
    79  }
    80  
    81  func (m *MockTransport) findResponder(method string, url *url.URL) (
    82  	responder Responder,
    83  	key, respKey internal.RouteKey,
    84  	submatches []string,
    85  ) {
    86  	urlStr := url.String()
    87  	key = internal.RouteKey{
    88  		Method: method,
    89  	}
    90  	for _, getResponder := range []func(internal.RouteKey) (Responder, internal.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 = urlStr
    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(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(urlStr, 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 := *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 := urlStr != 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 := url.RawPath
   139  		if pathAlone == "" {
   140  			pathAlone = url.Path
   141  		}
   142  
   143  		// First with unsorted querystring: /path?q...
   144  		if hasQueryString {
   145  			key.URL = pathAlone + strings.TrimPrefix(urlStr, surl) // concat after-path part
   146  			responder, respKey, submatches = getResponder(key)
   147  			if responder != nil {
   148  				break
   149  			}
   150  
   151  			// Then with sorted querystring: /path?sorted_q...
   152  			key.URL = pathAlone + "?" + sortedQuery(url.Query())
   153  			if url.Fragment != "" {
   154  				key.URL += "#" + url.Fragment
   155  			}
   156  			responder, respKey, submatches = getResponder(key)
   157  			if responder != nil {
   158  				break
   159  			}
   160  		}
   161  
   162  		// Then using path alone: /path
   163  		key.URL = pathAlone
   164  		responder, respKey, submatches = getResponder(key)
   165  		if responder != nil {
   166  			break
   167  		}
   168  	}
   169  	return
   170  }
   171  
   172  // RoundTrip receives HTTP requests and routes them to the appropriate
   173  // responder.  It is required to implement the http.RoundTripper
   174  // interface.  You will not interact with this directly, instead the
   175  // *http.Client you are using will call it for you.
   176  func (m *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   177  	method := req.Method
   178  	if method == "" {
   179  		// http.Request.Method is documented to default to GET:
   180  		method = http.MethodGet
   181  	}
   182  
   183  	var suggested *internal.ErrorNoResponderFoundMistake
   184  
   185  	responder, key, respKey, submatches := m.findResponder(method, req.URL)
   186  	if responder == nil {
   187  		// Responder not found, try to detect some common user mistakes on
   188  		// method then on path
   189  		var altResp Responder
   190  		var altKey internal.RouteKey
   191  
   192  		// On method first
   193  		if methodProbablyWrong(method) {
   194  			// Get → GET
   195  			altResp, _, altKey, _ = m.findResponder(strings.ToUpper(method), req.URL)
   196  		}
   197  		if altResp == nil {
   198  			// Search for any other method
   199  			altResp, _, altKey, _ = m.findResponder("", req.URL)
   200  		}
   201  		if altResp != nil {
   202  			suggested = &internal.ErrorNoResponderFoundMistake{
   203  				Kind:      "method",
   204  				Orig:      method,
   205  				Suggested: altKey.Method,
   206  			}
   207  		} else {
   208  			// Then on path
   209  			if altResp == nil && strings.HasSuffix(req.URL.Path, "/") {
   210  				// Try without final "/"
   211  				u := *req.URL
   212  				u.Path = strings.TrimSuffix(u.Path, "/")
   213  				altResp, _, altKey, _ = m.findResponder("", &u)
   214  			}
   215  			if altResp == nil && strings.Contains(req.URL.Path, "//") {
   216  				// Try without double "/"
   217  				u := *req.URL
   218  				squash := false
   219  				u.Path = strings.Map(func(r rune) rune {
   220  					if r == '/' {
   221  						if squash {
   222  							return -1
   223  						}
   224  						squash = true
   225  					} else {
   226  						squash = false
   227  					}
   228  					return r
   229  				}, u.Path)
   230  				altResp, _, altKey, _ = m.findResponder("", &u)
   231  			}
   232  			if altResp != nil {
   233  				suggested = &internal.ErrorNoResponderFoundMistake{
   234  					Kind:      "URL",
   235  					Orig:      req.URL.String(),
   236  					Suggested: altKey.URL,
   237  				}
   238  			}
   239  		}
   240  	}
   241  
   242  	m.mu.Lock()
   243  	// if we found a responder, call it
   244  	if responder != nil {
   245  		m.callCountInfo[key]++
   246  		if key != respKey {
   247  			m.callCountInfo[respKey]++
   248  		}
   249  		m.totalCallCount++
   250  	} else if m.noResponder != nil {
   251  		// we didn't find a responder, so fire the 'no responder' responder
   252  		m.callCountInfo[internal.NoResponder]++
   253  		m.totalCallCount++
   254  
   255  		// give a hint to NewNotFoundResponder() if it is a possible
   256  		// method or URL error
   257  		if suggested != nil {
   258  			req = req.WithContext(context.WithValue(req.Context(), suggestedKey, &suggestedInfo{
   259  				kind:      suggested.Kind,
   260  				suggested: suggested.Suggested,
   261  			}))
   262  		}
   263  		responder = m.noResponder
   264  	}
   265  	m.mu.Unlock()
   266  
   267  	if responder == nil {
   268  		if suggested != nil {
   269  			return nil, suggested
   270  		}
   271  		return ConnectionFailure(req)
   272  	}
   273  	return runCancelable(responder, internal.SetSubmatches(req, submatches))
   274  }
   275  
   276  // NumResponders returns the number of responders currently in use.
   277  // The responder registered with RegisterNoResponder() is not taken
   278  // into account.
   279  func (m *MockTransport) NumResponders() int {
   280  	m.mu.RLock()
   281  	defer m.mu.RUnlock()
   282  	return len(m.responders) + len(m.regexpResponders)
   283  }
   284  
   285  // Responders returns the list of currently registered responders.
   286  // Each responder is listed as a string containing "METHOD URL".
   287  // Non-regexp responders are listed first in alphabetical order
   288  // (sorted by URL then METHOD), then regexp responders in the order
   289  // they have been registered.
   290  // The responder registered with RegisterNoResponder() is not listed.
   291  func (m *MockTransport) Responders() []string {
   292  	m.mu.RLock()
   293  	defer m.mu.RUnlock()
   294  
   295  	rks := make([]internal.RouteKey, 0, len(m.responders))
   296  	for rk := range m.responders {
   297  		rks = append(rks, rk)
   298  	}
   299  	sort.Slice(rks, func(i, j int) bool {
   300  		if rks[i].URL == rks[j].URL {
   301  			return rks[i].Method < rks[j].Method
   302  		}
   303  		return rks[i].URL < rks[j].URL
   304  	})
   305  
   306  	rs := make([]string, 0, len(m.responders)+len(m.regexpResponders))
   307  	for _, rk := range rks {
   308  		rs = append(rs, rk.String())
   309  	}
   310  	for _, rr := range m.regexpResponders {
   311  		rs = append(rs, rr.method+" "+rr.origRx)
   312  	}
   313  	return rs
   314  }
   315  
   316  func runCancelable(responder Responder, req *http.Request) (*http.Response, error) {
   317  	ctx := req.Context()
   318  	if req.Cancel == nil && ctx.Done() == nil { // nolint: staticcheck
   319  		resp, err := responder(req)
   320  		return resp, internal.CheckStackTracer(req, err)
   321  	}
   322  
   323  	// Set up a goroutine that translates a close(req.Cancel) into a
   324  	// "request canceled" error, and another one that runs the
   325  	// responder. Then race them: first to the result channel wins.
   326  
   327  	type result struct {
   328  		response *http.Response
   329  		err      error
   330  	}
   331  	resultch := make(chan result, 1)
   332  	done := make(chan struct{}, 1)
   333  
   334  	go func() {
   335  		select {
   336  		case <-req.Cancel: // nolint: staticcheck
   337  			resultch <- result{
   338  				response: nil,
   339  				err:      errors.New("request canceled"),
   340  			}
   341  		case <-ctx.Done():
   342  			resultch <- result{
   343  				response: nil,
   344  				err:      ctx.Err(),
   345  			}
   346  		case <-done:
   347  		}
   348  	}()
   349  
   350  	go func() {
   351  		defer func() {
   352  			if err := recover(); err != nil {
   353  				resultch <- result{
   354  					response: nil,
   355  					err:      fmt.Errorf("panic in responder: got %q", err),
   356  				}
   357  			}
   358  		}()
   359  
   360  		response, err := responder(req)
   361  		resultch <- result{
   362  			response: response,
   363  			err:      err,
   364  		}
   365  	}()
   366  
   367  	r := <-resultch
   368  
   369  	// if a cancel() issued from context.WithCancel() or a
   370  	// close(req.Cancel) are never coming, we'll need to unblock the
   371  	// first goroutine.
   372  	done <- struct{}{}
   373  
   374  	return r.response, internal.CheckStackTracer(req, r.err)
   375  }
   376  
   377  // responderForKey returns a responder for a given key.
   378  func (m *MockTransport) responderForKey(key internal.RouteKey) (Responder, internal.RouteKey, []string) {
   379  	m.mu.RLock()
   380  	defer m.mu.RUnlock()
   381  	if key.Method != "" {
   382  		return m.responders[key], key, nil
   383  	}
   384  
   385  	for k, resp := range m.responders {
   386  		if key.URL == k.URL {
   387  			return resp, k, nil
   388  		}
   389  	}
   390  	return nil, key, nil
   391  }
   392  
   393  // responderForKeyUsingRegexp returns the first responder matching a
   394  // given key using regexps.
   395  func (m *MockTransport) regexpResponderForKey(key internal.RouteKey) (Responder, internal.RouteKey, []string) {
   396  	m.mu.RLock()
   397  	defer m.mu.RUnlock()
   398  	for _, regInfo := range m.regexpResponders {
   399  		if key.Method == "" || regInfo.method == key.Method {
   400  			if sm := regInfo.rx.FindStringSubmatch(key.URL); sm != nil {
   401  				if len(sm) == 1 {
   402  					sm = nil
   403  				} else {
   404  					sm = sm[1:]
   405  				}
   406  				return regInfo.responder, internal.RouteKey{
   407  					Method: regInfo.method,
   408  					URL:    regInfo.origRx,
   409  				}, sm
   410  			}
   411  		}
   412  	}
   413  	return nil, key, nil
   414  }
   415  
   416  func isRegexpURL(url string) bool {
   417  	return strings.HasPrefix(url, regexpPrefix)
   418  }
   419  
   420  func (m *MockTransport) checkMethod(method string) {
   421  	if !m.DontCheckMethod && methodProbablyWrong(method) {
   422  		panic(fmt.Sprintf("You probably want to use method %q instead of %q? If not and so want to disable this check, set MockTransport.DontCheckMethod field to true",
   423  			strings.ToUpper(method),
   424  			method,
   425  		))
   426  	}
   427  }
   428  
   429  // RegisterResponder adds a new responder, associated with a given
   430  // HTTP method and URL (or path).
   431  //
   432  // When a request comes in that matches, the responder is called and
   433  // the response returned to the client.
   434  //
   435  // If url contains query parameters, their order matters as well as
   436  // their content. All following URLs are here considered as different:
   437  //   http://z.tld?a=1&b=1
   438  //   http://z.tld?b=1&a=1
   439  //   http://z.tld?a&b
   440  //   http://z.tld?a=&b=
   441  //
   442  // If url begins with "=~", the following chars are considered as a
   443  // regular expression. If this regexp can not be compiled, it panics.
   444  // Note that the "=~" prefix remains in statistics returned by
   445  // GetCallCountInfo(). As 2 regexps can match the same URL, the regexp
   446  // responders are tested in the order they are registered. Registering
   447  // an already existing regexp responder (same method & same regexp
   448  // string) replaces its responder, but does not change its position.
   449  //
   450  // Registering an already existing responder resets the corresponding
   451  // statistics as returned by GetCallCountInfo().
   452  //
   453  // Registering a nil Responder removes the existing one and the
   454  // corresponding statistics as returned by GetCallCountInfo(). It does
   455  // nothing if it does not already exist.
   456  //
   457  // See RegisterRegexpResponder() to directly pass a *regexp.Regexp.
   458  //
   459  // Example:
   460  //   func TestFetchArticles(t *testing.T) {
   461  //     httpmock.Activate()
   462  //     defer httpmock.DeactivateAndReset()
   463  //
   464  //     httpmock.RegisterResponder("GET", "http://example.com/",
   465  //       httpmock.NewStringResponder(200, "hello world"))
   466  //
   467  //     httpmock.RegisterResponder("GET", "/path/only",
   468  //       httpmock.NewStringResponder("any host hello world", 200))
   469  //
   470  //     httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`,
   471  //       httpmock.NewStringResponder("any item get", 200))
   472  //
   473  //     // requests to http://example.com/ now return "hello world" and
   474  //     // requests to any host with path /path/only return "any host hello world"
   475  //     // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get"
   476  //   }
   477  //
   478  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
   479  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
   480  // mistake. This panic can be disabled by setting m.DontCheckMethod to
   481  // true prior to this call.
   482  func (m *MockTransport) RegisterResponder(method, url string, responder Responder) {
   483  	m.checkMethod(method)
   484  
   485  	if isRegexpURL(url) {
   486  		m.registerRegexpResponder(regexpResponder{
   487  			origRx:    url,
   488  			method:    method,
   489  			rx:        regexp.MustCompile(url[2:]),
   490  			responder: responder,
   491  		})
   492  		return
   493  	}
   494  
   495  	key := internal.RouteKey{
   496  		Method: method,
   497  		URL:    url,
   498  	}
   499  
   500  	m.mu.Lock()
   501  	if responder == nil {
   502  		delete(m.responders, key)
   503  		delete(m.callCountInfo, key)
   504  	} else {
   505  		m.responders[key] = responder
   506  		m.callCountInfo[key] = 0
   507  	}
   508  	m.mu.Unlock()
   509  }
   510  
   511  func (m *MockTransport) registerRegexpResponder(rxResp regexpResponder) {
   512  	m.mu.Lock()
   513  	defer m.mu.Unlock()
   514  
   515  found:
   516  	for {
   517  		for i, rr := range m.regexpResponders {
   518  			if rr.method == rxResp.method && rr.origRx == rxResp.origRx {
   519  				if rxResp.responder == nil {
   520  					copy(m.regexpResponders[:i], m.regexpResponders[i+1:])
   521  					m.regexpResponders[len(m.regexpResponders)-1] = regexpResponder{}
   522  					m.regexpResponders = m.regexpResponders[:len(m.regexpResponders)-1]
   523  				} else {
   524  					m.regexpResponders[i] = rxResp
   525  				}
   526  				break found
   527  			}
   528  		}
   529  		if rxResp.responder != nil {
   530  			m.regexpResponders = append(m.regexpResponders, rxResp)
   531  		}
   532  		break // nolint: staticcheck
   533  	}
   534  
   535  	key := internal.RouteKey{
   536  		Method: rxResp.method,
   537  		URL:    rxResp.origRx,
   538  	}
   539  	if rxResp.responder == nil {
   540  		delete(m.callCountInfo, key)
   541  	} else {
   542  		m.callCountInfo[key] = 0
   543  	}
   544  }
   545  
   546  // RegisterRegexpResponder adds a new responder, associated with a given
   547  // HTTP method and URL (or path) regular expression.
   548  //
   549  // When a request comes in that matches, the responder is called and
   550  // the response returned to the client.
   551  //
   552  // As 2 regexps can match the same URL, the regexp responders are
   553  // tested in the order they are registered. Registering an already
   554  // existing regexp responder (same method & same regexp string)
   555  // replaces its responder, but does not change its position, and
   556  // resets the corresponding statistics as returned by GetCallCountInfo().
   557  //
   558  // Registering a nil Responder removes the existing one and the
   559  // corresponding statistics as returned by GetCallCountInfo(). It does
   560  // nothing if it does not already exist.
   561  //
   562  // A "=~" prefix is added to the stringified regexp in the statistics
   563  // returned by GetCallCountInfo().
   564  //
   565  // See RegisterResponder function and the "=~" prefix in its url
   566  // parameter to avoid compiling the regexp by yourself.
   567  //
   568  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
   569  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
   570  // mistake. This panic can be disabled by setting m.DontCheckMethod to
   571  // true prior to this call.
   572  func (m *MockTransport) RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) {
   573  	m.checkMethod(method)
   574  
   575  	m.registerRegexpResponder(regexpResponder{
   576  		origRx:    regexpPrefix + urlRegexp.String(),
   577  		method:    method,
   578  		rx:        urlRegexp,
   579  		responder: responder,
   580  	})
   581  }
   582  
   583  // RegisterResponderWithQuery is same as RegisterResponder, but it
   584  // doesn't depend on query items order.
   585  //
   586  // If query is non-nil, its type can be:
   587  //   url.Values
   588  //   map[string]string
   589  //   string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function)
   590  //
   591  // If the query type is not recognized or the string cannot be parsed
   592  // using net/url.ParseQuery, a panic() occurs.
   593  //
   594  // Unlike RegisterResponder, path cannot be prefixed by "=~" to say it
   595  // is a regexp. If it is, a panic occurs.
   596  //
   597  // Registering an already existing responder resets the corresponding
   598  // statistics as returned by GetCallCountInfo().
   599  //
   600  // Registering a nil Responder removes the existing one and the
   601  // corresponding statistics as returned by GetCallCountInfo(). It does
   602  // nothing if it does not already exist.
   603  //
   604  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
   605  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
   606  // mistake. This panic can be disabled by setting m.DontCheckMethod to
   607  // true prior to this call.
   608  func (m *MockTransport) RegisterResponderWithQuery(method, path string, query any, responder Responder) {
   609  	if isRegexpURL(path) {
   610  		panic(`path begins with "=~", RegisterResponder should be used instead of RegisterResponderWithQuery`)
   611  	}
   612  
   613  	var mapQuery url.Values
   614  	switch q := query.(type) {
   615  	case url.Values:
   616  		mapQuery = q
   617  
   618  	case map[string]string:
   619  		mapQuery = make(url.Values, len(q))
   620  		for key, e := range q {
   621  			mapQuery[key] = []string{e}
   622  		}
   623  
   624  	case string:
   625  		var err error
   626  		mapQuery, err = url.ParseQuery(q)
   627  		if err != nil {
   628  			panic("RegisterResponderWithQuery bad query string: " + err.Error())
   629  		}
   630  
   631  	default:
   632  		if query != nil {
   633  			panic(fmt.Sprintf("RegisterResponderWithQuery bad query type %T. Only url.Values, map[string]string and string are allowed", query))
   634  		}
   635  	}
   636  
   637  	if queryString := sortedQuery(mapQuery); queryString != "" {
   638  		path += "?" + queryString
   639  	}
   640  	m.RegisterResponder(method, path, responder)
   641  }
   642  
   643  func sortedQuery(m url.Values) string {
   644  	if len(m) == 0 {
   645  		return ""
   646  	}
   647  
   648  	keys := make([]string, 0, len(m))
   649  	for k := range m {
   650  		keys = append(keys, k)
   651  	}
   652  	sort.Strings(keys)
   653  
   654  	var b bytes.Buffer
   655  	var values []string // nolint: prealloc
   656  
   657  	for _, k := range keys {
   658  		// Do not alter the passed url.Values
   659  		values = append(values, m[k]...)
   660  		sort.Strings(values)
   661  
   662  		k = url.QueryEscape(k)
   663  
   664  		for _, v := range values {
   665  			if b.Len() > 0 {
   666  				b.WriteByte('&')
   667  			}
   668  			fmt.Fprintf(&b, "%v=%v", k, url.QueryEscape(v))
   669  		}
   670  
   671  		values = values[:0]
   672  	}
   673  
   674  	return b.String()
   675  }
   676  
   677  // RegisterNoResponder is used to register a responder that is called
   678  // if no other responder is found.  The default is httpmock.ConnectionFailure
   679  // that returns an error able to indicate a possible method mismatch.
   680  //
   681  // Use it in conjunction with NewNotFoundResponder to ensure that all
   682  // routes have been mocked:
   683  //
   684  //   import (
   685  //     "testing"
   686  //     "github.com/jarcoal/httpmock"
   687  //   )
   688  //   ...
   689  //   func TestMyApp(t *testing.T) {
   690  //      ...
   691  //      // Calls testing.Fatal with the name of Responder-less route and
   692  //      // the stack trace of the call.
   693  //      httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal))
   694  //
   695  // Will abort the current test and print something like:
   696  //   transport_test.go:735: Called from net/http.Get()
   697  //         at /go/src/github.com/jarcoal/httpmock/transport_test.go:714
   698  //       github.com/jarcoal/httpmock.TestCheckStackTracer()
   699  //         at /go/src/testing/testing.go:865
   700  //       testing.tRunner()
   701  //         at /go/src/runtime/asm_amd64.s:1337
   702  //
   703  // If responder is passed as nil, the default behavior
   704  // (httpmock.ConnectionFailure) is re-enabled.
   705  func (m *MockTransport) RegisterNoResponder(responder Responder) {
   706  	m.mu.Lock()
   707  	m.noResponder = responder
   708  	m.mu.Unlock()
   709  }
   710  
   711  // Reset removes all registered responders (including the no
   712  // responder) from the MockTransport. It zeroes call counters too.
   713  func (m *MockTransport) Reset() {
   714  	m.mu.Lock()
   715  	m.responders = make(map[internal.RouteKey]Responder)
   716  	m.regexpResponders = nil
   717  	m.noResponder = nil
   718  	m.callCountInfo = make(map[internal.RouteKey]int)
   719  	m.totalCallCount = 0
   720  	m.mu.Unlock()
   721  }
   722  
   723  // ZeroCallCounters zeroes call counters without touching registered responders.
   724  func (m *MockTransport) ZeroCallCounters() {
   725  	m.mu.Lock()
   726  	for k := range m.callCountInfo {
   727  		m.callCountInfo[k] = 0
   728  	}
   729  	m.totalCallCount = 0
   730  	m.mu.Unlock()
   731  }
   732  
   733  // GetCallCountInfo gets the info on all the calls httpmock has caught
   734  // since it was activated or reset. The info is returned as a map of
   735  // the calling keys with the number of calls made to them as their
   736  // value. The key is the method, a space, and the url all concatenated
   737  // together.
   738  //
   739  // As a special case, regexp responders generate 2 entries for each
   740  // call. One for the call caught and the other for the rule that
   741  // matched. For example:
   742  //   RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
   743  //   http.Get("http://z.com")
   744  //
   745  // will generate the following result:
   746  //   map[string]int{
   747  //     `GET http://z.com`: 1,
   748  //     `GET =~z\.com\z`:   1,
   749  //   }
   750  func (m *MockTransport) GetCallCountInfo() map[string]int {
   751  	m.mu.RLock()
   752  	res := make(map[string]int, len(m.callCountInfo))
   753  	for k, v := range m.callCountInfo {
   754  		res[k.String()] = v
   755  	}
   756  	m.mu.RUnlock()
   757  	return res
   758  }
   759  
   760  // GetTotalCallCount returns the totalCallCount.
   761  func (m *MockTransport) GetTotalCallCount() int {
   762  	m.mu.RLock()
   763  	defer m.mu.RUnlock()
   764  	return m.totalCallCount
   765  }
   766  
   767  // DefaultTransport is the default mock transport used by Activate,
   768  // Deactivate, Reset, DeactivateAndReset, RegisterResponder, and
   769  // RegisterNoResponder.
   770  var DefaultTransport = NewMockTransport()
   771  
   772  // InitialTransport is a cache of the original transport used so we
   773  // can put it back when Deactivate is called.
   774  var InitialTransport = http.DefaultTransport
   775  
   776  // oldClients is used to handle custom http clients (i.e clients other
   777  // than http.DefaultClient).
   778  var oldClients = map[*http.Client]http.RoundTripper{}
   779  
   780  // oldClientsLock protects oldClients from concurrent writes.
   781  var oldClientsLock sync.Mutex
   782  
   783  // Activate starts the mock environment.  This should be called before
   784  // your tests run.  Under the hood this replaces the Transport on the
   785  // http.DefaultClient with httpmock.DefaultTransport.
   786  //
   787  // To enable mocks for a test, simply activate at the beginning of a test:
   788  //   func TestFetchArticles(t *testing.T) {
   789  //     httpmock.Activate()
   790  //     // all http requests using http.DefaultTransport will now be intercepted
   791  //   }
   792  //
   793  // If you want all of your tests in a package to be mocked, just call
   794  // Activate from init():
   795  //   func init() {
   796  //     httpmock.Activate()
   797  //   }
   798  //
   799  // or using a TestMain function:
   800  //   func TestMain(m *testing.M) {
   801  //     httpmock.Activate()
   802  //     os.Exit(m.Run())
   803  //   }
   804  func Activate() {
   805  	if Disabled() {
   806  		return
   807  	}
   808  
   809  	// make sure that if Activate is called multiple times it doesn't
   810  	// overwrite the InitialTransport with a mock transport.
   811  	if http.DefaultTransport != DefaultTransport {
   812  		InitialTransport = http.DefaultTransport
   813  	}
   814  
   815  	http.DefaultTransport = DefaultTransport
   816  }
   817  
   818  // ActivateNonDefault starts the mock environment with a non-default
   819  // http.Client.  This emulates the Activate function, but allows for
   820  // custom clients that do not use http.DefaultTransport
   821  //
   822  // To enable mocks for a test using a custom client, activate at the
   823  // beginning of a test:
   824  //   client := &http.Client{Transport: &http.Transport{TLSHandshakeTimeout: 60 * time.Second}}
   825  //   httpmock.ActivateNonDefault(client)
   826  func ActivateNonDefault(client *http.Client) {
   827  	if Disabled() {
   828  		return
   829  	}
   830  
   831  	// save the custom client & it's RoundTripper
   832  	oldClientsLock.Lock()
   833  	defer oldClientsLock.Unlock()
   834  	if _, ok := oldClients[client]; !ok {
   835  		oldClients[client] = client.Transport
   836  	}
   837  	client.Transport = DefaultTransport
   838  }
   839  
   840  // GetCallCountInfo gets the info on all the calls httpmock has caught
   841  // since it was activated or reset. The info is returned as a map of
   842  // the calling keys with the number of calls made to them as their
   843  // value. The key is the method, a space, and the url all concatenated
   844  // together.
   845  //
   846  // As a special case, regexp responders generate 2 entries for each
   847  // call. One for the call caught and the other for the rule that
   848  // matched. For example:
   849  //   RegisterResponder("GET", `=~z\.com\z`, NewStringResponder(200, "body"))
   850  //   http.Get("http://z.com")
   851  //
   852  // will generate the following result:
   853  //   map[string]int{
   854  //     `GET http://z.com`: 1,
   855  //     `GET =~z\.com\z`:   1,
   856  //   }
   857  func GetCallCountInfo() map[string]int {
   858  	return DefaultTransport.GetCallCountInfo()
   859  }
   860  
   861  // GetTotalCallCount gets the total number of calls httpmock has taken
   862  // since it was activated or reset.
   863  func GetTotalCallCount() int {
   864  	return DefaultTransport.GetTotalCallCount()
   865  }
   866  
   867  // Deactivate shuts down the mock environment.  Any HTTP calls made
   868  // after this will use a live transport.
   869  //
   870  // Usually you'll call it in a defer right after activating the mock
   871  // environment:
   872  //   func TestFetchArticles(t *testing.T) {
   873  //     httpmock.Activate()
   874  //     defer httpmock.Deactivate()
   875  //
   876  //     // when this test ends, the mock environment will close
   877  //   }
   878  //
   879  // Since go 1.14 you can also use (*testing.T).Cleanup() method as in:
   880  //   func TestFetchArticles(t *testing.T) {
   881  //     httpmock.Activate()
   882  //     t.Cleanup(httpmock.Deactivate)
   883  //
   884  //     // when this test ends, the mock environment will close
   885  //   }
   886  //
   887  // useful in test helpers to save your callers from calling defer themselves.
   888  func Deactivate() {
   889  	if Disabled() {
   890  		return
   891  	}
   892  	http.DefaultTransport = InitialTransport
   893  
   894  	// reset the custom clients to use their original RoundTripper
   895  	oldClientsLock.Lock()
   896  	defer oldClientsLock.Unlock()
   897  	for oldClient, oldTransport := range oldClients {
   898  		oldClient.Transport = oldTransport
   899  		delete(oldClients, oldClient)
   900  	}
   901  }
   902  
   903  // Reset removes any registered mocks and returns the mock
   904  // environment to its initial state. It zeroes call counters too.
   905  func Reset() {
   906  	DefaultTransport.Reset()
   907  }
   908  
   909  // ZeroCallCounters zeroes call counters without touching registered responders.
   910  func ZeroCallCounters() {
   911  	DefaultTransport.ZeroCallCounters()
   912  }
   913  
   914  // DeactivateAndReset is just a convenience method for calling
   915  // Deactivate() and then Reset().
   916  //
   917  // Happy deferring!
   918  func DeactivateAndReset() {
   919  	Deactivate()
   920  	Reset()
   921  }
   922  
   923  // RegisterResponder adds a new responder, associated with a given
   924  // HTTP method and URL (or path).
   925  //
   926  // When a request comes in that matches, the responder is called and
   927  // the response returned to the client.
   928  //
   929  // If url contains query parameters, their order matters as well as
   930  // their content. All following URLs are here considered as different:
   931  //   http://z.tld?a=1&b=1
   932  //   http://z.tld?b=1&a=1
   933  //   http://z.tld?a&b
   934  //   http://z.tld?a=&b=
   935  //
   936  // If url begins with "=~", the following chars are considered as a
   937  // regular expression. If this regexp can not be compiled, it panics.
   938  // Note that the "=~" prefix remains in statistics returned by
   939  // GetCallCountInfo(). As 2 regexps can match the same URL, the regexp
   940  // responders are tested in the order they are registered. Registering
   941  // an already existing regexp responder (same method & same regexp
   942  // string) replaces its responder, but does not change its position.
   943  //
   944  // Registering an already existing responder resets the corresponding
   945  // statistics as returned by GetCallCountInfo().
   946  //
   947  // Registering a nil Responder removes the existing one and the
   948  // corresponding statistics as returned by GetCallCountInfo(). It does
   949  // nothing if it does not already exist.
   950  //
   951  // See RegisterRegexpResponder() to directly pass a *regexp.Regexp.
   952  //
   953  // Example:
   954  //   func TestFetchArticles(t *testing.T) {
   955  //     httpmock.Activate()
   956  //     defer httpmock.DeactivateAndReset()
   957  //
   958  //     httpmock.RegisterResponder("GET", "http://example.com/",
   959  //       httpmock.NewStringResponder(200, "hello world"))
   960  //
   961  //     httpmock.RegisterResponder("GET", "/path/only",
   962  //       httpmock.NewStringResponder("any host hello world", 200))
   963  //
   964  //     httpmock.RegisterResponder("GET", `=~^/item/id/\d+\z`,
   965  //       httpmock.NewStringResponder("any item get", 200))
   966  //
   967  //     // requests to http://example.com/ now return "hello world" and
   968  //     // requests to any host with path /path/only return "any host hello world"
   969  //     // requests to any host with path matching ^/item/id/\d+\z regular expression return "any item get"
   970  //   }
   971  //
   972  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
   973  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
   974  // mistake. This panic can be disabled by setting
   975  // DefaultTransport.DontCheckMethod to true prior to this call.
   976  func RegisterResponder(method, url string, responder Responder) {
   977  	DefaultTransport.RegisterResponder(method, url, responder)
   978  }
   979  
   980  // RegisterRegexpResponder adds a new responder, associated with a given
   981  // HTTP method and URL (or path) regular expression.
   982  //
   983  // When a request comes in that matches, the responder is called and
   984  // the response returned to the client.
   985  //
   986  // As 2 regexps can match the same URL, the regexp responders are
   987  // tested in the order they are registered. Registering an already
   988  // existing regexp responder (same method & same regexp string)
   989  // replaces its responder, but does not change its position, and
   990  // resets the corresponding statistics as returned by GetCallCountInfo().
   991  //
   992  // Registering a nil Responder removes the existing one and the
   993  // corresponding statistics as returned by GetCallCountInfo(). It does
   994  // nothing if it does not already exist.
   995  //
   996  // A "=~" prefix is added to the stringified regexp in the statistics
   997  // returned by GetCallCountInfo().
   998  //
   999  // See RegisterResponder function and the "=~" prefix in its url
  1000  // parameter to avoid compiling the regexp by yourself.
  1001  //
  1002  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
  1003  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
  1004  // mistake. This panic can be disabled by setting
  1005  // DefaultTransport.DontCheckMethod to true prior to this call.
  1006  func RegisterRegexpResponder(method string, urlRegexp *regexp.Regexp, responder Responder) {
  1007  	DefaultTransport.RegisterRegexpResponder(method, urlRegexp, responder)
  1008  }
  1009  
  1010  // RegisterResponderWithQuery it is same as RegisterResponder, but
  1011  // doesn't depends on query items order.
  1012  //
  1013  // query type can be:
  1014  //   url.Values
  1015  //   map[string]string
  1016  //   string, a query string like "a=12&a=13&b=z&c" (see net/url.ParseQuery function)
  1017  //
  1018  // If the query type is not recognized or the string cannot be parsed
  1019  // using net/url.ParseQuery, a panic() occurs.
  1020  //
  1021  // Unlike RegisterResponder, path cannot be prefixed by "=~" to say it
  1022  // is a regexp. If it is, a panic occurs.
  1023  //
  1024  // Registering an already existing responder resets the corresponding
  1025  // statistics as returned by GetCallCountInfo().
  1026  //
  1027  // Registering a nil Responder removes the existing one and the
  1028  // corresponding statistics as returned by GetCallCountInfo(). It does
  1029  // nothing if it does not already exist.
  1030  //
  1031  // Example using a net/url.Values:
  1032  //   func TestFetchArticles(t *testing.T) {
  1033  //     httpmock.Activate()
  1034  //     defer httpmock.DeactivateAndReset()
  1035  //
  1036  //     expectedQuery := net.Values{
  1037  //       "a": []string{"3", "1", "8"},
  1038  //       "b": []string{"4", "2"},
  1039  //     }
  1040  //     httpmock.RegisterResponderWithQueryValues(
  1041  //       "GET", "http://example.com/", expectedQuery,
  1042  //       httpmock.NewStringResponder("hello world", 200))
  1043  //
  1044  //     // requests to http://example.com?a=1&a=3&a=8&b=2&b=4
  1045  //     //      and to http://example.com?b=4&a=2&b=2&a=8&a=1
  1046  //     // now return 'hello world'
  1047  //   }
  1048  //
  1049  // or using a map[string]string:
  1050  //   func TestFetchArticles(t *testing.T) {
  1051  //     httpmock.Activate()
  1052  //     defer httpmock.DeactivateAndReset()
  1053  //
  1054  //     expectedQuery := map[string]string{
  1055  //       "a": "1",
  1056  //       "b": "2"
  1057  //     }
  1058  //     httpmock.RegisterResponderWithQuery(
  1059  //       "GET", "http://example.com/", expectedQuery,
  1060  //       httpmock.NewStringResponder("hello world", 200))
  1061  //
  1062  //     // requests to http://example.com?a=1&b=2 and http://example.com?b=2&a=1 now return 'hello world'
  1063  //   }
  1064  //
  1065  // or using a query string:
  1066  //   func TestFetchArticles(t *testing.T) {
  1067  //     httpmock.Activate()
  1068  //     defer httpmock.DeactivateAndReset()
  1069  //
  1070  //     expectedQuery := "a=3&b=4&b=2&a=1&a=8"
  1071  //     httpmock.RegisterResponderWithQueryValues(
  1072  //       "GET", "http://example.com/", expectedQuery,
  1073  //       httpmock.NewStringResponder("hello world", 200))
  1074  //
  1075  //     // requests to http://example.com?a=1&a=3&a=8&b=2&b=4
  1076  //     //      and to http://example.com?b=4&a=2&b=2&a=8&a=1
  1077  //     // now return 'hello world'
  1078  //   }
  1079  //
  1080  // If method is a lower-cased version of CONNECT, DELETE, GET, HEAD,
  1081  // OPTIONS, POST, PUT or TRACE, a panics occurs to notice the possible
  1082  // mistake. This panic can be disabled by setting
  1083  // DefaultTransport.DontCheckMethod to true prior to this call.
  1084  func RegisterResponderWithQuery(method, path string, query any, responder Responder) {
  1085  	DefaultTransport.RegisterResponderWithQuery(method, path, query, responder)
  1086  }
  1087  
  1088  // RegisterNoResponder adds a mock that is called whenever a request
  1089  // for an unregistered URL is received.  The default behavior is to
  1090  // return a connection error.
  1091  //
  1092  // In some cases you may not want all URLs to be mocked, in which case
  1093  // you can do this:
  1094  //   func TestFetchArticles(t *testing.T) {
  1095  //     httpmock.Activate()
  1096  //     defer httpmock.DeactivateAndReset()
  1097  //     httpmock.RegisterNoResponder(httpmock.InitialTransport.RoundTrip)
  1098  //
  1099  //     // any requests that don't have a registered URL will be fetched normally
  1100  //   }
  1101  func RegisterNoResponder(responder Responder) {
  1102  	DefaultTransport.RegisterNoResponder(responder)
  1103  }
  1104  
  1105  // ErrSubmatchNotFound is the error returned by GetSubmatch* functions
  1106  // when the given submatch index cannot be found.
  1107  var ErrSubmatchNotFound = errors.New("submatch not found")
  1108  
  1109  // GetSubmatch has to be used in Responders installed by
  1110  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1111  // allows to retrieve the n-th submatch of the matching regexp, as a
  1112  // string. Example:
  1113  //   RegisterResponder("GET", `=~^/item/name/([^/]+)\z`,
  1114  //     func(req *http.Request) (*http.Response, error) {
  1115  //       name, err := GetSubmatch(req, 1) // 1=first regexp submatch
  1116  //       if err != nil {
  1117  //         return nil, err
  1118  //       }
  1119  //       return NewJsonResponse(200, map[string]any{
  1120  //         "id":   123,
  1121  //         "name": name,
  1122  //       })
  1123  //     })
  1124  //
  1125  // It panics if n < 1. See MustGetSubmatch to avoid testing the
  1126  // returned error.
  1127  func GetSubmatch(req *http.Request, n int) (string, error) {
  1128  	if n <= 0 {
  1129  		panic(fmt.Sprintf("getting submatches starts at 1, not %d", n))
  1130  	}
  1131  	n--
  1132  
  1133  	submatches := internal.GetSubmatches(req)
  1134  	if n >= len(submatches) {
  1135  		return "", ErrSubmatchNotFound
  1136  	}
  1137  	return submatches[n], nil
  1138  }
  1139  
  1140  // GetSubmatchAsInt has to be used in Responders installed by
  1141  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1142  // allows to retrieve the n-th submatch of the matching regexp, as an
  1143  // int64. Example:
  1144  //   RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
  1145  //     func(req *http.Request) (*http.Response, error) {
  1146  //       id, err := GetSubmatchAsInt(req, 1) // 1=first regexp submatch
  1147  //       if err != nil {
  1148  //         return nil, err
  1149  //       }
  1150  //       return NewJsonResponse(200, map[string]any{
  1151  //         "id":   id,
  1152  //         "name": "The beautiful name",
  1153  //       })
  1154  //     })
  1155  //
  1156  // It panics if n < 1. See MustGetSubmatchAsInt to avoid testing the
  1157  // returned error.
  1158  func GetSubmatchAsInt(req *http.Request, n int) (int64, error) {
  1159  	sm, err := GetSubmatch(req, n)
  1160  	if err != nil {
  1161  		return 0, err
  1162  	}
  1163  	return strconv.ParseInt(sm, 10, 64)
  1164  }
  1165  
  1166  // GetSubmatchAsUint has to be used in Responders installed by
  1167  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1168  // allows to retrieve the n-th submatch of the matching regexp, as a
  1169  // uint64. Example:
  1170  //   RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
  1171  //     func(req *http.Request) (*http.Response, error) {
  1172  //       id, err := GetSubmatchAsUint(req, 1) // 1=first regexp submatch
  1173  //       if err != nil {
  1174  //         return nil, err
  1175  //       }
  1176  //       return NewJsonResponse(200, map[string]any{
  1177  //         "id":   id,
  1178  //         "name": "The beautiful name",
  1179  //       })
  1180  //     })
  1181  //
  1182  // It panics if n < 1. See MustGetSubmatchAsUint to avoid testing the
  1183  // returned error.
  1184  func GetSubmatchAsUint(req *http.Request, n int) (uint64, error) {
  1185  	sm, err := GetSubmatch(req, n)
  1186  	if err != nil {
  1187  		return 0, err
  1188  	}
  1189  	return strconv.ParseUint(sm, 10, 64)
  1190  }
  1191  
  1192  // GetSubmatchAsFloat has to be used in Responders installed by
  1193  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1194  // allows to retrieve the n-th submatch of the matching regexp, as a
  1195  // float64. Example:
  1196  //   RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`,
  1197  //     func(req *http.Request) (*http.Response, error) {
  1198  //       height, err := GetSubmatchAsFloat(req, 1) // 1=first regexp submatch
  1199  //       if err != nil {
  1200  //         return nil, err
  1201  //       }
  1202  //       return NewJsonResponse(200, map[string]any{
  1203  //         "id":     id,
  1204  //         "name":   "The beautiful name",
  1205  //         "height": height,
  1206  //       })
  1207  //     })
  1208  //
  1209  // It panics if n < 1. See MustGetSubmatchAsFloat to avoid testing the
  1210  // returned error.
  1211  func GetSubmatchAsFloat(req *http.Request, n int) (float64, error) {
  1212  	sm, err := GetSubmatch(req, n)
  1213  	if err != nil {
  1214  		return 0, err
  1215  	}
  1216  	return strconv.ParseFloat(sm, 64)
  1217  }
  1218  
  1219  // MustGetSubmatch works as GetSubmatch except that it panics in case
  1220  // of error (submatch not found). It has to be used in Responders
  1221  // installed by RegisterRegexpResponder or RegisterResponder + "=~"
  1222  // URL prefix. It allows to retrieve the n-th submatch of the matching
  1223  // regexp, as a string. Example:
  1224  //   RegisterResponder("GET", `=~^/item/name/([^/]+)\z`,
  1225  //     func(req *http.Request) (*http.Response, error) {
  1226  //       name := MustGetSubmatch(req, 1) // 1=first regexp submatch
  1227  //       return NewJsonResponse(200, map[string]any{
  1228  //         "id":   123,
  1229  //         "name": name,
  1230  //       })
  1231  //     })
  1232  //
  1233  // It panics if n < 1.
  1234  func MustGetSubmatch(req *http.Request, n int) string {
  1235  	s, err := GetSubmatch(req, n)
  1236  	if err != nil {
  1237  		panic("GetSubmatch failed: " + err.Error())
  1238  	}
  1239  	return s
  1240  }
  1241  
  1242  // MustGetSubmatchAsInt works as GetSubmatchAsInt except that it
  1243  // panics in case of error (submatch not found or invalid int64
  1244  // format). It has to be used in Responders installed by
  1245  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1246  // allows to retrieve the n-th submatch of the matching regexp, as an
  1247  // int64. Example:
  1248  //   RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
  1249  //     func(req *http.Request) (*http.Response, error) {
  1250  //       id := MustGetSubmatchAsInt(req, 1) // 1=first regexp submatch
  1251  //       return NewJsonResponse(200, map[string]any{
  1252  //         "id":   id,
  1253  //         "name": "The beautiful name",
  1254  //       })
  1255  //     })
  1256  //
  1257  // It panics if n < 1.
  1258  func MustGetSubmatchAsInt(req *http.Request, n int) int64 {
  1259  	i, err := GetSubmatchAsInt(req, n)
  1260  	if err != nil {
  1261  		panic("GetSubmatchAsInt failed: " + err.Error())
  1262  	}
  1263  	return i
  1264  }
  1265  
  1266  // MustGetSubmatchAsUint works as GetSubmatchAsUint except that it
  1267  // panics in case of error (submatch not found or invalid uint64
  1268  // format). It has to be used in Responders installed by
  1269  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1270  // allows to retrieve the n-th submatch of the matching regexp, as a
  1271  // uint64. Example:
  1272  //   RegisterResponder("GET", `=~^/item/id/(\d+)\z`,
  1273  //     func(req *http.Request) (*http.Response, error) {
  1274  //       id, err := MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
  1275  //       return NewJsonResponse(200, map[string]any{
  1276  //         "id":   id,
  1277  //         "name": "The beautiful name",
  1278  //       })
  1279  //     })
  1280  //
  1281  // It panics if n < 1.
  1282  func MustGetSubmatchAsUint(req *http.Request, n int) uint64 {
  1283  	u, err := GetSubmatchAsUint(req, n)
  1284  	if err != nil {
  1285  		panic("GetSubmatchAsUint failed: " + err.Error())
  1286  	}
  1287  	return u
  1288  }
  1289  
  1290  // MustGetSubmatchAsFloat works as GetSubmatchAsFloat except that it
  1291  // panics in case of error (submatch not found or invalid float64
  1292  // format). It has to be used in Responders installed by
  1293  // RegisterRegexpResponder or RegisterResponder + "=~" URL prefix. It
  1294  // allows to retrieve the n-th submatch of the matching regexp, as a
  1295  // float64. Example:
  1296  //   RegisterResponder("PATCH", `=~^/item/id/\d+\?height=(\d+(?:\.\d*)?)\z`,
  1297  //     func(req *http.Request) (*http.Response, error) {
  1298  //       height := MustGetSubmatchAsFloat(req, 1) // 1=first regexp submatch
  1299  //       return NewJsonResponse(200, map[string]any{
  1300  //         "id":     id,
  1301  //         "name":   "The beautiful name",
  1302  //         "height": height,
  1303  //       })
  1304  //     })
  1305  //
  1306  // It panics if n < 1.
  1307  func MustGetSubmatchAsFloat(req *http.Request, n int) float64 {
  1308  	f, err := GetSubmatchAsFloat(req, n)
  1309  	if err != nil {
  1310  		panic("GetSubmatchAsFloat failed: " + err.Error())
  1311  	}
  1312  	return f
  1313  }
  1314  

View as plain text