diff --git a/def/error.go b/def/error.go index 917512a..4b49182 100644 --- a/def/error.go +++ b/def/error.go @@ -7,4 +7,6 @@ var ( ErrCanNotDecode = errors.New("msgpack : invalid code") ErrCanNotSetSliceAsMapKey = errors.New("can not set slice as map key") ErrCanNotSetMapAsMapKey = errors.New("can not set map as map key") + + ErrTooShortBytes = errors.New("too short bytes") ) diff --git a/internal/common/testutil/testutil.go b/internal/common/testutil/testutil.go index d4054b1..fded725 100644 --- a/internal/common/testutil/testutil.go +++ b/internal/common/testutil/testutil.go @@ -10,7 +10,7 @@ import ( func NoError(t *testing.T, err error) { t.Helper() if err != nil { - t.Fatal(err) + t.Fatalf("error is not nil: %v", err) } } diff --git a/internal/decoding/bin.go b/internal/decoding/bin.go index 7789640..65fb859 100644 --- a/internal/decoding/bin.go +++ b/internal/decoding/bin.go @@ -28,22 +28,31 @@ func (d *decoder) asBin(offset int, k reflect.Kind) ([]byte, int, error) { if err != nil { return emptyBytes, 0, err } - o := offset + int(uint8(l)) - return d.data[offset:o], o, nil + v, offset, err := d.readSizeN(offset, int(uint8(l))) + if err != nil { + return emptyBytes, 0, err + } + return v, offset, nil case def.Bin16: bs, offset, err := d.readSize2(offset) - o := offset + int(binary.BigEndian.Uint16(bs)) if err != nil { return emptyBytes, 0, err } - return d.data[offset:o], o, nil + v, offset, err := d.readSizeN(offset, int(binary.BigEndian.Uint16(bs))) + if err != nil { + return emptyBytes, 0, err + } + return v, offset, nil case def.Bin32: bs, offset, err := d.readSize4(offset) - o := offset + int(binary.BigEndian.Uint32(bs)) if err != nil { return emptyBytes, 0, err } - return d.data[offset:o], o, nil + v, offset, err := d.readSizeN(offset, int(binary.BigEndian.Uint32(bs))) + if err != nil { + return emptyBytes, 0, err + } + return v, offset, nil } return emptyBytes, 0, d.errorTemplate(code, k) diff --git a/internal/decoding/bin_test.go b/internal/decoding/bin_test.go new file mode 100644 index 0000000..bd4d66c --- /dev/null +++ b/internal/decoding/bin_test.go @@ -0,0 +1,93 @@ +package decoding + +import ( + "reflect" + "testing" + + "github.com/shamaton/msgpack/v2/def" + tu "github.com/shamaton/msgpack/v2/internal/common/testutil" +) + +func Test_isCodeBin(t *testing.T) { + d := decoder{} + for i := 0x00; i <= 0xff; i++ { + v := byte(i) + isBin := v == def.Bin8 || v == def.Bin16 || v == def.Bin32 + tu.Equal(t, d.isCodeBin(v), isBin) + } +} + +func Test_asBin(t *testing.T) { + method := func(d *decoder) func(int, reflect.Kind) ([]byte, int, error) { + return d.asBin + } + testcases := AsXXXTestCases[[]byte]{ + { + Name: "error.code", + Data: []byte{}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin8.error.size", + Data: []byte{def.Bin8}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin8.error.data", + Data: []byte{def.Bin8, 1}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin8.ok", + Data: []byte{def.Bin8, 1, 'a'}, + Expected: []byte{'a'}, + MethodAs: method, + }, + { + Name: "Bin16.error.size", + Data: []byte{def.Bin16}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin16.error.data", + Data: []byte{def.Bin16, 0, 1}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin16.ok", + Data: []byte{def.Bin16, 0, 1, 'b'}, + Expected: []byte{'b'}, + MethodAs: method, + }, + { + Name: "Bin32.error.size", + Data: []byte{def.Bin32}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin32.error.data", + Data: []byte{def.Bin32, 0, 1}, + Error: def.ErrTooShortBytes, + MethodAs: method, + }, + { + Name: "Bin32.ok", + Data: []byte{def.Bin32, 0, 0, 0, 1, 'c'}, + Expected: []byte{'c'}, + MethodAs: method, + }, + { + Name: "Unexpected", + Data: []byte{def.Nil}, + Error: def.ErrCanNotDecode, + MethodAs: method, + }, + } + testcases.Run(t) +} diff --git a/internal/decoding/decoding.go b/internal/decoding/decoding.go index a1b064d..0c23892 100644 --- a/internal/decoding/decoding.go +++ b/internal/decoding/decoding.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" + "github.com/shamaton/msgpack/v2/def" "github.com/shamaton/msgpack/v2/internal/common" ) @@ -338,5 +339,5 @@ func (d *decoder) decode(rv reflect.Value, offset int) (int, error) { } func (d *decoder) errorTemplate(code byte, k reflect.Kind) error { - return fmt.Errorf("msgpack : invalid code %x decoding %v", code, k) + return fmt.Errorf("msgpack : invalid code %x decoding %v, %w", code, k, def.ErrCanNotDecode) } diff --git a/internal/decoding/decoding_test.go b/internal/decoding/decoding_test.go new file mode 100644 index 0000000..b17c32e --- /dev/null +++ b/internal/decoding/decoding_test.go @@ -0,0 +1,64 @@ +package decoding + +import ( + "reflect" + "testing" + + tu "github.com/shamaton/msgpack/v2/internal/common/testutil" +) + +type AsXXXTestCase[T any] struct { + Name string + Code byte + Data []byte + Expected T + Error error + MethodAs func(d *decoder) func(int, reflect.Kind) (T, int, error) + MethodAsWithCode func(d *decoder) func(byte, reflect.Kind) (T, int, error) + MethodAsCustom func(d *decoder) (T, int, error) +} + +type AsXXXTestCases[T any] []AsXXXTestCase[T] + +func (tcs AsXXXTestCases[T]) Run(t *testing.T) { + for _, tc := range tcs { + tc.Run(t) + } +} + +func (tc *AsXXXTestCase[T]) Run(t *testing.T) { + const kind = reflect.String + t.Helper() + + if tc.MethodAs == nil && tc.MethodAsWithCode == nil && tc.MethodAsCustom == nil { + t.Fatal("must set either method or methodAsWithCode or MethodAsCustom") + } + + methodAs := func(d *decoder) (T, int, error) { + if tc.MethodAs != nil { + return tc.MethodAs(d)(0, kind) + } + if tc.MethodAsWithCode != nil { + return tc.MethodAsWithCode(d)(tc.Code, kind) + } + if tc.MethodAsCustom != nil { + return tc.MethodAsCustom(d) + } + panic("unreachable") + } + + t.Run(tc.Name, func(t *testing.T) { + d := decoder{ + data: tc.Data, + } + + v, offset, err := methodAs(&d) + if tc.Error != nil { + tu.IsError(t, err, tc.Error) + return + } + tu.NoError(t, err) + tu.Equal(t, v, tc.Expected) + tu.Equal(t, offset, len(tc.Data)) + }) +} diff --git a/internal/decoding/read.go b/internal/decoding/read.go index 5d976bc..dcb9e32 100644 --- a/internal/decoding/read.go +++ b/internal/decoding/read.go @@ -1,15 +1,13 @@ package decoding import ( - "errors" - "github.com/shamaton/msgpack/v2/def" ) func (d *decoder) readSize1(index int) (byte, int, error) { rb := def.Byte1 if len(d.data) < index+rb { - return 0, 0, errors.New("too short bytes") + return 0, 0, def.ErrTooShortBytes } return d.data[index], index + rb, nil } @@ -28,7 +26,7 @@ func (d *decoder) readSize8(index int) ([]byte, int, error) { func (d *decoder) readSizeN(index, n int) ([]byte, int, error) { if len(d.data) < index+n { - return emptyBytes, 0, errors.New("too short bytes") + return emptyBytes, 0, def.ErrTooShortBytes } return d.data[index : index+n], index + n, nil }