diff --git a/services/parsers/mirclike.go b/services/parsers/mirclike.go index a3f59e1..f96a293 100644 --- a/services/parsers/mirclike.go +++ b/services/parsers/mirclike.go @@ -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", + } } } diff --git a/services/parsers/mirclike_test.go b/services/parsers/mirclike_test.go index 247a026..26608a0 100644 --- a/services/parsers/mirclike_test.go +++ b/services/parsers/mirclike_test.go @@ -58,29 +58,38 @@ func TestMircPost(t *testing.T) { func TestMircPostErrors(t *testing.T) { table := []struct { Input string - Err error + Err string }{ - {"[12:34] Things said.", nil}, - {"[12:34] >Stuff> Things said.", parsers.ErrNotPost}, - {"12:34] Things said.", parsers.ErrNotPost}, - {"* Stuff Things said.", parsers.ErrNotPost}, - {"", parsers.ErrNotPost}, - {"[12:34 Things said.", parsers.ErrNotPost}, - {"[TE:XT] Things said.", parsers.ErrNotPost}, - {"[10] Things said.", parsers.ErrNotPost}, - {"[12:34:56:789] Things said.", nil}, - {"[12:34:56.789] Things said.", parsers.ErrNotPost}, - {"[12:34] ", parsers.ErrNotPost}, - {"[12:34] * Stuff", parsers.ErrNotPost}, - {"[12:34] =Scene=", parsers.ErrNotPost}, - {"[12:34] <=Scene=>", parsers.ErrNotPost}, + {"[12:34] Things said.", ""}, + {"[12:34] >Stuff> Things said.", "line is neither action nor text post"}, + {"12:34] Things said.", "no timestamp"}, + {"* Stuff Things said.", "no timestamp"}, + {"", "no timestamp"}, + {"[12:34 Things said.", "incomplete timestamp"}, + {"[TE:XT] Things said.", "invalid number in timestamp"}, + {"[10] Things said.", "invalid timestamp"}, + {"[12:34:56:789] Things said.", ""}, + {"[12:34:56.789] Things said.", "invalid number in timestamp"}, + {"[12:34] ", "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 {