diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2023-02-20 12:47:10 -0700 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2023-02-20 12:47:10 -0700 |
commit | f5ca3478c68e47ae20fd12748c1552fdf81f75f9 (patch) | |
tree | b3d3f889ed25084fe33ed9e01554d6ca51104bb5 /compat/json/compat.go | |
parent | d240d0b06c7b5711f583d961eddfc37d07d4546e (diff) | |
parent | 49ee8be679add0bd3cf08a2669331b3be7a835f8 (diff) |
Merge branch 'lukeshu/fixes'
Diffstat (limited to 'compat/json/compat.go')
-rw-r--r-- | compat/json/compat.go | 150 |
1 files changed, 135 insertions, 15 deletions
diff --git a/compat/json/compat.go b/compat/json/compat.go index c96470d..695c1a8 100644 --- a/compat/json/compat.go +++ b/compat/json/compat.go @@ -11,10 +11,13 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io" "strconv" + "unicode/utf8" "git.lukeshu.com/go/lowmemjson" + "git.lukeshu.com/go/lowmemjson/internal/jsonstring" ) //nolint:stylecheck // ST1021 False positive; these aren't comments on individual types. @@ -144,7 +147,23 @@ func convertReEncodeError(err error) error { } func HTMLEscape(dst *bytes.Buffer, src []byte) { - _, _ = lowmemjson.NewReEncoder(dst, lowmemjson.ReEncoderConfig{}).Write(src) + for n := 0; n < len(src); { + c, size := utf8.DecodeRune(src[n:]) + if c == utf8.RuneError && size == 1 { + dst.WriteByte(src[n]) + } else { + mode := lowmemjson.EscapeHTMLSafe(c, lowmemjson.BackslashEscapeNone) + switch mode { + case lowmemjson.BackslashEscapeNone: + dst.WriteRune(c) + case lowmemjson.BackslashEscapeUnicode: + _ = jsonstring.WriteStringUnicodeEscape(dst, c, mode) + default: + panic(fmt.Errorf("lowmemjson.EscapeHTMLSafe returned an unexpected escape mode=%d", mode)) + } + } + n += size + } } func reencode(dst io.Writer, src []byte, cfg lowmemjson.ReEncoderConfig) error { @@ -157,38 +176,75 @@ func reencode(dst io.Writer, src []byte, cfg lowmemjson.ReEncoderConfig) error { } func Compact(dst *bytes.Buffer, src []byte) error { - return reencode(dst, src, lowmemjson.ReEncoderConfig{ + start := dst.Len() + err := reencode(dst, src, lowmemjson.ReEncoderConfig{ Compact: true, + InvalidUTF8: lowmemjson.InvalidUTF8Preserve, BackslashEscape: lowmemjson.EscapePreserve, }) + if err != nil { + dst.Truncate(start) + } + return err +} + +func isSpace(c byte) bool { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + return true + default: + return false + } } func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { - return reencode(dst, src, lowmemjson.ReEncoderConfig{ + start := dst.Len() + err := reencode(dst, src, lowmemjson.ReEncoderConfig{ Indent: indent, Prefix: prefix, + InvalidUTF8: lowmemjson.InvalidUTF8Preserve, BackslashEscape: lowmemjson.EscapePreserve, }) + if err != nil { + dst.Truncate(start) + return err + } + + // Preserve trailing whitespace. + lastNonWS := len(src) - 1 + for ; lastNonWS >= 0 && isSpace(src[lastNonWS]); lastNonWS-- { + } + if _, err := dst.Write(src[lastNonWS+1:]); err != nil { + return err + } + + return nil } func Valid(data []byte) bool { formatter := lowmemjson.NewReEncoder(io.Discard, lowmemjson.ReEncoderConfig{ - Compact: true, + Compact: true, + InvalidUTF8: lowmemjson.InvalidUTF8Error, }) - _, err := formatter.Write(data) - return err == nil + if _, err := formatter.Write(data); err != nil { + return false + } + if err := formatter.Close(); err != nil { + return false + } + return true } // Decode wrappers /////////////////////////////////////////////////// -func convertDecodeError(err error) error { +func convertDecodeError(err error, isUnmarshal bool) error { if derr, ok := err.(*lowmemjson.DecodeError); ok { switch terr := derr.Err.(type) { case *lowmemjson.DecodeSyntaxError: switch { case errors.Is(terr.Err, io.EOF): err = io.EOF - case errors.Is(terr.Err, io.ErrUnexpectedEOF): + case errors.Is(terr.Err, io.ErrUnexpectedEOF) && isUnmarshal: err = &SyntaxError{ msg: "unexpected end of JSON input", Offset: terr.Offset, @@ -228,13 +284,66 @@ func convertDecodeError(err error) error { return err } +type decodeValidator struct{} + +func (*decodeValidator) DecodeJSON(r io.RuneScanner) error { + for { + if _, _, err := r.ReadRune(); err != nil { + + if err == io.EOF { + return nil + } + return err + } + } +} + +var _ lowmemjson.Decodable = (*decodeValidator)(nil) + func Unmarshal(data []byte, ptr any) error { - return convertDecodeError(lowmemjson.NewDecoder(bytes.NewReader(data)).DecodeThenEOF(ptr)) + if err := convertDecodeError(lowmemjson.NewDecoder(bytes.NewReader(data)).DecodeThenEOF(&decodeValidator{}), true); err != nil { + return err + } + if err := convertDecodeError(lowmemjson.NewDecoder(bytes.NewReader(data)).DecodeThenEOF(ptr), true); err != nil { + return err + } + return nil +} + +type teeRuneScanner struct { + src io.RuneScanner + dst *bytes.Buffer + lastSize int +} + +func (tee *teeRuneScanner) ReadRune() (r rune, size int, err error) { + r, size, err = tee.src.ReadRune() + if err == nil { + if _, err := tee.dst.WriteRune(r); err != nil { + return 0, 0, err + } + } + + tee.lastSize = size + return +} + +func (tee *teeRuneScanner) UnreadRune() error { + if tee.lastSize == 0 { + return lowmemjson.ErrInvalidUnreadRune + } + _ = tee.src.UnreadRune() + tee.dst.Truncate(tee.dst.Len() - tee.lastSize) + tee.lastSize = 0 + return nil } type Decoder struct { + validatorBuf *bufio.Reader + validator *lowmemjson.Decoder + + decoderBuf bytes.Buffer *lowmemjson.Decoder - buf *bufio.Reader } func NewDecoder(r io.Reader) *Decoder { @@ -242,18 +351,29 @@ func NewDecoder(r io.Reader) *Decoder { if !ok { br = bufio.NewReader(r) } - return &Decoder{ - Decoder: lowmemjson.NewDecoder(br), - buf: br, + ret := &Decoder{ + validatorBuf: br, } + ret.validator = lowmemjson.NewDecoder(&teeRuneScanner{ + src: ret.validatorBuf, + dst: &ret.decoderBuf, + }) + ret.Decoder = lowmemjson.NewDecoder(&ret.decoderBuf) + return ret } func (dec *Decoder) Decode(ptr any) error { - return convertDecodeError(dec.Decoder.Decode(ptr)) + if err := convertDecodeError(dec.validator.Decode(&decodeValidator{}), false); err != nil { + return err + } + if err := convertDecodeError(dec.Decoder.Decode(ptr), false); err != nil { + return err + } + return nil } func (dec *Decoder) Buffered() io.Reader { - dat, _ := dec.buf.Peek(dec.buf.Buffered()) + dat, _ := dec.validatorBuf.Peek(dec.validatorBuf.Buffered()) return bytes.NewReader(dat) } |