helpers.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package httpexpect
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "reflect"
  6. "regexp"
  7. "github.com/xeipuuv/gojsonschema"
  8. "github.com/yalp/jsonpath"
  9. "github.com/yudai/gojsondiff"
  10. "github.com/yudai/gojsondiff/formatter"
  11. )
  12. func toString(str interface{}) (s string, ok bool) {
  13. ok = true
  14. defer func() {
  15. if err := recover(); err != nil {
  16. ok = false
  17. }
  18. }()
  19. s = reflect.ValueOf(str).Convert(reflect.TypeOf("")).String()
  20. return
  21. }
  22. func getPath(chain *chain, value interface{}, path string) *Value {
  23. if chain.failed() {
  24. return &Value{*chain, nil}
  25. }
  26. result, err := jsonpath.Read(value, path)
  27. if err != nil {
  28. chain.fail(err.Error())
  29. return &Value{*chain, nil}
  30. }
  31. return &Value{*chain, result}
  32. }
  33. func checkSchema(chain *chain, value, schema interface{}) {
  34. if chain.failed() {
  35. return
  36. }
  37. valueLoader := gojsonschema.NewGoLoader(value)
  38. var schemaLoader gojsonschema.JSONLoader
  39. if str, ok := toString(schema); ok {
  40. if ok, _ := regexp.MatchString(`^\w+://`, str); ok {
  41. schemaLoader = gojsonschema.NewReferenceLoader(str)
  42. } else {
  43. schemaLoader = gojsonschema.NewStringLoader(str)
  44. }
  45. } else {
  46. schemaLoader = gojsonschema.NewGoLoader(schema)
  47. }
  48. result, err := gojsonschema.Validate(schemaLoader, valueLoader)
  49. if err != nil {
  50. chain.fail("\n%s\n\nschema:\n%s\n\nvalue:\n%s",
  51. err.Error(),
  52. dumpSchema(schema),
  53. dumpValue(value))
  54. return
  55. }
  56. if !result.Valid() {
  57. errors := ""
  58. for _, err := range result.Errors() {
  59. errors += fmt.Sprintf(" %s\n", err)
  60. }
  61. chain.fail(
  62. "\njson schema validation failed, schema:\n%s\n\nvalue:%s\n\nerrors:\n%s",
  63. dumpSchema(schema),
  64. dumpValue(value),
  65. errors)
  66. return
  67. }
  68. }
  69. func dumpSchema(schema interface{}) string {
  70. if s, ok := toString(schema); ok {
  71. schema = s
  72. }
  73. return regexp.MustCompile(`(?m:^)`).
  74. ReplaceAllString(fmt.Sprintf("%v", schema), " ")
  75. }
  76. func canonNumber(chain *chain, number interface{}) (f float64, ok bool) {
  77. ok = true
  78. defer func() {
  79. if err := recover(); err != nil {
  80. chain.fail("%v", err)
  81. ok = false
  82. }
  83. }()
  84. f = reflect.ValueOf(number).Convert(reflect.TypeOf(float64(0))).Float()
  85. return
  86. }
  87. func canonArray(chain *chain, in interface{}) ([]interface{}, bool) {
  88. var out []interface{}
  89. data, ok := canonValue(chain, in)
  90. if ok {
  91. out, ok = data.([]interface{})
  92. if !ok {
  93. chain.fail("expected array, got %v", out)
  94. }
  95. }
  96. return out, ok
  97. }
  98. func canonMap(chain *chain, in interface{}) (map[string]interface{}, bool) {
  99. var out map[string]interface{}
  100. data, ok := canonValue(chain, in)
  101. if ok {
  102. out, ok = data.(map[string]interface{})
  103. if !ok {
  104. chain.fail("expected map, got %v", out)
  105. }
  106. }
  107. return out, ok
  108. }
  109. func canonValue(chain *chain, in interface{}) (interface{}, bool) {
  110. b, err := json.Marshal(in)
  111. if err != nil {
  112. chain.fail(err.Error())
  113. return nil, false
  114. }
  115. var out interface{}
  116. if err := json.Unmarshal(b, &out); err != nil {
  117. chain.fail(err.Error())
  118. return nil, false
  119. }
  120. return out, true
  121. }
  122. func dumpValue(value interface{}) string {
  123. b, err := json.MarshalIndent(value, " ", " ")
  124. if err != nil {
  125. return " " + fmt.Sprintf("%#v", value)
  126. }
  127. return " " + string(b)
  128. }
  129. func diffValues(expected, actual interface{}) string {
  130. differ := gojsondiff.New()
  131. var diff gojsondiff.Diff
  132. if ve, ok := expected.(map[string]interface{}); ok {
  133. if va, ok := actual.(map[string]interface{}); ok {
  134. diff = differ.CompareObjects(ve, va)
  135. } else {
  136. return " (unavailable)"
  137. }
  138. } else if ve, ok := expected.([]interface{}); ok {
  139. if va, ok := actual.([]interface{}); ok {
  140. diff = differ.CompareArrays(ve, va)
  141. } else {
  142. return " (unavailable)"
  143. }
  144. } else {
  145. return " (unavailable)"
  146. }
  147. config := formatter.AsciiFormatterConfig{
  148. ShowArrayIndex: true,
  149. }
  150. formatter := formatter.NewAsciiFormatter(expected, config)
  151. str, err := formatter.Format(diff)
  152. if err != nil {
  153. return " (unavailable)"
  154. }
  155. return "--- expected\n+++ actual\n" + str
  156. }