Skip to content

Commit

Permalink
roll all the error types into one
Browse files Browse the repository at this point in the history
  • Loading branch information
efd6 committed Sep 1, 2023
1 parent 3917f92 commit d326682
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 73 deletions.
114 changes: 45 additions & 69 deletions x-pack/filebeat/input/httpjson/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func encodeAsJSON(trReq transformable) ([]byte, error) {
func decodeAsJSON(p []byte, dst *response) error {
err := json.Unmarshal(p, &dst.body)
if err != nil {
return jsonError{error: err, body: p}
return textContextError{error: err, body: p}
}
return nil
}
Expand All @@ -102,36 +102,14 @@ func decodeAsNdjson(p []byte, dst *response) error {
for dec.More() {
var o interface{}
if err := dec.Decode(&o); err != nil {
return jsonError{error: err, body: p}
return textContextError{error: err, body: p}
}
results = append(results, o)
}
dst.body = results
return nil
}

type jsonError struct {
error
body []byte
}

func (e jsonError) Error() string {
switch err := e.error.(type) {
case nil:
return "<nil>"
case *json.SyntaxError:
return fmt.Sprintf("%v: text context %q", err, textContext(e.body, err.Offset))
case *json.UnmarshalTypeError:
return fmt.Sprintf("%v: text context %q", err, textContext(e.body, err.Offset))
default:
return err.Error()
}
}

func (e jsonError) Unwrap() error {
return e.error
}

// decodeAsCSV decodes p as a headed CSV document into dst.
func decodeAsCSV(p []byte, dst *response) error {
var results []interface{}

Check failure on line 115 in x-pack/filebeat/input/httpjson/encoding.go

View workflow job for this annotation

GitHub Actions / lint (windows)

Consider pre-allocating `results` (prealloc)
Expand Down Expand Up @@ -164,7 +142,7 @@ func decodeAsCSV(p []byte, dst *response) error {

if err != nil {
if err != io.EOF { //nolint:errorlint // csv.Reader never wraps io.EOF.
return csvError{error: err, body: p}
return textContextError{error: err, body: p}
}
}

Expand All @@ -173,31 +151,6 @@ func decodeAsCSV(p []byte, dst *response) error {
return nil
}

type csvError struct {
error
body []byte
}

func (e csvError) Error() string {
switch err := e.error.(type) {
case nil:
return "<nil>"
case *csv.ParseError:
lines := bytes.Split(e.body, []byte{'\n'})
l := err.Line - 1 // Lines are 1-based.
if uint(l) >= uint(len(lines)) {
return err.Error()
}
return fmt.Sprintf("%v: text context %q", err, textContext(lines[l], int64(err.Column)))
default:
return err.Error()
}
}

func (e csvError) Unwrap() error {
return e.error
}

// decodeAsZip decodes p as a ZIP archive into dst.
func decodeAsZip(p []byte, dst *response) error {
var results []interface{}
Expand All @@ -219,7 +172,7 @@ func decodeAsZip(p []byte, dst *response) error {
var o interface{}
if err := dec.Decode(&o); err != nil {
rc.Close()
return jsonError{error: err, body: p}
return textContextError{error: err, body: p}
}
results = append(results, o)
}
Expand All @@ -239,22 +192,36 @@ func decodeAsZip(p []byte, dst *response) error {
func decodeAsXML(p []byte, dst *response) error {
cdata, body, err := xml.Unmarshal(bytes.NewReader(p), dst.xmlDetails)
if err != nil {
return xmlError{error: err, body: p}
return textContextError{error: err, body: p}
}
dst.body = body
dst.header["XML-CDATA"] = []string{cdata}
return nil
}

type xmlError struct {
// textContextError is an error that can provide the text context for
// a decoding error from the csv, json and xml packages.
type textContextError struct {
error
body []byte
}

func (e xmlError) Error() string {
func (e textContextError) Error() string {
var ctx []byte
switch err := e.error.(type) {

Check failure on line 211 in x-pack/filebeat/input/httpjson/encoding.go

View workflow job for this annotation

GitHub Actions / lint (windows)

type switch on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint)
case nil:
return "<nil>"
case *json.SyntaxError:
ctx = textContext(e.body, err.Offset)
case *json.UnmarshalTypeError:
ctx = textContext(e.body, err.Offset)
case *csv.ParseError:
lines := bytes.Split(e.body, []byte{'\n'})
l := err.Line - 1 // Lines are 1-based.
if uint(l) >= uint(len(lines)) {
return err.Error()
}
ctx = textContext(lines[l], int64(err.Column))
case *stdxml.SyntaxError:
lines := bytes.Split(e.body, []byte{'\n'})
l := err.Line - 1 // Lines are 1-based.
Expand All @@ -271,37 +238,46 @@ func (e xmlError) Error() string {
if pos < 0 {
pos = 0
}
return fmt.Sprintf("%v: text context %q", err, textContext(lines[l], int64(pos)))
ctx = textContext(lines[l], int64(pos))
default:
return err.Error()
}
return fmt.Sprintf("%v: text context %q", e.error, ctx)
}

func (e xmlError) Unwrap() error {
func (e textContextError) Unwrap() error {
return e.error
}

// textContext returns the context of text around the provided position starting
// five bytes before pos and extending ten bytes, dependent on the length of the
// ten bytes before pos and ten bytes after, dependent on the length of the
// text and the value of pos relative to bounds. If a text truncation is made,
// an ellipsis is added to indicate this. The returned []byte should not be mutated
// as it may be shared with the caller.
// an ellipsis is added to indicate this.
func textContext(text []byte, pos int64) []byte {
left := maxInt64(0, pos-5)
text = text[left:]
var pad int64
if len(text) == 0 {
return text
}
const (
dots = "..."
span = 10
)
left := maxInt64(0, pos-span)
right := minInt(pos+span+1, int64(len(text)))
ctx := make([]byte, right-left+2*int64(len(dots)))
copy(ctx[3:], text[left:right])
if left != 0 {
pad = 3
text = append([]byte("..."), text...)
copy(ctx, dots)
left = 0
} else {
left = int64(len(dots))
}
right := minInt(pos+10+pad, int64(len(text)))
if right != int64(len(text)) {
// Ensure we don't clobber the body's bytes.
text = append(text[:right:right], []byte("...")...)
copy(ctx[len(ctx)-len(dots):], dots)
right = int64(len(ctx))
} else {
text = text[:right]
right = int64(len(ctx) - len(dots))
}
return text
return ctx[left:right]
}

func minInt(a, b int64) int64 {
Expand Down
28 changes: 24 additions & 4 deletions x-pack/filebeat/input/httpjson/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestDecodeJSON(t *testing.T) {
},
{
body: "[{\"a\":\"b\"},\nunfortunate text\n{\"c\":\"d\"}]",
err: `invalid character 'u' looking for beginning of value: text context "...\"},\nunfortunate text\n{\"..."`,
err: `invalid character 'u' looking for beginning of value: text context "...a\":\"b\"},\nunfortunate ..."`,
},
}
for _, test := range tests {
Expand Down Expand Up @@ -136,7 +136,7 @@ func TestDecodeNdjson(t *testing.T) {
},
{
body: "{\"a\":\"b\"}unfortunate text\r\n{\"c\":\"d\"}\r\n",
err: `invalid character 'u' looking for beginning of value: text context "...\"b\"}unfortunate text..."`,
err: `invalid character 'u' looking for beginning of value: text context "{\"a\":\"b\"}unfortunate ..."`,
},
}
for _, test := range tests {
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestDecodeCSV(t *testing.T) {
{
body: "EVENT_TYPE,TIMESTAMP,REQUEST_ID,ORGANIZATION_ID,USER_ID\n" +
"Login,20211018071505.579,id4,user2\n",
err: "record on line 2: wrong number of fields: text context \"Login,20211...\"",
err: "record on line 2: wrong number of fields: text context \"Login,202110...\"",
},
}
for _, test := range tests {
Expand Down Expand Up @@ -235,7 +235,7 @@ func TestDecodeXML(t *testing.T) {
</i>
</o>
`,
err: `XML syntax error on line 7: element <n> closed by </name>: text context "... <n>Egil's Saga</name>"`,
err: `XML syntax error on line 7: element <n> closed by </name>: text context "... <n>Egil's S..."`,
},
}
for _, test := range tests {
Expand Down Expand Up @@ -288,3 +288,23 @@ func TestEncodeAsForm(t *testing.T) {
assert.Equal(t, "application/x-www-form-urlencoded", trReq.header().Get("Content-Type"))
}
}

func TestTextContext(t *testing.T) {
tests := []struct {
text string
pos int64
want string
}{
{},
{text: "0987654321*1234567890", pos: 10, want: "0987654321*1234567890"},
{text: "54321*1234567890xxxxx", pos: 5, want: "54321*1234567890..."},
{text: "xxxxx0987654321*12345", pos: 15, want: "...0987654321*12345"},
{text: "x0987654321*1234567890x", pos: 11, want: "...0987654321*1234567890..."},
}
for _, test := range tests {
got := string(textContext([]byte(test.text), test.pos))
if got != test.want {
t.Errorf("unexpected result for textContext(%q, %d): got:%q want:%q", test.text, test.pos, got, test.want)
}
}
}

0 comments on commit d326682

Please sign in to comment.