pkg/config: fix line numbers shown incorrectly for TOML type mismatch errors (#5195)

This commit is contained in:
fatedier
2026-03-04 20:53:22 +08:00
committed by GitHub
parent 774478d071
commit b7435967b0
2 changed files with 20 additions and 10 deletions

View File

@@ -180,24 +180,26 @@ func LoadConfigure(b []byte, c any, strict bool, formats ...string) error {
} }
originalBytes := b originalBytes := b
parsedFromTOML := false
var tomlObj any var tomlObj any
tomlErr := toml.Unmarshal(b, &tomlObj) tomlErr := toml.Unmarshal(b, &tomlObj)
if tomlErr == nil { if tomlErr == nil {
parsedFromTOML = true
var err error var err error
b, err = jsonx.Marshal(&tomlObj) b, err = jsonx.Marshal(&tomlObj)
if err != nil { if err != nil {
return err return err
} }
} else if format == "toml" { } else if format == "toml" {
// File is known to be TOML but has syntax errors — report with line/column info. // File is known to be TOML but has syntax errors.
return formatTOMLError(tomlErr) return formatTOMLError(tomlErr)
} }
// If the buffer smells like JSON (first non-whitespace character is '{'), unmarshal as JSON directly. // If the buffer smells like JSON (first non-whitespace character is '{'), unmarshal as JSON directly.
if yaml.IsJSONBuffer(b) { if yaml.IsJSONBuffer(b) {
if err := decodeJSONContent(b, c, strict); err != nil { if err := decodeJSONContent(b, c, strict); err != nil {
return enhanceDecodeError(err, originalBytes) return enhanceDecodeError(err, originalBytes, !parsedFromTOML)
} }
return nil return nil
} }
@@ -206,7 +208,7 @@ func LoadConfigure(b []byte, c any, strict bool, formats ...string) error {
if strict { if strict {
// In strict mode, always use our custom handler to support YAML merge // In strict mode, always use our custom handler to support YAML merge
if err := parseYAMLWithDotFieldsHandling(b, c); err != nil { if err := parseYAMLWithDotFieldsHandling(b, c); err != nil {
return enhanceDecodeError(err, originalBytes) return enhanceDecodeError(err, originalBytes, !parsedFromTOML)
} }
return nil return nil
} }
@@ -223,18 +225,20 @@ func formatTOMLError(err error) error {
} }
var strictErr *toml.StrictMissingError var strictErr *toml.StrictMissingError
if errors.As(err, &strictErr) { if errors.As(err, &strictErr) {
return fmt.Errorf("toml: %s", strictErr.Error()) return strictErr
} }
return err return err
} }
// enhanceDecodeError tries to add field path and line number information to JSON/YAML decode errors. // enhanceDecodeError tries to add field path and line number information to JSON/YAML decode errors.
func enhanceDecodeError(err error, originalContent []byte) error { func enhanceDecodeError(err error, originalContent []byte, includeLine bool) error {
var typeErr *json.UnmarshalTypeError var typeErr *json.UnmarshalTypeError
if errors.As(err, &typeErr) && typeErr.Field != "" { if errors.As(err, &typeErr) && typeErr.Field != "" {
line := findFieldLineInContent(originalContent, typeErr.Field) if includeLine {
if line > 0 { line := findFieldLineInContent(originalContent, typeErr.Field)
return fmt.Errorf("line %d: field \"%s\": cannot unmarshal %s into %s", line, typeErr.Field, typeErr.Value, typeErr.Type) if line > 0 {
return fmt.Errorf("line %d: field \"%s\": cannot unmarshal %s into %s", line, typeErr.Field, typeErr.Value, typeErr.Type)
}
} }
return fmt.Errorf("field \"%s\": cannot unmarshal %s into %s", typeErr.Field, typeErr.Value, typeErr.Type) return fmt.Errorf("field \"%s\": cannot unmarshal %s into %s", typeErr.Field, typeErr.Value, typeErr.Type)
} }
@@ -244,6 +248,10 @@ func enhanceDecodeError(err error, originalContent []byte) error {
// findFieldLineInContent searches the original config content for a field name // findFieldLineInContent searches the original config content for a field name
// and returns the 1-indexed line number where it appears, or 0 if not found. // and returns the 1-indexed line number where it appears, or 0 if not found.
func findFieldLineInContent(content []byte, fieldPath string) int { func findFieldLineInContent(content []byte, fieldPath string) int {
if fieldPath == "" {
return 0
}
// Use the last component of the field path (e.g. "proxies" from "proxies" or // Use the last component of the field path (e.g. "proxies" from "proxies" or
// "protocol" from "transport.protocol"). // "protocol" from "transport.protocol").
parts := strings.Split(fieldPath, ".") parts := strings.Split(fieldPath, ".")

View File

@@ -496,7 +496,7 @@ serverPort: 7000
require.Equal(7000, clientCfg.ServerPort) require.Equal(7000, clientCfg.ServerPort)
} }
func TestTOMLSyntaxErrorWithLineNumber(t *testing.T) { func TestTOMLSyntaxErrorWithPosition(t *testing.T) {
require := require.New(t) require := require.New(t)
// TOML with syntax error (unclosed table array header) // TOML with syntax error (unclosed table array header)
@@ -510,8 +510,9 @@ name = "test"
clientCfg := v1.ClientConfig{} clientCfg := v1.ClientConfig{}
err := LoadConfigure([]byte(content), &clientCfg, false, "toml") err := LoadConfigure([]byte(content), &clientCfg, false, "toml")
require.Error(err) require.Error(err)
require.Contains(err.Error(), "line")
require.Contains(err.Error(), "toml") require.Contains(err.Error(), "toml")
require.Contains(err.Error(), "line")
require.Contains(err.Error(), "column")
} }
func TestTOMLTypeMismatchErrorWithFieldInfo(t *testing.T) { func TestTOMLTypeMismatchErrorWithFieldInfo(t *testing.T) {
@@ -529,6 +530,7 @@ proxies = "this should be a table array"
// The error should contain field info // The error should contain field info
errMsg := err.Error() errMsg := err.Error()
require.Contains(errMsg, "proxies") require.Contains(errMsg, "proxies")
require.NotContains(errMsg, "line")
} }
func TestFindFieldLineInContent(t *testing.T) { func TestFindFieldLineInContent(t *testing.T) {