aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/json/jsontext/errors.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-04-11 14:19:51 -0700
committerGopher Robot <gobot@golang.org>2025-04-18 08:24:07 -0700
commit0e17905793cb5e0acc323a0cdf3733199d93976a (patch)
treefec117ceb6b56866e6c51e6acd72901cf91717ce /src/encoding/json/jsontext/errors.go
parentc889004615b40535ebd5054cbcf2deebdb3a299a (diff)
downloadgo-0e17905793cb5e0acc323a0cdf3733199d93976a.tar.xz
encoding/json: add json/v2 with GOEXPERIMENT=jsonv2 guard
This imports the proposed new v2 JSON API implemented in github.com/go-json-experiment/json as of commit d3c622f1b874954c355e60c8e6b6baa5f60d2fed. When GOEXPERIMENT=jsonv2 is set, the encoding/json/v2 and encoding/jsontext packages are visible, the encoding/json package is implemented in terms of encoding/json/v2, and the encoding/json package include various additional APIs. (See #71497 for details.) When GOEXPERIMENT=jsonv2 is not set, the new API is not present and the encoding/json package is unchanged. The experimental API is not bound by the Go compatibility promise and is expected to evolve as updates are made to the json/v2 proposal. The contents of encoding/json/internal/jsontest/testdata are compressed with zstd v1.5.7 with the -19 option. Fixes #71845 For #71497 Change-Id: Ib8c94e5f0586b6aaa22833190b41cf6ef59f4f01 Reviewed-on: https://go-review.googlesource.com/c/go/+/665796 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Joseph Tsai <joetsai@digital-static.net> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/encoding/json/jsontext/errors.go')
-rw-r--r--src/encoding/json/jsontext/errors.go182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/encoding/json/jsontext/errors.go b/src/encoding/json/jsontext/errors.go
new file mode 100644
index 0000000000..4b95d03f40
--- /dev/null
+++ b/src/encoding/json/jsontext/errors.go
@@ -0,0 +1,182 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.jsonv2
+
+package jsontext
+
+import (
+ "bytes"
+ "io"
+ "strconv"
+
+ "encoding/json/internal/jsonwire"
+)
+
+const errorPrefix = "jsontext: "
+
+type ioError struct {
+ action string // either "read" or "write"
+ err error
+}
+
+func (e *ioError) Error() string {
+ return errorPrefix + e.action + " error: " + e.err.Error()
+}
+func (e *ioError) Unwrap() error {
+ return e.err
+}
+
+// SyntacticError is a description of a syntactic error that occurred when
+// encoding or decoding JSON according to the grammar.
+//
+// The contents of this error as produced by this package may change over time.
+type SyntacticError struct {
+ requireKeyedLiterals
+ nonComparable
+
+ // ByteOffset indicates that an error occurred after this byte offset.
+ ByteOffset int64
+ // JSONPointer indicates that an error occurred within this JSON value
+ // as indicated using the JSON Pointer notation (see RFC 6901).
+ JSONPointer Pointer
+
+ // Err is the underlying error.
+ Err error
+}
+
+// wrapSyntacticError wraps an error and annotates it with a precise location
+// using the provided [encoderState] or [decoderState].
+// If err is an [ioError] or [io.EOF], then it is not wrapped.
+//
+// It takes a relative offset pos that can be resolved into
+// an absolute offset using state.offsetAt.
+//
+// It takes a where that specify how the JSON pointer is derived.
+// If the underlying error is a [pointerSuffixError],
+// then the suffix is appended to the derived pointer.
+func wrapSyntacticError(state interface {
+ offsetAt(pos int) int64
+ AppendStackPointer(b []byte, where int) []byte
+}, err error, pos, where int) error {
+ if _, ok := err.(*ioError); err == io.EOF || ok {
+ return err
+ }
+ offset := state.offsetAt(pos)
+ ptr := state.AppendStackPointer(nil, where)
+ if serr, ok := err.(*pointerSuffixError); ok {
+ ptr = serr.appendPointer(ptr)
+ err = serr.error
+ }
+ if d, ok := state.(*decoderState); ok && err == errMismatchDelim {
+ where := "at start of value"
+ if len(d.Tokens.Stack) > 0 && d.Tokens.Last.Length() > 0 {
+ switch {
+ case d.Tokens.Last.isArray():
+ where = "after array element (expecting ',' or ']')"
+ ptr = []byte(Pointer(ptr).Parent()) // problem is with parent array
+ case d.Tokens.Last.isObject():
+ where = "after object value (expecting ',' or '}')"
+ ptr = []byte(Pointer(ptr).Parent()) // problem is with parent object
+ }
+ }
+ err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
+ }
+ return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err}
+}
+
+func (e *SyntacticError) Error() string {
+ pointer := e.JSONPointer
+ offset := e.ByteOffset
+ b := []byte(errorPrefix)
+ if e.Err != nil {
+ b = append(b, e.Err.Error()...)
+ if e.Err == ErrDuplicateName {
+ b = strconv.AppendQuote(append(b, ' '), pointer.LastToken())
+ pointer = pointer.Parent()
+ offset = 0 // not useful to print offset for duplicate names
+ }
+ } else {
+ b = append(b, "syntactic error"...)
+ }
+ if pointer != "" {
+ b = strconv.AppendQuote(append(b, " within "...), jsonwire.TruncatePointer(string(pointer), 100))
+ }
+ if offset > 0 {
+ b = strconv.AppendInt(append(b, " after offset "...), offset, 10)
+ }
+ return string(b)
+}
+
+func (e *SyntacticError) Unwrap() error {
+ return e.Err
+}
+
+// pointerSuffixError represents a JSON pointer suffix to be appended
+// to [SyntacticError.JSONPointer]. It is an internal error type
+// used within this package and does not appear in the public API.
+//
+// This type is primarily used to annotate errors in Encoder.WriteValue
+// and Decoder.ReadValue with precise positions.
+// At the time WriteValue or ReadValue is called, a JSON pointer to the
+// upcoming value can be constructed using the Encoder/Decoder state.
+// However, tracking pointers within values during normal operation
+// would incur a performance penalty in the error-free case.
+//
+// To provide precise error locations without this overhead,
+// the error is wrapped with object names or array indices
+// as the call stack is popped when an error occurs.
+// Since this happens in reverse order, pointerSuffixError holds
+// the pointer in reverse and is only later reversed when appending to
+// the pointer prefix.
+//
+// For example, if the encoder is at "/alpha/bravo/charlie"
+// and an error occurs in WriteValue at "/xray/yankee/zulu", then
+// the final pointer should be "/alpha/bravo/charlie/xray/yankee/zulu".
+//
+// As pointerSuffixError is populated during the error return path,
+// it first contains "/zulu", then "/zulu/yankee",
+// and finally "/zulu/yankee/xray".
+// These tokens are reversed and concatenated to "/alpha/bravo/charlie"
+// to form the full pointer.
+type pointerSuffixError struct {
+ error
+
+ // reversePointer is a JSON pointer, but with each token in reverse order.
+ reversePointer []byte
+}
+
+// wrapWithObjectName wraps err with a JSON object name access,
+// which must be a valid quoted JSON string.
+func wrapWithObjectName(err error, quotedName []byte) error {
+ serr, _ := err.(*pointerSuffixError)
+ if serr == nil {
+ serr = &pointerSuffixError{error: err}
+ }
+ name := jsonwire.UnquoteMayCopy(quotedName, false)
+ serr.reversePointer = appendEscapePointerName(append(serr.reversePointer, '/'), name)
+ return serr
+}
+
+// wrapWithArrayIndex wraps err with a JSON array index access.
+func wrapWithArrayIndex(err error, index int64) error {
+ serr, _ := err.(*pointerSuffixError)
+ if serr == nil {
+ serr = &pointerSuffixError{error: err}
+ }
+ serr.reversePointer = strconv.AppendUint(append(serr.reversePointer, '/'), uint64(index), 10)
+ return serr
+}
+
+// appendPointer appends the path encoded in e to the end of pointer.
+func (e *pointerSuffixError) appendPointer(pointer []byte) []byte {
+ // Copy each token in reversePointer to the end of pointer in reverse order.
+ // Double reversal means that the appended suffix is now in forward order.
+ bi, bo := e.reversePointer, pointer
+ for len(bi) > 0 {
+ i := bytes.LastIndexByte(bi, '/')
+ bi, bo = bi[:i], append(bo, bi[i:]...)
+ }
+ return bo
+}