You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
6.0 KiB
182 lines
6.0 KiB
// 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 |
|
}
|
|
|