Show lines where parsing error happened if available

feature/filters
Svilen Markov 2025-07-25 19:27:39 +07:00
parent eb6329aef3
commit 9720ebe9b1
2 changed files with 56 additions and 9 deletions

@ -48,6 +48,7 @@ func (c1 *hslColorField) SameAs(c2 *hslColorField) bool {
func (c *hslColorField) UnmarshalYAML(node *yaml.Node) error {
var value string
errorLine := fmt.Sprintf("line %d:", node.Line)
if err := node.Decode(&value); err != nil {
return err
@ -56,7 +57,7 @@ func (c *hslColorField) UnmarshalYAML(node *yaml.Node) error {
matches := hslColorFieldPattern.FindStringSubmatch(value)
if len(matches) != 4 {
return fmt.Errorf("invalid HSL color format: %s", value)
return fmt.Errorf("%s invalid HSL color format: %s", errorLine, value)
}
hue, err := strconv.ParseFloat(matches[1], 64)
@ -65,7 +66,7 @@ func (c *hslColorField) UnmarshalYAML(node *yaml.Node) error {
}
if hue > hslHueMax {
return fmt.Errorf("HSL hue must be between 0 and %d", hslHueMax)
return fmt.Errorf("%s HSL hue must be between 0 and %d", errorLine, hslHueMax)
}
saturation, err := strconv.ParseFloat(matches[2], 64)
@ -74,7 +75,7 @@ func (c *hslColorField) UnmarshalYAML(node *yaml.Node) error {
}
if saturation > hslSaturationMax {
return fmt.Errorf("HSL saturation must be between 0 and %d", hslSaturationMax)
return fmt.Errorf("%s HSL saturation must be between 0 and %d", errorLine, hslSaturationMax)
}
lightness, err := strconv.ParseFloat(matches[3], 64)
@ -83,7 +84,7 @@ func (c *hslColorField) UnmarshalYAML(node *yaml.Node) error {
}
if lightness > hslLightnessMax {
return fmt.Errorf("HSL lightness must be between 0 and %d", hslLightnessMax)
return fmt.Errorf("%s HSL lightness must be between 0 and %d", errorLine, hslLightnessMax)
}
c.H = hue
@ -99,6 +100,7 @@ type durationField time.Duration
func (d *durationField) UnmarshalYAML(node *yaml.Node) error {
var value string
errorLine := fmt.Sprintf("line %d:", node.Line)
if err := node.Decode(&value); err != nil {
return err
@ -107,12 +109,12 @@ func (d *durationField) UnmarshalYAML(node *yaml.Node) error {
matches := durationFieldPattern.FindStringSubmatch(value)
if len(matches) != 3 {
return fmt.Errorf("invalid duration format: %s", value)
return fmt.Errorf("%s invalid duration format for value `%s`", errorLine, value)
}
duration, err := strconv.Atoi(matches[1])
if err != nil {
return err
return fmt.Errorf("%s invalid duration value: %s", errorLine, matches[1])
}
switch matches[2] {
@ -245,6 +247,8 @@ func (q *queryParametersField) UnmarshalYAML(node *yaml.Node) error {
*q = make(queryParametersField)
errorLine := fmt.Sprintf("line %d:", node.Line)
// TODO: refactor the duplication in the switch cases if any more types get added
for key, value := range decoded {
switch v := value.(type) {
@ -266,11 +270,11 @@ func (q *queryParametersField) UnmarshalYAML(node *yaml.Node) error {
case bool:
(*q)[key] = append((*q)[key], fmt.Sprintf("%t", item))
default:
return fmt.Errorf("invalid query parameter value type: %T", item)
return fmt.Errorf("%s invalid query parameter value type: %T", errorLine, item)
}
}
default:
return fmt.Errorf("invalid query parameter value type: %T", value)
return fmt.Errorf("%s invalid query parameter value type: %T", errorLine, value)
}
}

@ -6,6 +6,9 @@ import (
"log"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"golang.org/x/crypto/bcrypt"
)
@ -105,7 +108,12 @@ func serveApp(configPath string) error {
config, err := newConfigFromYAML(newContents)
if err != nil {
log.Printf("Config has errors: %v", err)
errStr := strings.ReplaceAll(err.Error(), "\n", "")
errStr = sequentialWhitespacePattern.ReplaceAllString(errStr, " ")
errStr = strings.ReplaceAll(errStr, "!!seq", "array")
log.Printf("Config has errors: %v", errStr)
printConfigLinesNearErrorIfAvailable(err, newContents)
if !hadValidConfigOnStartup {
close(exitChannel)
@ -180,6 +188,41 @@ func serveApp(configPath string) error {
return nil
}
var errorLinePattern = regexp.MustCompile(`line (\d+)`)
func printConfigLinesNearErrorIfAvailable(err error, configContents []byte) {
if err == nil {
return
}
matches := errorLinePattern.FindStringSubmatch(err.Error())
if len(matches) < 2 {
return
}
errorAtLine, err := strconv.Atoi(matches[1])
if err != nil {
return
}
contents := strings.ReplaceAll(string(configContents), "\r\n", "\n")
lines := strings.Split(contents, "\n")
if errorAtLine < 1 || errorAtLine > len(lines) {
return
}
contextLength := 3
for i := max(0, errorAtLine-contextLength-1); i < min(len(lines), errorAtLine+contextLength); i++ {
if i == errorAtLine-1 {
fmt.Printf("-> %s\n", lines[i])
} else {
fmt.Printf("| %s\n", lines[i])
}
}
}
func serveUpdateNoticeIfConfigLocationNotMigrated(configPath string) bool {
if !isRunningInsideDockerContainer() {
return false