match.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. package httpexpect
  2. import (
  3. "errors"
  4. "reflect"
  5. )
  6. // Match provides methods to inspect attached regexp match results.
  7. type Match struct {
  8. chain *chain
  9. submatches []string
  10. names map[string]int
  11. }
  12. // NewMatch returns a new Match instance.
  13. //
  14. // If reporter is nil, the function panics.
  15. // Both submatches and names may be nil.
  16. //
  17. // Example:
  18. //
  19. // s := "http://example.com/users/john"
  20. // r := regexp.MustCompile(`http://(?P<host>.+)/users/(?P<user>.+)`)
  21. //
  22. // m := NewMatch(t, r.FindStringSubmatch(s), r.SubexpNames())
  23. //
  24. // m.NotEmpty()
  25. // m.Length().IsEqual(3)
  26. //
  27. // m.Index(0).IsEqual("http://example.com/users/john")
  28. // m.Index(1).IsEqual("example.com")
  29. // m.Index(2).IsEqual("john")
  30. //
  31. // m.Name("host").IsEqual("example.com")
  32. // m.Name("user").IsEqual("john")
  33. func NewMatch(reporter Reporter, submatches []string, names []string) *Match {
  34. return newMatch(newChainWithDefaults("Match()", reporter), submatches, names)
  35. }
  36. // NewMatchC returns a new Match instance with config.
  37. //
  38. // Requirements for config are same as for WithConfig function.
  39. // Both submatches and names may be nil.
  40. //
  41. // See NewMatch for usage example.
  42. func NewMatchC(config Config, submatches []string, names []string) *Match {
  43. return newMatch(newChainWithConfig("Match()", config.withDefaults()), submatches, names)
  44. }
  45. func newMatch(parent *chain, matchList []string, nameList []string) *Match {
  46. m := &Match{parent.clone(), nil, nil}
  47. if matchList != nil {
  48. m.submatches = matchList
  49. } else {
  50. m.submatches = []string{}
  51. }
  52. m.names = map[string]int{}
  53. for n, name := range nameList {
  54. if name != "" {
  55. m.names[name] = n
  56. }
  57. }
  58. return m
  59. }
  60. // Raw returns underlying submatches attached to Match.
  61. // This is the value originally passed to NewMatch.
  62. //
  63. // Example:
  64. //
  65. // m := NewMatch(t, submatches, names)
  66. // assert.Equal(t, submatches, m.Raw())
  67. func (m *Match) Raw() []string {
  68. return m.submatches
  69. }
  70. // Alias is similar to Value.Alias.
  71. func (m *Match) Alias(name string) *Match {
  72. opChain := m.chain.enter("Alias(%q)", name)
  73. defer opChain.leave()
  74. m.chain.setAlias(name)
  75. return m
  76. }
  77. // Length returns a new Number instance with number of submatches.
  78. //
  79. // Example:
  80. //
  81. // m := NewMatch(t, submatches, names)
  82. // m.Length().IsEqual(len(submatches))
  83. func (m *Match) Length() *Number {
  84. opChain := m.chain.enter("Length()")
  85. defer opChain.leave()
  86. if opChain.failed() {
  87. return newNumber(opChain, 0)
  88. }
  89. return newNumber(opChain, float64(len(m.submatches)))
  90. }
  91. // Index returns a new String instance with submatch for given index.
  92. //
  93. // Note that submatch with index 0 contains the whole match. If index is out
  94. // of bounds, Index reports failure and returns empty (but non-nil) instance.
  95. //
  96. // Example:
  97. //
  98. // s := "http://example.com/users/john"
  99. //
  100. // r := regexp.MustCompile(`http://(.+)/users/(.+)`)
  101. // m := NewMatch(t, r.FindStringSubmatch(s), nil)
  102. //
  103. // m.Index(0).IsEqual("http://example.com/users/john")
  104. // m.Index(1).IsEqual("example.com")
  105. // m.Index(2).IsEqual("john")
  106. func (m *Match) Index(index int) *String {
  107. opChain := m.chain.enter("Index(%d)", index)
  108. defer opChain.leave()
  109. if opChain.failed() {
  110. return newString(opChain, "")
  111. }
  112. if index < 0 || index >= len(m.submatches) {
  113. opChain.fail(AssertionFailure{
  114. Type: AssertInRange,
  115. Actual: &AssertionValue{index},
  116. Expected: &AssertionValue{AssertionRange{
  117. Min: 0,
  118. Max: len(m.submatches) - 1,
  119. }},
  120. Errors: []error{
  121. errors.New("expected: valid sub-match index"),
  122. },
  123. })
  124. return newString(opChain, "")
  125. }
  126. return newString(opChain, m.submatches[index])
  127. }
  128. // Name returns a new String instance with submatch for given name.
  129. //
  130. // If there is no submatch with given name, Name reports failure and returns
  131. // empty (but non-nil) instance.
  132. //
  133. // Example:
  134. //
  135. // s := "http://example.com/users/john"
  136. //
  137. // r := regexp.MustCompile(`http://(?P<host>.+)/users/(?P<user>.+)`)
  138. // m := NewMatch(t, r.FindStringSubmatch(s), r.SubexpNames())
  139. //
  140. // m.Name("host").IsEqual("example.com")
  141. // m.Name("user").IsEqual("john")
  142. func (m *Match) Name(name string) *String {
  143. opChain := m.chain.enter("Name(%q)", name)
  144. defer opChain.leave()
  145. if opChain.failed() {
  146. return newString(opChain, "")
  147. }
  148. index, ok := m.names[name]
  149. if !ok {
  150. nameList := make([]interface{}, 0, len(m.names))
  151. for n := range m.names {
  152. nameList = append(nameList, n)
  153. }
  154. opChain.fail(AssertionFailure{
  155. Type: AssertBelongs,
  156. Actual: &AssertionValue{name},
  157. Expected: &AssertionValue{AssertionList(nameList)},
  158. Errors: []error{
  159. errors.New("expected: existing sub-match name"),
  160. },
  161. })
  162. return newString(opChain, "")
  163. }
  164. return newString(opChain, m.submatches[index])
  165. }
  166. // IsEmpty succeeds if submatches array is empty.
  167. //
  168. // Example:
  169. //
  170. // m := NewMatch(t, submatches, names)
  171. // m.IsEmpty()
  172. func (m *Match) IsEmpty() *Match {
  173. opChain := m.chain.enter("IsEmpty()")
  174. defer opChain.leave()
  175. if opChain.failed() {
  176. return m
  177. }
  178. if !(len(m.submatches) == 0) {
  179. opChain.fail(AssertionFailure{
  180. Type: AssertEmpty,
  181. Actual: &AssertionValue{m.submatches},
  182. Errors: []error{
  183. errors.New("expected: empty sub-match list"),
  184. },
  185. })
  186. }
  187. return m
  188. }
  189. // NotEmpty succeeds if submatches array is non-empty.
  190. //
  191. // Example:
  192. //
  193. // m := NewMatch(t, submatches, names)
  194. // m.NotEmpty()
  195. func (m *Match) NotEmpty() *Match {
  196. opChain := m.chain.enter("NotEmpty()")
  197. defer opChain.leave()
  198. if opChain.failed() {
  199. return m
  200. }
  201. if !(len(m.submatches) != 0) {
  202. opChain.fail(AssertionFailure{
  203. Type: AssertNotEmpty,
  204. Actual: &AssertionValue{m.submatches},
  205. Errors: []error{
  206. errors.New("expected: non-empty sub-match list"),
  207. },
  208. })
  209. }
  210. return m
  211. }
  212. // Deprecated: use IsEmpty instead.
  213. func (m *Match) Empty() *Match {
  214. return m.IsEmpty()
  215. }
  216. // Values succeeds if submatches array, starting from index 1, is equal to
  217. // given array.
  218. //
  219. // Note that submatch with index 0 contains the whole match and is not
  220. // included into this check.
  221. //
  222. // Example:
  223. //
  224. // s := "http://example.com/users/john"
  225. // r := regexp.MustCompile(`http://(.+)/users/(.+)`)
  226. // m := NewMatch(t, r.FindStringSubmatch(s), nil)
  227. // m.Values("example.com", "john")
  228. func (m *Match) Values(values ...string) *Match {
  229. opChain := m.chain.enter("Values()")
  230. defer opChain.leave()
  231. if opChain.failed() {
  232. return m
  233. }
  234. if values == nil {
  235. values = []string{}
  236. }
  237. if !reflect.DeepEqual(values, m.getValues()) {
  238. opChain.fail(AssertionFailure{
  239. Type: AssertEqual,
  240. Actual: &AssertionValue{m.submatches},
  241. Expected: &AssertionValue{values},
  242. Errors: []error{
  243. errors.New("expected: sub-match lists are equal"),
  244. },
  245. })
  246. }
  247. return m
  248. }
  249. // NotValues succeeds if submatches array, starting from index 1, is not
  250. // equal to given array.
  251. //
  252. // Note that submatch with index 0 contains the whole match and is not
  253. // included into this check.
  254. //
  255. // Example:
  256. //
  257. // s := "http://example.com/users/john"
  258. // r := regexp.MustCompile(`http://(.+)/users/(.+)`)
  259. // m := NewMatch(t, r.FindStringSubmatch(s), nil)
  260. // m.NotValues("example.com", "bob")
  261. func (m *Match) NotValues(values ...string) *Match {
  262. opChain := m.chain.enter("NotValues()")
  263. defer opChain.leave()
  264. if values == nil {
  265. values = []string{}
  266. }
  267. if reflect.DeepEqual(values, m.getValues()) {
  268. opChain.fail(AssertionFailure{
  269. Type: AssertNotEqual,
  270. Actual: &AssertionValue{m.submatches},
  271. Expected: &AssertionValue{values},
  272. Errors: []error{
  273. errors.New("expected: sub-match lists are non-equal"),
  274. },
  275. })
  276. }
  277. return m
  278. }
  279. func (m *Match) getValues() []string {
  280. if len(m.submatches) > 1 {
  281. return m.submatches[1:]
  282. }
  283. return []string{}
  284. }