request.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package gock
  2. import (
  3. "io"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. )
  9. // MapRequestFunc represents the required function interface for request mappers.
  10. type MapRequestFunc func(*http.Request) *http.Request
  11. // FilterRequestFunc represents the required function interface for request filters.
  12. type FilterRequestFunc func(*http.Request) bool
  13. // Request represents the high-level HTTP request used to store
  14. // request fields used to match intercepted requests.
  15. type Request struct {
  16. // Mock stores the parent mock reference for the current request mock used for method delegation.
  17. Mock Mock
  18. // Response stores the current Response instance for the current matches Request.
  19. Response *Response
  20. // Error stores the latest mock request configuration error.
  21. Error error
  22. // Counter stores the pending times that the current mock should be active.
  23. Counter int
  24. // Persisted stores if the current mock should be always active.
  25. Persisted bool
  26. // URLStruct stores the parsed URL as *url.URL struct.
  27. URLStruct *url.URL
  28. // Method stores the Request HTTP method to match.
  29. Method string
  30. // CompressionScheme stores the Request Compression scheme to match and use for decompression.
  31. CompressionScheme string
  32. // Header stores the HTTP header fields to match.
  33. Header http.Header
  34. // Cookies stores the Request HTTP cookies values to match.
  35. Cookies []*http.Cookie
  36. // BodyBuffer stores the body data to match.
  37. BodyBuffer []byte
  38. // Mappers stores the request functions mappers used for matching.
  39. Mappers []MapRequestFunc
  40. // Filters stores the request functions filters used for matching.
  41. Filters []FilterRequestFunc
  42. }
  43. // NewRequest creates a new Request instance.
  44. func NewRequest() *Request {
  45. return &Request{
  46. Counter: 1,
  47. URLStruct: &url.URL{},
  48. Header: make(http.Header),
  49. }
  50. }
  51. // URL defines the mock URL to match.
  52. func (r *Request) URL(uri string) *Request {
  53. r.URLStruct, r.Error = url.Parse(uri)
  54. return r
  55. }
  56. // SetURL defines the url.URL struct to be used for matching.
  57. func (r *Request) SetURL(u *url.URL) *Request {
  58. r.URLStruct = u
  59. return r
  60. }
  61. // Path defines the mock URL path value to match.
  62. func (r *Request) Path(path string) *Request {
  63. r.URLStruct.Path = path
  64. return r
  65. }
  66. // Get specifies the GET method and the given URL path to match.
  67. func (r *Request) Get(path string) *Request {
  68. return r.method("GET", path)
  69. }
  70. // Post specifies the POST method and the given URL path to match.
  71. func (r *Request) Post(path string) *Request {
  72. return r.method("POST", path)
  73. }
  74. // Put specifies the PUT method and the given URL path to match.
  75. func (r *Request) Put(path string) *Request {
  76. return r.method("PUT", path)
  77. }
  78. // Delete specifies the DELETE method and the given URL path to match.
  79. func (r *Request) Delete(path string) *Request {
  80. return r.method("DELETE", path)
  81. }
  82. // Patch specifies the PATCH method and the given URL path to match.
  83. func (r *Request) Patch(path string) *Request {
  84. return r.method("PATCH", path)
  85. }
  86. // Head specifies the HEAD method and the given URL path to match.
  87. func (r *Request) Head(path string) *Request {
  88. return r.method("HEAD", path)
  89. }
  90. // method is a DRY shortcut used to declare the expected HTTP method and URL path.
  91. func (r *Request) method(method, path string) *Request {
  92. if path != "/" {
  93. r.URLStruct.Path = path
  94. }
  95. r.Method = strings.ToUpper(method)
  96. return r
  97. }
  98. // Body defines the body data to match based on a io.Reader interface.
  99. func (r *Request) Body(body io.Reader) *Request {
  100. r.BodyBuffer, r.Error = ioutil.ReadAll(body)
  101. return r
  102. }
  103. // BodyString defines the body to match based on a given string.
  104. func (r *Request) BodyString(body string) *Request {
  105. r.BodyBuffer = []byte(body)
  106. return r
  107. }
  108. // File defines the body to match based on the given file path string.
  109. func (r *Request) File(path string) *Request {
  110. r.BodyBuffer, r.Error = ioutil.ReadFile(path)
  111. return r
  112. }
  113. // Compression defines the request compression scheme, and enables automatic body decompression.
  114. // Supports only the "gzip" scheme so far.
  115. func (r *Request) Compression(scheme string) *Request {
  116. r.Header.Set("Content-Encoding", scheme)
  117. r.CompressionScheme = scheme
  118. return r
  119. }
  120. // JSON defines the JSON body to match based on a given structure.
  121. func (r *Request) JSON(data interface{}) *Request {
  122. if r.Header.Get("Content-Type") == "" {
  123. r.Header.Set("Content-Type", "application/json")
  124. }
  125. r.BodyBuffer, r.Error = readAndDecode(data, "json")
  126. return r
  127. }
  128. // XML defines the XML body to match based on a given structure.
  129. func (r *Request) XML(data interface{}) *Request {
  130. if r.Header.Get("Content-Type") == "" {
  131. r.Header.Set("Content-Type", "application/xml")
  132. }
  133. r.BodyBuffer, r.Error = readAndDecode(data, "xml")
  134. return r
  135. }
  136. // MatchType defines the request Content-Type MIME header field.
  137. // Supports type alias. E.g: json, xml, form, text...
  138. func (r *Request) MatchType(kind string) *Request {
  139. mime := BodyTypeAliases[kind]
  140. if mime != "" {
  141. kind = mime
  142. }
  143. r.Header.Set("Content-Type", kind)
  144. return r
  145. }
  146. // MatchHeader defines a new key and value header to match.
  147. func (r *Request) MatchHeader(key, value string) *Request {
  148. r.Header.Set(key, value)
  149. return r
  150. }
  151. // HeaderPresent defines that a header field must be present in the request.
  152. func (r *Request) HeaderPresent(key string) *Request {
  153. r.Header.Set(key, ".*")
  154. return r
  155. }
  156. // MatchHeaders defines a map of key-value headers to match.
  157. func (r *Request) MatchHeaders(headers map[string]string) *Request {
  158. for key, value := range headers {
  159. r.Header.Set(key, value)
  160. }
  161. return r
  162. }
  163. // MatchParam defines a new key and value URL query param to match.
  164. func (r *Request) MatchParam(key, value string) *Request {
  165. query := r.URLStruct.Query()
  166. query.Set(key, value)
  167. r.URLStruct.RawQuery = query.Encode()
  168. return r
  169. }
  170. // MatchParams defines a map of URL query param key-value to match.
  171. func (r *Request) MatchParams(params map[string]string) *Request {
  172. query := r.URLStruct.Query()
  173. for key, value := range params {
  174. query.Set(key, value)
  175. }
  176. r.URLStruct.RawQuery = query.Encode()
  177. return r
  178. }
  179. // ParamPresent matches if the given query param key is present in the URL.
  180. func (r *Request) ParamPresent(key string) *Request {
  181. r.MatchParam(key, ".*")
  182. return r
  183. }
  184. // Persist defines the current HTTP mock as persistent and won't be removed after intercepting it.
  185. func (r *Request) Persist() *Request {
  186. r.Persisted = true
  187. return r
  188. }
  189. // Times defines the number of times that the current HTTP mock should remain active.
  190. func (r *Request) Times(num int) *Request {
  191. r.Counter = num
  192. return r
  193. }
  194. // AddMatcher adds a new matcher function to match the request.
  195. func (r *Request) AddMatcher(fn MatchFunc) *Request {
  196. r.Mock.AddMatcher(fn)
  197. return r
  198. }
  199. // SetMatcher sets a new matcher function to match the request.
  200. func (r *Request) SetMatcher(matcher Matcher) *Request {
  201. r.Mock.SetMatcher(matcher)
  202. return r
  203. }
  204. // Map adds a new request mapper function to map http.Request before the matching process.
  205. func (r *Request) Map(fn MapRequestFunc) *Request {
  206. r.Mappers = append(r.Mappers, fn)
  207. return r
  208. }
  209. // Filter filters a new request filter function to filter http.Request before the matching process.
  210. func (r *Request) Filter(fn FilterRequestFunc) *Request {
  211. r.Filters = append(r.Filters, fn)
  212. return r
  213. }
  214. // EnableNetworking enables the use real networking for the current mock.
  215. func (r *Request) EnableNetworking() *Request {
  216. if r.Response != nil {
  217. r.Response.UseNetwork = true
  218. }
  219. return r
  220. }
  221. // Reply defines the Response status code and returns the mock Response DSL.
  222. func (r *Request) Reply(status int) *Response {
  223. return r.Response.Status(status)
  224. }
  225. // ReplyError defines the Response simulated error.
  226. func (r *Request) ReplyError(err error) *Response {
  227. return r.Response.SetError(err)
  228. }
  229. // ReplyFunc allows the developer to define the mock response via a custom function.
  230. func (r *Request) ReplyFunc(replier func(*Response)) *Response {
  231. replier(r.Response)
  232. return r.Response
  233. }