improve parser error.
This commit is contained in:
@@ -2,14 +2,26 @@ package parsers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNotPost is returned by parsePost if the line is empty or not a post.
|
||||
var ErrNotPost = errors.New("not a post")
|
||||
type ParseError struct {
|
||||
Line string
|
||||
Problem string
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
return fmt.Sprintf("Unrecognized post: %s (error: %s)", e.Line, e.Problem)
|
||||
}
|
||||
|
||||
func IsParseError(err error) bool {
|
||||
_, ok := err.(*ParseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log.
|
||||
var ErrEmptyLog = errors.New("no valid posts found in log")
|
||||
@@ -56,24 +68,36 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error
|
||||
// Do basic validation
|
||||
line = strings.Trim(line, " \t\n\r")
|
||||
if len(line) == 0 || !strings.HasPrefix(line, "[") {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "no timestamp",
|
||||
}
|
||||
}
|
||||
|
||||
// Parse timestamp
|
||||
tsEndIndex := strings.IndexByte(line, ']')
|
||||
if tsEndIndex == -1 || len(line) < tsEndIndex+5 {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "incomplete timestamp",
|
||||
}
|
||||
}
|
||||
tsStr := line[1:tsEndIndex]
|
||||
tsSplit := strings.Split(tsStr, ":")
|
||||
tsUnits := make([]int, len(tsSplit))
|
||||
if len(tsSplit) < 2 {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "invalid timestamp",
|
||||
}
|
||||
}
|
||||
for i := range tsSplit {
|
||||
n, err := strconv.Atoi(tsSplit[i])
|
||||
if err != nil {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "invalid number in timestamp",
|
||||
}
|
||||
}
|
||||
|
||||
tsUnits[i] = n
|
||||
@@ -94,7 +118,10 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error
|
||||
if line[tsEndIndex+2] == '*' {
|
||||
split := strings.SplitN(line[tsEndIndex+4:], " ", 2)
|
||||
if len(split) == 1 {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "post is empty",
|
||||
}
|
||||
}
|
||||
|
||||
post := models.Post{
|
||||
@@ -115,7 +142,10 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error
|
||||
} else if line[tsEndIndex+2] == '<' {
|
||||
split := strings.SplitN(line[tsEndIndex+2:], " ", 2)
|
||||
if len(split) == 1 {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "post is empty",
|
||||
}
|
||||
}
|
||||
|
||||
post := models.Post{
|
||||
@@ -134,6 +164,9 @@ func MircPost(line string, date time.Time, prev models.Post) (models.Post, error
|
||||
|
||||
return post, nil
|
||||
} else {
|
||||
return models.Post{}, ErrNotPost
|
||||
return models.Post{}, &ParseError{
|
||||
Line: line,
|
||||
Problem: "line is neither action nor text post",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,29 +58,38 @@ func TestMircPost(t *testing.T) {
|
||||
func TestMircPostErrors(t *testing.T) {
|
||||
table := []struct {
|
||||
Input string
|
||||
Err error
|
||||
Err string
|
||||
}{
|
||||
{"[12:34] <Stuff> Things said.", nil},
|
||||
{"[12:34] >Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"12:34] <Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"* Stuff Things said.", parsers.ErrNotPost},
|
||||
{"", parsers.ErrNotPost},
|
||||
{"[12:34 <Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"[TE:XT] <Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"[10] <Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"[12:34:56:789] <Stuff> Things said.", nil},
|
||||
{"[12:34:56.789] <Stuff> Things said.", parsers.ErrNotPost},
|
||||
{"[12:34] <Stuff>", parsers.ErrNotPost},
|
||||
{"[12:34] * Stuff", parsers.ErrNotPost},
|
||||
{"[12:34] =Scene=", parsers.ErrNotPost},
|
||||
{"[12:34] <=Scene=>", parsers.ErrNotPost},
|
||||
{"[12:34] <Stuff> Things said.", ""},
|
||||
{"[12:34] >Stuff> Things said.", "line is neither action nor text post"},
|
||||
{"12:34] <Stuff> Things said.", "no timestamp"},
|
||||
{"* Stuff Things said.", "no timestamp"},
|
||||
{"", "no timestamp"},
|
||||
{"[12:34 <Stuff> Things said.", "incomplete timestamp"},
|
||||
{"[TE:XT] <Stuff> Things said.", "invalid number in timestamp"},
|
||||
{"[10] <Stuff> Things said.", "invalid timestamp"},
|
||||
{"[12:34:56:789] <Stuff> Things said.", ""},
|
||||
{"[12:34:56.789] <Stuff> Things said.", "invalid number in timestamp"},
|
||||
{"[12:34] <Stuff>", "post is empty"},
|
||||
{"[12:34] * Stuff", "post is empty"},
|
||||
{"[12:34] =Scene=", "line is neither action nor text post"},
|
||||
{"[12:34] <=Scene=>", "post is empty"},
|
||||
}
|
||||
|
||||
for i, row := range table {
|
||||
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||
_, err := parsers.MircPost(row.Input, time.Now(), models.Post{})
|
||||
|
||||
assert.Equal(t, row.Err, err, "Error should match")
|
||||
errProblem := ""
|
||||
if err != nil {
|
||||
if e2, ok := err.(*parsers.ParseError); ok {
|
||||
errProblem = e2.Problem
|
||||
} else {
|
||||
errProblem = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, row.Err, errProblem, "Error should match")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -160,7 +169,7 @@ func TestMircLogErrors(t *testing.T) {
|
||||
_, err2 := parsers.MircLog("\n\n\n\n\n[14:57]* Stuff \n\t\r\n", time.Time{}, true)
|
||||
|
||||
assert.Equal(t, parsers.ErrEmptyLog, err1)
|
||||
assert.Equal(t, parsers.ErrNotPost, err2)
|
||||
assert.True(t, parsers.IsParseError(err2))
|
||||
}
|
||||
|
||||
func parseDate(t *testing.T, date string) time.Time {
|
||||
|
||||
Reference in New Issue
Block a user