Display better error messages on json.SyntaxError
Better display an error message on an encounter of a json.SyntaxError. Rolls back the file position, to read the entire file, then steps through the file reading a single byte at a time, populating lines until encountering the syntax error. Then relays the offending line as well as the previous line in the file to the user, also placing a `^` that points the the offending column of the decoder error. ``` ➤ packer validate template.json Failed to parse template: Error parsing JSON: invalid character '"' after object key:value pair At line 9, column 8 (offset 121): 8: "name": "vbox" 9: " ```
This commit is contained in:
parent
314aad379a
commit
aca4aed47c
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
@ -311,24 +312,6 @@ func Parse(r io.Reader) (*Template, error) {
|
|||
return rawTpl.Template()
|
||||
}
|
||||
|
||||
// Find line number and position based on the offset
|
||||
func findLinePos(f *os.File, offset int64) (int64, int64, string) {
|
||||
scanner := bufio.NewScanner(f)
|
||||
count := int64(0)
|
||||
for scanner.Scan() {
|
||||
count += 1
|
||||
scanLength := len(scanner.Text()) + 1
|
||||
if offset < int64(scanLength) {
|
||||
return count, offset, scanner.Text()
|
||||
}
|
||||
offset = offset - int64(scanLength)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return 0, 0, err.Error()
|
||||
}
|
||||
return 0, 0, ""
|
||||
}
|
||||
|
||||
// ParseFile is the same as Parse but is a helper to automatically open
|
||||
// a file for parsing.
|
||||
func ParseFile(path string) (*Template, error) {
|
||||
|
@ -359,8 +342,9 @@ func ParseFile(path string) (*Template, error) {
|
|||
}
|
||||
// Rewind the file and get a better error
|
||||
f.Seek(0, os.SEEK_SET)
|
||||
line, pos, errorLine := findLinePos(f, syntaxErr.Offset)
|
||||
err = fmt.Errorf("Error in line %d, char %d: %s\n%s", line, pos, syntaxErr, errorLine)
|
||||
// Grab the error location, and return a string to point to offending syntax error
|
||||
line, col, highlight := highlightPosition(f, syntaxErr.Offset)
|
||||
err = fmt.Errorf("Error parsing JSON: %s\nAt line %d, column %d (offset %d):\n%s", err, line, col, syntaxErr.Offset, highlight)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -374,3 +358,46 @@ func ParseFile(path string) (*Template, error) {
|
|||
tpl.Path = path
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
// Takes a file and the location in bytes of a parse error
|
||||
// from json.SyntaxError.Offset and returns the line, column,
|
||||
// and pretty-printed context around the error with an arrow indicating the exact
|
||||
// position of the syntax error.
|
||||
func highlightPosition(f *os.File, pos int64) (line, col int, highlight string) {
|
||||
// Modified version of the function in Camlistore by Brad Fitzpatrick
|
||||
// https://github.com/camlistore/camlistore/blob/4b5403dd5310cf6e1ae8feb8533fd59262701ebc/vendor/go4.org/errorutil/highlight.go
|
||||
line = 1
|
||||
// New io.Reader for file
|
||||
br := bufio.NewReader(f)
|
||||
// Initialize lines
|
||||
lastLine := ""
|
||||
thisLine := new(bytes.Buffer)
|
||||
// Loop through template to find line, column
|
||||
for n := int64(0); n < pos; n++ {
|
||||
// read byte from io.Reader
|
||||
b, err := br.ReadByte()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// If end of line, save line as previous line in case next line is offender
|
||||
if b == '\n' {
|
||||
lastLine = thisLine.String()
|
||||
thisLine.Reset()
|
||||
line++
|
||||
col = 1
|
||||
} else {
|
||||
// Write current line, until line is safe, or error point is encountered
|
||||
col++
|
||||
thisLine.WriteByte(b)
|
||||
}
|
||||
}
|
||||
|
||||
// Populate highlight string to place a '^' char at offending column
|
||||
if line > 1 {
|
||||
highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine)
|
||||
}
|
||||
|
||||
highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String())
|
||||
highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -356,9 +356,9 @@ func TestParse_bad(t *testing.T) {
|
|||
File string
|
||||
Expected string
|
||||
}{
|
||||
{"error-beginning.json", "line 1, char 1"},
|
||||
{"error-middle.json", "line 5, char 5"},
|
||||
{"error-end.json", "line 1, char 30"},
|
||||
{"error-beginning.json", "line 1, column 1 (offset 1)"},
|
||||
{"error-middle.json", "line 5, column 6 (offset 50)"},
|
||||
{"error-end.json", "line 1, column 30 (offset 30)"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
_, err := ParseFile(fixtureDir(tc.File))
|
||||
|
|
Loading…
Reference in New Issue