add line numbers for parser errors (#7326)
This commit is contained in:
parent
0679d491fe
commit
4efb98cb4f
2 changed files with 68 additions and 9 deletions
|
@ -65,9 +65,22 @@ var (
|
||||||
errInvalidCommand = errors.New("command must be one of \"from\", \"license\", \"template\", \"system\", \"adapter\", \"parameter\", or \"message\"")
|
errInvalidCommand = errors.New("command must be one of \"from\", \"license\", \"template\", \"system\", \"adapter\", \"parameter\", or \"message\"")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ParserError struct {
|
||||||
|
LineNumber int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ParserError) Error() string {
|
||||||
|
if e.LineNumber > 0 {
|
||||||
|
return fmt.Sprintf("(line %d): %s", e.LineNumber, e.Msg)
|
||||||
|
}
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
func ParseFile(r io.Reader) (*File, error) {
|
func ParseFile(r io.Reader) (*File, error) {
|
||||||
var cmd Command
|
var cmd Command
|
||||||
var curr state
|
var curr state
|
||||||
|
var currLine int = 1
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
var role string
|
var role string
|
||||||
|
|
||||||
|
@ -84,11 +97,18 @@ func ParseFile(r io.Reader) (*File, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isNewline(r) {
|
||||||
|
currLine++
|
||||||
|
}
|
||||||
|
|
||||||
next, r, err := parseRuneForState(r, curr)
|
next, r, err := parseRuneForState(r, curr)
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
return nil, fmt.Errorf("%w: %s", err, b.String())
|
return nil, fmt.Errorf("%w: %s", err, b.String())
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, &ParserError{
|
||||||
|
LineNumber: currLine,
|
||||||
|
Msg: err.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process the state transition, some transitions need to be intercepted and redirected
|
// process the state transition, some transitions need to be intercepted and redirected
|
||||||
|
@ -96,7 +116,10 @@ func ParseFile(r io.Reader) (*File, error) {
|
||||||
switch curr {
|
switch curr {
|
||||||
case stateName:
|
case stateName:
|
||||||
if !isValidCommand(b.String()) {
|
if !isValidCommand(b.String()) {
|
||||||
return nil, errInvalidCommand
|
return nil, &ParserError{
|
||||||
|
LineNumber: currLine,
|
||||||
|
Msg: errInvalidCommand.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// next state sometimes depends on the current buffer value
|
// next state sometimes depends on the current buffer value
|
||||||
|
@ -117,7 +140,10 @@ func ParseFile(r io.Reader) (*File, error) {
|
||||||
cmd.Name = b.String()
|
cmd.Name = b.String()
|
||||||
case stateMessage:
|
case stateMessage:
|
||||||
if !isValidMessageRole(b.String()) {
|
if !isValidMessageRole(b.String()) {
|
||||||
return nil, errInvalidMessageRole
|
return nil, &ParserError{
|
||||||
|
LineNumber: currLine,
|
||||||
|
Msg: errInvalidMessageRole.Error(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
role = b.String()
|
role = b.String()
|
||||||
|
|
|
@ -3,6 +3,7 @@ package parser
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -180,8 +181,15 @@ func TestParseFileBadCommand(t *testing.T) {
|
||||||
FROM foo
|
FROM foo
|
||||||
BADCOMMAND param1 value1
|
BADCOMMAND param1 value1
|
||||||
`
|
`
|
||||||
|
parserError := &ParserError{
|
||||||
|
LineNumber: 3,
|
||||||
|
Msg: errInvalidCommand.Error(),
|
||||||
|
}
|
||||||
|
|
||||||
_, err := ParseFile(strings.NewReader(input))
|
_, err := ParseFile(strings.NewReader(input))
|
||||||
require.ErrorIs(t, err, errInvalidCommand)
|
if !errors.As(err, &parserError) {
|
||||||
|
t.Errorf("unexpected error: expected: %s, actual: %s", parserError.Error(), err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseFileMessages(t *testing.T) {
|
func TestParseFileMessages(t *testing.T) {
|
||||||
|
@ -245,7 +253,10 @@ FROM foo
|
||||||
MESSAGE badguy I'm a bad guy!
|
MESSAGE badguy I'm a bad guy!
|
||||||
`,
|
`,
|
||||||
nil,
|
nil,
|
||||||
errInvalidMessageRole,
|
&ParserError{
|
||||||
|
LineNumber: 3,
|
||||||
|
Msg: errInvalidMessageRole.Error(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`
|
`
|
||||||
|
@ -264,13 +275,35 @@ MESSAGE system`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, tt := range cases {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
modelfile, err := ParseFile(strings.NewReader(c.input))
|
modelfile, err := ParseFile(strings.NewReader(tt.input))
|
||||||
require.ErrorIs(t, err, c.err)
|
|
||||||
if modelfile != nil {
|
if modelfile != nil {
|
||||||
assert.Equal(t, c.expected, modelfile.Commands)
|
assert.Equal(t, tt.expected, modelfile.Commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.err == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, but got %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt.err.(type) {
|
||||||
|
case *ParserError:
|
||||||
|
var pErr *ParserError
|
||||||
|
if errors.As(err, &pErr) {
|
||||||
|
// got the correct type of error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, tt.err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("unexpected error: expected: %v, actual: %v", tt.err, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue