| // 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 | |
| } | |