Skip to content

Commit

Permalink
Bugfix-773 show better error message on JSON decoding errors
Browse files Browse the repository at this point in the history
instead of dumping entire JSON, offset, line number and column numbers are displayed
  • Loading branch information
openmohan committed Aug 9, 2019
1 parent bd6d2d9 commit 1a52600
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 2 deletions.
23 changes: 22 additions & 1 deletion js/modules/k6/http/response_test.go
Expand Up @@ -80,6 +80,11 @@ const jsonData = `{"glossary": {
"GlossSeeAlso": ["GML","XML"]},
"GlossSee": "markup"}}}}}`

const invalidJSONData = `{
"a":"apple",
"t":testing"
}`

func myFormHandler(w http.ResponseWriter, r *http.Request) {
var body []byte
var err error
Expand Down Expand Up @@ -110,6 +115,14 @@ func jsonHandler(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(body)
}

func invalidJSONHandler(w http.ResponseWriter, r *http.Request) {
body := []byte(invalidJSONData)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
w.WriteHeader(200)
_, _ = w.Write(body)
}

func TestResponse(t *testing.T) {
tb, state, samples, rt, _ := newRuntime(t)
defer tb.Cleanup()
Expand All @@ -118,6 +131,7 @@ func TestResponse(t *testing.T) {

tb.Mux.HandleFunc("/myforms/get", myFormHandler)
tb.Mux.HandleFunc("/json", jsonHandler)
tb.Mux.HandleFunc("/invalidjson", invalidJSONHandler)

t.Run("Html", func(t *testing.T) {
_, err := common.RunString(rt, sr(`
Expand Down Expand Up @@ -178,7 +192,14 @@ func TestResponse(t *testing.T) {

t.Run("Invalid", func(t *testing.T) {
_, err := common.RunString(rt, sr(`http.request("GET", "HTTPBIN_URL/html").json();`))
assert.EqualError(t, err, "GoError: invalid character '<' looking for beginning of value")
//nolint:lll
assert.EqualError(t, err, "GoError: cannot parse json due to an error at line 1, character 2 , error: invalid character '<' looking for beginning of value")
})

t.Run("Invalid", func(t *testing.T) {
_, err := common.RunString(rt, sr(`http.request("GET", "HTTPBIN_URL/invalidjson").json();`))
//nolint:lll
assert.EqualError(t, err, "GoError: cannot parse json due to an error at line 3, character 9 , error: invalid character 'e' in literal true (expecting 'r')")
})
})
t.Run("JsonSelector", func(t *testing.T) {
Expand Down
39 changes: 38 additions & 1 deletion lib/netext/httpext/response.go
Expand Up @@ -24,6 +24,9 @@ import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"

"github.com/loadimpact/k6/lib/netext"
"github.com/pkg/errors"
Expand Down Expand Up @@ -55,6 +58,17 @@ const (
ResponseTypeNone
)

type jsonError struct {
line int
character int
err error
}

func (j jsonError) Error() string {
errMessage := "cannot parse json due to an error at line"
return fmt.Sprintf("%s %d, character %d , error: %v", errMessage, j.line, j.character, j.err)
}

// ResponseTimings is a struct to put all timings for a given HTTP response/request
type ResponseTimings struct {
Duration float64 `json:"duration"`
Expand Down Expand Up @@ -127,7 +141,6 @@ func (res *Response) JSON(selector ...string) (interface{}, error) {
}

if hasSelector {

if !res.validatedJSON {
if !gjson.ValidBytes(body) {
return nil, nil
Expand All @@ -144,11 +157,35 @@ func (res *Response) JSON(selector ...string) (interface{}, error) {
}

if err := json.Unmarshal(body, &v); err != nil {
if syntaxError, ok := err.(*json.SyntaxError); ok {
err = checkErrorInJSON(body, int(syntaxError.Offset), err)
}
return nil, err
}
res.validatedJSON = true
res.cachedJSON = v
}
return res.cachedJSON, nil
}

func checkErrorInJSON(input []byte, offset int, err error) error {
lf := '\n'
str := string(input)

// Humans tend to count from 1.
line := 1
character := 0

for i, b := range str {
if b == lf {
line++
character = 0
}
character++
if i == offset {
break
}
}

return jsonError{line: line, character: character, err: err}
}

0 comments on commit 1a52600

Please sign in to comment.