From d269405eabf30250283c271c4ff6095a2c5ded2c Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Wed, 14 Jan 2026 22:54:19 -0500 Subject: [PATCH] core: Show JSON error offsets where possible (#7437) --- admin.go | 5 ++++- caddyconfig/configadapters.go | 6 +++++- cmd/main.go | 5 ++++- modules.go | 6 +++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/admin.go b/admin.go index 6ccec41e7..0b8aedf9f 100644 --- a/admin.go +++ b/admin.go @@ -1110,7 +1110,10 @@ func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error if len(body) > 0 { err = json.Unmarshal(body, &val) if err != nil { - return fmt.Errorf("decoding request body: %v", err) + if jsonErr, ok := err.(*json.SyntaxError); ok { + return fmt.Errorf("decoding request body: %w, at offset %d", jsonErr, jsonErr.Offset) + } + return fmt.Errorf("decoding request body: %w", err) } } diff --git a/caddyconfig/configadapters.go b/caddyconfig/configadapters.go index 0ca3c3af1..8a5a37f08 100644 --- a/caddyconfig/configadapters.go +++ b/caddyconfig/configadapters.go @@ -81,7 +81,11 @@ func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) err = json.Unmarshal(enc, &tmp) if err != nil { if warnings != nil { - *warnings = append(*warnings, Warning{Message: err.Error()}) + message := err.Error() + if jsonErr, ok := err.(*json.SyntaxError); ok { + message = fmt.Sprintf("%v, at offset %d", jsonErr.Error(), jsonErr.Offset) + } + *warnings = append(*warnings, Warning{Message: message}) } return nil } diff --git a/cmd/main.go b/cmd/main.go index 411f4545d..4a969573e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -231,7 +231,10 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([ // validate that the config is at least valid JSON err = json.Unmarshal(config, new(any)) if err != nil { - return nil, "", "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err) + if jsonErr, ok := err.(*json.SyntaxError); ok { + return nil, "", "", fmt.Errorf("config is not valid JSON: %w, at offset %d; did you mean to use a config adapter (the --adapter flag)?", err, jsonErr.Offset) + } + return nil, "", "", fmt.Errorf("config is not valid JSON: %w; did you mean to use a config adapter (the --adapter flag)?", err) } } diff --git a/modules.go b/modules.go index 24c452589..93a9343f6 100644 --- a/modules.go +++ b/modules.go @@ -342,7 +342,11 @@ func ParseStructTag(tag string) (map[string]string, error) { func StrictUnmarshalJSON(data []byte, v any) error { dec := json.NewDecoder(bytes.NewReader(data)) dec.DisallowUnknownFields() - return dec.Decode(v) + err := dec.Decode(v) + if jsonErr, ok := err.(*json.SyntaxError); ok { + return fmt.Errorf("%w, at offset %d", jsonErr, jsonErr.Offset) + } + return err } var JSONRawMessageType = reflect.TypeFor[json.RawMessage]()