console/frontend: better error formatting

This commit is contained in:
Vincent Bernat
2022-05-16 21:41:33 +02:00
parent 7f4a34c17e
commit dd64d02b48
8 changed files with 88 additions and 15 deletions

13
common/helpers/strings.go Normal file
View File

@@ -0,0 +1,13 @@
package helpers
import "unicode"
// Capitalize turns the first letter of a string to its upper case version.
func Capitalize(str string) string {
if len(str) == 0 {
return ""
}
r := []rune(str)
r[0] = unicode.ToUpper(r[0])
return string(r)
}

View File

@@ -0,0 +1,22 @@
package helpers
import "testing"
func TestCapitalize(t *testing.T) {
cases := []struct {
In string
Out string
}{
{"", ""},
{"Hello", "Hello"},
{"bye", "Bye"},
{" nothing", " nothing"},
{"école", "École"},
}
for _, tc := range cases {
got := Capitalize(tc.In)
if diff := Diff(got, tc.Out); diff != "" {
t.Errorf("Capitalize(%q) (-got, +want):\n%s", tc.In, diff)
}
}
}

20
console/filter/error.go Normal file
View File

@@ -0,0 +1,20 @@
package filter
import "fmt"
// HumanError returns a more human-readable error for errList. It only outputs the first one.
func HumanError(err error) string {
el, ok := err.(errList)
if !ok {
return err.Error()
}
if len(el) == 0 {
return ""
}
switch e := el[0].(type) {
case *parserError:
return fmt.Sprintf("at line %d, position %d: %s", e.pos.line, e.pos.col, e.Inner.Error())
default:
return e.Error()
}
}

View File

@@ -0,0 +1,18 @@
package filter
import (
"testing"
"akvorado/common/helpers"
)
func TestFilterError(t *testing.T) {
_, err := Parse("", []byte(`
InIfDescription = "Gi0/0/0/0"
AND Proto = 1000
OR `))
expected := "at line 3, position 13: expecting an unsigned 8-bit integer"
if diff := helpers.Diff(HumanError(err), expected); diff != "" {
t.Errorf("HumanError() (-got, +want):\n%s", diff)
}
}

View File

@@ -98,10 +98,10 @@ func TestInvalidFilter(t *testing.T) {
{`Proto = 1000`},
{`SrcPort = 1000000`},
{`ForwardingStatus >= 900`},
{`Proto = 1000 AND`},
{`AND Proto = 1000`},
{`Proto = 1000AND Proto = 1000`},
{`Proto = 1000 ANDProto = 1000`},
{`Proto = 100 AND`},
{`AND Proto = 100`},
{`Proto = 100AND Proto = 100`},
{`Proto = 100 ANDProto = 100`},
}
for _, tc := range cases {
_, err := Parse("", []byte(tc.Input))

View File

@@ -1,7 +1,7 @@
<template>
<div class="my-4 flex rounded-lg p-4 text-sm" :class="classes" role="alert">
<InformationCircleIcon class="mr-2 h-5 w-5" />
<slot></slot>
<InformationCircleIcon class="mr-2 h-5 w-5 shrink-0" />
<span><slot></slot></span>
</div>
</template>

View File

@@ -89,7 +89,7 @@ func (gc graphColumn) MarshalText() ([]byte, error) {
if ok {
return []byte(got), nil
}
return nil, errors.New("unknown group operator")
return nil, errors.New("unknown field")
}
func (gc graphColumn) String() string {
got, _ := graphColumnMap.LoadValue(gc)
@@ -101,7 +101,7 @@ func (gc *graphColumn) UnmarshalText(input []byte) error {
*gc = got
return nil
}
return errors.New("unknown group operator")
return errors.New("unknown field")
}
type graphFilter struct {
@@ -114,7 +114,7 @@ func (gf graphFilter) MarshalText() ([]byte, error) {
func (gf *graphFilter) UnmarshalText(input []byte) error {
got, err := filter.Parse("", input)
if err != nil {
return fmt.Errorf("cannot parse filter: %w", err)
return fmt.Errorf("cannot parse filter: %s", filter.HumanError(err))
}
*gf = graphFilter{got.(string)}
return nil
@@ -212,28 +212,28 @@ func (c *Component) graphHandlerFunc(gc *gin.Context) {
ctx := c.t.Context(gc.Request.Context())
var query graphQuery
if err := gc.ShouldBindJSON(&query); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
if query.Start.After(query.End) {
gc.JSON(http.StatusBadRequest, gin.H{"message": "start should not be after end"})
gc.JSON(http.StatusBadRequest, gin.H{"message": "Start should not be after end"})
return
}
if query.Points < 5 || query.Points > 2000 {
gc.JSON(http.StatusBadRequest, gin.H{"message": "points should be >= 5 and <= 2000"})
gc.JSON(http.StatusBadRequest, gin.H{"message": "Points should be >= 5 and <= 2000"})
return
}
if query.Limit == 0 {
query.Limit = 10
}
if query.Limit < 5 || query.Limit > 50 {
gc.JSON(http.StatusBadRequest, gin.H{"message": "limit should be >= 5 and <= 50"})
gc.JSON(http.StatusBadRequest, gin.H{"message": "Limit should be >= 5 and <= 50"})
return
}
sqlQuery, err := query.toSQL()
if err != nil {
gc.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
gc.JSON(http.StatusBadRequest, gin.H{"message": helpers.Capitalize(err.Error())})
return
}
resolution := time.Duration(int64(query.End.Sub(query.Start).Nanoseconds()) / int64(query.Points))

View File

@@ -23,7 +23,7 @@ func (c *Component) widgetFlowLastHandlerFunc(gc *gin.Context) {
}
if !rows.Next() {
gc.JSON(http.StatusNotFound, gin.H{"message": "no flow currently in database."})
gc.JSON(http.StatusNotFound, gin.H{"message": "No flow currently in database."})
return
}
defer rows.Close()