Skip to content

Commit

Permalink
Merge pull request #69 from tdakkota/feat/better-errors
Browse files Browse the repository at this point in the history
feat: pass offset to `unexpected byte` error
  • Loading branch information
tdakkota committed Jan 20, 2023
2 parents 2f8261b + cc57d4d commit 75ecdde
Show file tree
Hide file tree
Showing 35 changed files with 1,387 additions and 897 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Expand Up @@ -8,7 +8,7 @@ insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf

[{*.go, go.mod}]
[{*.go, go.mod, *.tmpl}]
indent_style = tab
indent_size = 4

Expand Down
7 changes: 6 additions & 1 deletion dec.go
Expand Up @@ -85,7 +85,8 @@ type Decoder struct {
head int // offset in buf to start of current json stream
tail int // offset in buf to end of current json stream

depth int
streamOffset int // for reader, offset in stream to start of current buf contents
depth int
}

const defaultBuf = 512
Expand Down Expand Up @@ -114,6 +115,10 @@ func DecodeStr(input string) *Decoder {
return DecodeBytes([]byte(input))
}

func (d *Decoder) offset() int {
return d.streamOffset + d.head
}

// Reset resets reader and underlying state, next reads will use provided io.Reader.
func (d *Decoder) Reset(reader io.Reader) {
d.reader = reader
Expand Down
19 changes: 10 additions & 9 deletions dec_arr.go
Expand Up @@ -17,7 +17,7 @@ func (d *Decoder) Elem() (ok bool, err error) {
case '[':
c, err := d.more()
if err != nil {
return false, errors.Wrap(err, "next")
return false, err
}
if c != ']' {
d.unread()
Expand All @@ -29,24 +29,24 @@ func (d *Decoder) Elem() (ok bool, err error) {
case ',':
return true, nil
default:
return false, errors.Wrap(badToken(c), `"[" or "," or "]" expected`)
return false, errors.Wrap(badToken(c, d.offset()), `"[", "," or "]" expected`)
}
}

// Arr decodes array and invokes callback on each array element.
func (d *Decoder) Arr(f func(d *Decoder) error) error {
if err := d.consume('['); err != nil {
return errors.Wrap(err, "start")
return errors.Wrap(err, `"[" expected`)
}
if f == nil {
return d.skipArr()
}
if err := d.incDepth(); err != nil {
return errors.Wrap(err, "inc")
return err
}
c, err := d.more()
if err != nil {
return err
return errors.Wrap(err, `value or "]" expected`)
}
if c == ']' {
return d.decDepth()
Expand All @@ -58,23 +58,24 @@ func (d *Decoder) Arr(f func(d *Decoder) error) error {

c, err = d.more()
if err != nil {
return errors.Wrap(err, "next")
return errors.Wrap(err, `"," or "]" expected`)
}
for c == ',' {
// Skip whitespace before reading element.
if _, err := d.next(); err != nil {
return errors.Wrap(err, "next")
return err
}
d.unread()
if err := f(d); err != nil {
return errors.Wrap(err, "callback")
}
if c, err = d.next(); err != nil {
return errors.Wrap(err, "next")
return err
}
}
if c != ']' {
return errors.Wrap(badToken(c), "end")
err := badToken(c, d.offset()-1)
return errors.Wrap(err, `"]" expected`)
}
return d.decDepth()
}
7 changes: 4 additions & 3 deletions dec_arr_iter.go
Expand Up @@ -15,10 +15,10 @@ type ArrIter struct {
// ArrIter creates new array iterator.
func (d *Decoder) ArrIter() (ArrIter, error) {
if err := d.consume('['); err != nil {
return ArrIter{}, errors.Wrap(err, "start")
return ArrIter{}, errors.Wrap(err, `"[" expected`)
}
if err := d.incDepth(); err != nil {
return ArrIter{}, errors.Wrap(err, "inc")
return ArrIter{}, err
}
if _, err := d.more(); err != nil {
return ArrIter{}, err
Expand Down Expand Up @@ -46,7 +46,8 @@ func (i *ArrIter) Next() bool {
}
if i.comma {
if c != ',' {
i.err = badToken(c)
err := badToken(c, dec.offset()-1)
i.err = errors.Wrap(err, `"," expected`)
return false
}
} else {
Expand Down
13 changes: 8 additions & 5 deletions dec_bool.go
Expand Up @@ -6,7 +6,10 @@ func (d *Decoder) Bool() (bool, error) {
return false, err
}

var buf [4]byte
var (
offset = d.offset()
buf [4]byte
)
if err := d.readExact4(&buf); err != nil {
return false, err
}
Expand All @@ -20,19 +23,19 @@ func (d *Decoder) Bool() (bool, error) {
return false, err
}
if c != 'e' {
return false, badToken(c)
return false, badToken(c, offset+4)
}
return false, nil
default:
switch c := buf[0]; c {
case 't':
const encodedTrue = 't' | 'r'<<8 | 'u'<<16 | 'e'<<24
return false, findInvalidToken4(buf, encodedTrue)
return false, findInvalidToken4(buf, encodedTrue, offset)
case 'f':
const encodedFals = 'f' | 'a'<<8 | 'l'<<16 | 's'<<24
return false, findInvalidToken4(buf, encodedFals)
return false, findInvalidToken4(buf, encodedFals, offset)
default:
return false, badToken(c)
return false, badToken(c, offset)
}
}
}
6 changes: 5 additions & 1 deletion dec_capture.go
Expand Up @@ -13,10 +13,14 @@ func (d *Decoder) Capture(f func(d *Decoder) error) error {

if d.reader != nil {
// TODO(tdakkota): May it be more efficient?
var buf bytes.Buffer
var (
buf bytes.Buffer
streamOffset = d.streamOffset
)
reader := io.TeeReader(d.reader, &buf)
defer func() {
d.reader = io.MultiReader(&buf, d.reader)
d.streamOffset = streamOffset
}()
d.reader = reader
}
Expand Down
17 changes: 17 additions & 0 deletions dec_error.go
@@ -0,0 +1,17 @@
package jx

import "fmt"

// badTokenErr means that Token was unexpected while decoding.
type badTokenErr struct {
Token byte
Offset int
}

func (e *badTokenErr) Error() string {
return fmt.Sprintf("unexpected byte %d %q at %d", e.Token, e.Token, e.Offset)
}

func badToken(c byte, offset int) error {
return &badTokenErr{Token: c, Offset: offset}
}
16 changes: 16 additions & 0 deletions dec_error_test.go
@@ -0,0 +1,16 @@
package jx

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_badTokenErr_Error(t *testing.T) {
e := &badTokenErr{
Token: 'c',
Offset: 10,
}
s := error(e).Error()
require.Equal(t, "unexpected byte 99 'c' at 10", s)
}
26 changes: 23 additions & 3 deletions dec_float.go
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"io"
"math/big"
"strconv"

"github.com/go-faster/errors"
)
Expand Down Expand Up @@ -71,7 +72,7 @@ func (d *Decoder) BigInt() (*big.Int, error) {
func (d *Decoder) Float32() (float32, error) {
c, err := d.more()
if err != nil {
return 0, errors.Wrap(err, "byte")
return 0, err
}
if c != '-' {
d.unread()
Expand Down Expand Up @@ -227,7 +228,7 @@ func (d *Decoder) float32Slow() (float32, error) {
func (d *Decoder) Float64() (float64, error) {
c, err := d.more()
if err != nil {
return 0, errors.Wrap(err, "byte")
return 0, err
}
if floatDigits[c] >= 0 {
d.unread()
Expand All @@ -241,7 +242,7 @@ func (d *Decoder) Float64() (float64, error) {
}
return -v, err
default:
return 0, badToken(c)
return 0, badToken(c, d.offset())
}
}

Expand Down Expand Up @@ -355,3 +356,22 @@ func validateFloat(str []byte) error {
}
return nil
}

func (d *Decoder) floatSlow(size int) (float64, error) {
var buf [32]byte

str, err := d.numberAppend(buf[:0])
if err != nil {
return 0, errors.Wrap(err, "number")
}
if err := validateFloat(str); err != nil {
return 0, errors.Wrap(err, "invalid")
}

val, err := strconv.ParseFloat(string(str), size)
if err != nil {
return 0, err
}

return val, nil
}
8 changes: 4 additions & 4 deletions dec_float_test.go
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"
)

func decodeStr(t *testing.T, s string, f func(d *Decoder)) {
func decodeStr(t *testing.T, s string, f func(t *testing.T, d *Decoder)) {
t.Helper()
for _, d := range []struct {
Name string
Expand All @@ -36,7 +36,7 @@ func decodeStr(t *testing.T, s string, f func(d *Decoder)) {
t.Run(d.Name, func(t *testing.T) {
t.Helper()
dec := d.Fn()
f(dec)
f(t, dec)
})
}
}
Expand Down Expand Up @@ -117,14 +117,14 @@ func TestDecoder_Float64(t *testing.T) {
require.NoError(t, err)
})
t.Run("32", func(t *testing.T) {
decodeStr(t, tc.String, func(d *Decoder) {
decodeStr(t, tc.String, func(t *testing.T, d *Decoder) {
v, err := d.Float32()
require.NoError(t, err)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
})
})
t.Run("64", func(t *testing.T) {
decodeStr(t, tc.String, func(d *Decoder) {
decodeStr(t, tc.String, func(t *testing.T, d *Decoder) {
v, err := d.Float64()
require.NoError(t, err)
require.InEpsilonf(t, tc.Value, v, epsilon, "%v != %v", tc.Value, v)
Expand Down

0 comments on commit 75ecdde

Please sign in to comment.