-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #456 from tdakkota/fix/json-parse
feat(parser): wrap errors with line and column
- Loading branch information
Showing
24 changed files
with
779 additions
and
521 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package json | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/go-json-experiment/json" | ||
) | ||
|
||
// LineColumn returns the line and column of the location. | ||
// | ||
// If offset is invalid, line and column are 0 and ok is false. | ||
func LineColumn(offset int64, data []byte) (line, column int64, ok bool) { | ||
if offset < 0 || int64(len(data)) <= offset { | ||
return 0, 0, false | ||
} | ||
|
||
{ | ||
unread := data[offset:] | ||
trimmed := bytes.TrimLeft(unread, "\x20\t\r\n,:") | ||
if len(trimmed) != len(unread) { | ||
// Skip leading whitespace, because decoder does not do it. | ||
offset += int64(len(unread) - len(trimmed)) | ||
} | ||
} | ||
|
||
lines := data[:offset] | ||
// Lines count from 1. | ||
line = int64(bytes.Count(lines, []byte("\n"))) + 1 | ||
// Find offset from last newline. | ||
lastNL := int64(bytes.LastIndexByte(lines, '\n')) | ||
column = offset - lastNL | ||
return line, column, true | ||
} | ||
|
||
// Location is a JSON value location. | ||
type Location struct { | ||
JSONPointer string `json:"-"` | ||
Offset int64 `json:"-"` | ||
Line, Column int64 `json:"-"` | ||
} | ||
|
||
// String implements fmt.Stringer. | ||
func (l Location) String() string { | ||
if l.Line == 0 { | ||
return strconv.Quote(l.JSONPointer) | ||
} | ||
return fmt.Sprintf("%d:%d", l.Line, l.Column) | ||
} | ||
|
||
// Locatable is an interface for JSON value location store. | ||
type Locatable interface { | ||
setLocation(Location) | ||
Location() (Location, bool) | ||
} | ||
|
||
// Locator stores the Location of a JSON value. | ||
type Locator struct { | ||
location Location | ||
set bool | ||
} | ||
|
||
func (l *Locator) setLocation(loc Location) { | ||
l.location = loc | ||
l.set = true | ||
} | ||
|
||
// Location returns the location of the value if it is set. | ||
func (l Locator) Location() (Location, bool) { | ||
return l.location, l.set | ||
} | ||
|
||
// LocationUnmarshaler is json.Unmarshalers that sets the location. | ||
func LocationUnmarshaler(data []byte) *json.Unmarshalers { | ||
return json.UnmarshalFuncV2(func(opts json.UnmarshalOptions, d *json.Decoder, l Locatable) error { | ||
if _, ok := l.(*Locator); ok { | ||
return nil | ||
} | ||
|
||
offset := d.InputOffset() | ||
line, column, _ := LineColumn(offset, data) | ||
l.setLocation(Location{ | ||
JSONPointer: d.StackPointer(), | ||
Offset: offset, | ||
Line: line, | ||
Column: column, | ||
}) | ||
return json.SkipFunc | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package parser | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/go-faster/errors" | ||
|
||
ogenjson "github.com/ogen-go/ogen/json" | ||
) | ||
|
||
var _ interface { | ||
error | ||
errors.Wrapper | ||
errors.Formatter | ||
} = (*LocationError)(nil) | ||
|
||
// LocationError is a wrapper for an error that has a location. | ||
type LocationError struct { | ||
file string | ||
loc ogenjson.Location | ||
err error | ||
} | ||
|
||
// Unwrap implements errors.Wrapper. | ||
func (e *LocationError) Unwrap() error { | ||
return e.err | ||
} | ||
|
||
func (e *LocationError) fileName() string { | ||
filename := e.file | ||
if filename != "" { | ||
switch { | ||
case e.loc.Line != 0: | ||
// Line is set, so return "${filename}:". | ||
filename += ":" | ||
case e.loc.JSONPointer != "": | ||
// Line is not set, but JSONPointer is set, so return "${filename}#${JSONPointer}". | ||
filename += "#" | ||
default: | ||
// Neither line nor JSONPointer is set, so return empty string. | ||
return "" | ||
} | ||
} | ||
return filename | ||
} | ||
|
||
// FormatError implements errors.Formatter. | ||
func (e *LocationError) FormatError(p errors.Printer) (next error) { | ||
p.Printf("at %s%s", e.fileName(), e.loc) | ||
return e.err | ||
} | ||
|
||
// Error implements error. | ||
func (e *LocationError) Error() string { | ||
return fmt.Sprintf("at %s%s: %s", e.fileName(), e.loc, e.err) | ||
} | ||
|
||
func (p *parser) wrapLocation(l ogenjson.Locatable, err error) error { | ||
if err == nil { | ||
return nil | ||
} | ||
loc, ok := l.Location() | ||
if !ok { | ||
return err | ||
} | ||
return &LocationError{ | ||
file: p.filename, | ||
loc: loc, | ||
err: err, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.