summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@datawire.io>2022-08-16 21:50:07 -0600
committerLuke Shumaker <lukeshu@datawire.io>2022-08-17 00:34:48 -0600
commit111cdaffb625f7db9b8c5a27e386999944346e0d (patch)
treecdb6493361b4343cd2cb05ef76b77cfc5a682a1b
parent0e6d82b97165e9cdd294aaeac02bbbf26263877a (diff)
Fix those now-failing error checks
-rw-r--r--compat/json/compat.go11
-rw-r--r--decode.go29
-rw-r--r--reencode.go40
3 files changed, 71 insertions, 9 deletions
diff --git a/compat/json/compat.go b/compat/json/compat.go
index 8d6ec35..59eff6c 100644
--- a/compat/json/compat.go
+++ b/compat/json/compat.go
@@ -78,6 +78,9 @@ func convertReEncodeError(err error) error {
msg: se.Err.Error(),
Offset: se.Offset + 1,
}
+ if errors.Is(se.Err, io.ErrUnexpectedEOF) {
+ err.(*SyntaxError).msg = "unexpected end of JSON input"
+ }
}
return err
}
@@ -91,6 +94,9 @@ func HTMLEscape(dst *bytes.Buffer, src []byte) {
func reencode(src []byte, formatter *lowmemjson.ReEncoder) error {
_, err := formatter.Write(src)
+ if err == nil {
+ err = formatter.Close()
+ }
return convertReEncodeError(err)
}
@@ -130,6 +136,9 @@ func convertDecodeError(err error) error {
msg: terr.Err.Error(),
Offset: terr.Offset,
}
+ if errors.Is(terr.Err, io.ErrUnexpectedEOF) {
+ err.(*SyntaxError).msg = "unexpected end of JSON input"
+ }
case *lowmemjson.DecodeTypeError:
if typeErr, ok := terr.Err.(*json.UnmarshalTypeError); ok {
err = &UnmarshalTypeError{
@@ -160,7 +169,7 @@ func convertDecodeError(err error) error {
}
func Unmarshal(data []byte, ptr any) error {
- return convertDecodeError(lowmemjson.Decode(bytes.NewReader(data), ptr))
+ return convertDecodeError(lowmemjson.DecodeThenEOF(bytes.NewReader(data), ptr))
}
type Decoder struct {
diff --git a/decode.go b/decode.go
index a17a572..52fcb5e 100644
--- a/decode.go
+++ b/decode.go
@@ -121,6 +121,26 @@ func Decode(r interface {
return NewDecoder(r).Decode(ptr)
}
+// DecodeThenEOF is like decode, but emits an error if there is extra data after the JSON.
+func DecodeThenEOF(r interface {
+ io.Reader
+ io.RuneScanner
+}, ptr any) error {
+ dec := NewDecoder(r)
+ if err := dec.Decode(ptr); err != nil {
+ return err
+ }
+ if c, _, err := r.ReadRune(); err == nil {
+ return &DecodeError{
+ Err: &DecodeSyntaxError{
+ Err: fmt.Errorf("invalid character %q after top-level value", c),
+ Offset: dec.InputOffset(),
+ },
+ }
+ }
+ return nil
+}
+
func (dec *Decoder) Decode(ptr any) (err error) {
ptrVal := reflect.ValueOf(ptr)
if ptrVal.Kind() != reflect.Pointer || ptrVal.IsNil() || !ptrVal.Elem().CanSet() {
@@ -147,6 +167,15 @@ func (dec *Decoder) Decode(ptr any) (err error) {
}
}()
dec.decode(ptrVal.Elem(), false)
+ for {
+ _, s, t, _ := dec.io.ReadRuneType()
+ if t == RuneTypeEOF || t == RuneTypeError {
+ if s > 0 {
+ dec.io.UnreadRune()
+ }
+ break
+ }
+ }
return nil
}
diff --git a/reencode.go b/reencode.go
index 1e5f5aa..7c5ce52 100644
--- a/reencode.go
+++ b/reencode.go
@@ -5,7 +5,6 @@
package lowmemjson
import (
- "errors"
"fmt"
"io"
"unicode/utf8"
@@ -16,6 +15,8 @@ type reencodeState func(rune) error
type ReEncoder struct {
Out io.Writer
+ AllowMultipleValues bool
+
// Whether to minify the JSON.
Compact bool
// String to use to indent; ignored if Compact is true.
@@ -83,13 +84,22 @@ func (enc *ReEncoder) Close() error {
}
}
if _, err := enc.par.HandleEOF(); err != nil {
- enc.err = err
+ enc.err = &ReEncodeSyntaxError{
+ Err: err,
+ Offset: enc.inputPos,
+ }
return enc.err
}
if err := enc.handleRune(0, 0); err != nil {
- enc.err = err
+ enc.err = &ReEncodeSyntaxError{
+ Err: err,
+ Offset: enc.inputPos,
+ }
return enc.err
}
+ if enc.AllowMultipleValues {
+ enc.par.Reset()
+ }
return nil
}
@@ -97,22 +107,36 @@ func (enc *ReEncoder) WriteRune(c rune) (n int, err error) {
if enc.err != nil {
return 0, enc.err
}
- if enc.bufLen != 0 {
- enc.err = errors.New("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()n")
+ if enc.bufLen > 0 {
+ enc.err = fmt.Errorf("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()en: %q", enc.buf[:enc.bufLen])
return 0, enc.err
}
+ enc.written = 0
+
+rehandle:
t, err := enc.par.HandleRune(c)
if err != nil {
enc.err = &ReEncodeSyntaxError{
Err: err,
Offset: enc.inputPos,
}
- return 0, enc.err
+ return enc.written, enc.err
}
-
- enc.written = 0
enc.err = enc.handleRune(c, t)
+ if enc.err == nil && t == RuneTypeEOF {
+ if enc.AllowMultipleValues {
+ enc.par.Reset()
+ goto rehandle
+ } else {
+ enc.err = &ReEncodeSyntaxError{
+ Err: fmt.Errorf("invalid character %q after top-level value", c),
+ Offset: enc.inputPos,
+ }
+ return enc.written, enc.err
+ }
+ }
+
enc.inputPos += int64(utf8.RuneLen(c))
return enc.written, enc.err
}