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