Identify configuration source files in load errors
This commit is contained in:
@@ -223,6 +223,10 @@ go run ./cmd/notarius pipelines list \
|
|||||||
Successful commands write their primary result to standard output. Warnings and
|
Successful commands write their primary result to standard output. Warnings and
|
||||||
errors are written to standard error.
|
errors are written to standard error.
|
||||||
|
|
||||||
|
Configuration load errors include ConfigKit's field diagnostics and source
|
||||||
|
coordinates, followed by a source-ID legend identifying the acquired files or
|
||||||
|
source labels. This includes imported files and the selected configuration profile.
|
||||||
|
|
||||||
For **run --json**, actionable process warnings remain on standard error and standard output is a
|
For **run --json**, actionable process warnings remain on standard error and standard output is a
|
||||||
machine-readable success result only. Syntax and runtime diagnostics remain on
|
machine-readable success result only. Syntax and runtime diagnostics remain on
|
||||||
standard error. Parse the result only after the process exits with status 0.
|
standard error. Parse the result only after the process exits with status 0.
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ normalization instead of reporting a synthetic default for the explicit value.
|
|||||||
|
|
||||||
## Finding 4 — Include Source Filenames In Load Diagnostics
|
## Finding 4 — Include Source Filenames In Load Diagnostics
|
||||||
|
|
||||||
|
Status: Addressed. CLI load errors include a quoted source legend; command tests
|
||||||
|
cover imported/profile failures, source coordinates, and terminal-control escaping.
|
||||||
|
|
||||||
Priority: P2. Import/profile failures lack actionable file attribution.
|
Priority: P2. Import/profile failures lack actionable file attribution.
|
||||||
|
|
||||||
`internal/cli/configkit_load.go:29` calls `WriteDiagnostics` with diagnostics
|
`internal/cli/configkit_load.go:29` calls `WriteDiagnostics` with diagnostics
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ func writeConfigLoadError(output io.Writer, err error) {
|
|||||||
var loadError *configkit.LoadError
|
var loadError *configkit.LoadError
|
||||||
if errors.As(err, &loadError) {
|
if errors.As(err, &loadError) {
|
||||||
if writeErr := configkit.WriteDiagnostics(output, loadError.Diagnostics()); writeErr == nil {
|
if writeErr := configkit.WriteDiagnostics(output, loadError.Diagnostics()); writeErr == nil {
|
||||||
|
// ConfigKit's locations contain source IDs. Keep its renderer and
|
||||||
|
// supply the acquired-source legend so imported/profile errors can
|
||||||
|
// be traced to a file. Quoting also escapes terminal controls.
|
||||||
|
for _, source := range loadError.Sources() {
|
||||||
|
label := source.Path
|
||||||
|
if label == "" {
|
||||||
|
label = source.Label
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(output, "source %q: %q\n", source.ID, label); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,45 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestConfigLoadDiagnosticsIdentifyComposedSources(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
for _, profile := range []bool{false, true} {
|
||||||
|
t.Run(strconv.FormatBool(profile), func(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
main := filepath.Join(root, "config.yml")
|
||||||
|
fragment := filepath.Join(root, "pipeline\n\x1b.yml")
|
||||||
|
args := []string{"config", "validate", "--config", main}
|
||||||
|
if profile {
|
||||||
|
fragment = filepath.Join(root, "profiles", "development.yml")
|
||||||
|
writeConfigKitFile(t, main, "version: 5\n")
|
||||||
|
args = append(args, "--config-profile", "development")
|
||||||
|
} else {
|
||||||
|
writeConfigKitFile(t, main, "version: 5\nconfigkit:\n imports:\n - path: "+strconv.Quote(filepath.Base(fragment))+"\n")
|
||||||
|
}
|
||||||
|
writeConfigKitFile(t, fragment, "concurrency: {total_llm: invalid-private-value}\n")
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
if code := RunWithOptions(args, &stdout, &stderr, commandContractOptions(t)); code != 1 {
|
||||||
|
t.Fatalf("status = %d; %s", code, stderr.String())
|
||||||
|
}
|
||||||
|
got := stderr.String()
|
||||||
|
if stdout.Len() != 0 || !strings.Contains(got, "decode.type") ||
|
||||||
|
!strings.Contains(got, "source-2:1:") ||
|
||||||
|
!strings.Contains(got, strconv.Quote(fragment)) ||
|
||||||
|
strings.Contains(got, "invalid-private-value") ||
|
||||||
|
strings.ContainsRune(got, '\x1b') {
|
||||||
|
t.Fatalf("stdout = %q; stderr = %q", stdout.String(), got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadConfigKitUsesDiscoveryProfileAndOverrides(t *testing.T) {
|
func TestLoadConfigKitUsesDiscoveryProfileAndOverrides(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
|
|||||||
Reference in New Issue
Block a user