Add focused configuration reports
This commit is contained in:
12
docs/cli.md
12
docs/cli.md
@@ -358,6 +358,7 @@ YAML for structured values; `--format` selects text, YAML, or JSON.
|
|||||||
example config show
|
example config show
|
||||||
example config show --scope promptkit
|
example config show --scope promptkit
|
||||||
example config show --scope all
|
example config show --scope all
|
||||||
|
example config show --path /server/addr --format json
|
||||||
```
|
```
|
||||||
|
|
||||||
`--scope all` acquires every selected scope before producing one mapping keyed
|
`--scope all` acquires every selected scope before producing one mapping keyed
|
||||||
@@ -365,12 +366,21 @@ by scope name, including when only one scope is registered. A single selected
|
|||||||
scope is unwrapped. A provider failure prevents report output. Conversion or
|
scope is unwrapped. A provider failure prevents report output. Conversion or
|
||||||
output failures are recorded in `Outcome.Presentation.Output`.
|
output failures are recorded in `Outcome.Presentation.Output`.
|
||||||
|
|
||||||
|
For a scope that supports path-selected show, `--path POINTER` displays only
|
||||||
|
that safe effective value. An explicitly empty pointer selects the root, and a
|
||||||
|
present null remains a value. Missing and historical-only paths fail; path
|
||||||
|
selection cannot be combined with `--scope all`.
|
||||||
|
|
||||||
### List configuration sources
|
### List configuration sources
|
||||||
|
|
||||||
`config sources` reports the source inventory captured during the selected
|
`config sources` reports the source inventory captured during the selected
|
||||||
scope's acquisition. It reads the source facet once and does not reload
|
scope's acquisition. It reads the source facet once and does not reload
|
||||||
configuration. The default scope is `app`; `--scope all` is unsupported. Text is
|
configuration. The default scope is `app`; `--scope all` is unsupported. Text is
|
||||||
the automatic format, and `--format yaml` or `--format json` returns a structured report:
|
the automatic format, and `--format yaml` or `--format json` returns a structured report.
|
||||||
|
`--source-id ID` selects one entry by the ID from that acquisition; an unknown
|
||||||
|
ID fails. If acquisition fails but returns a captured source inventory, the
|
||||||
|
command reports that evidence and preserves the acquisition error. Unavailable
|
||||||
|
inventory is reported as such.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
scope: app
|
scope: app
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Shared reporting and PromptKit v0.20.0 implementation plan
|
# Shared reporting and PromptKit v0.20.0 implementation plan
|
||||||
|
|
||||||
Status: implementation in progress; Stages 1–17 complete.
|
Status: implementation in progress; Stages 1–18 complete.
|
||||||
|
|
||||||
## Scope, authority, and execution
|
## Scope, authority, and execution
|
||||||
|
|
||||||
@@ -648,6 +648,11 @@ capabilities, historical-only evidence, and result delivery.
|
|||||||
|
|
||||||
## Stage 18: Focused effective values and source evidence
|
## Stage 18: Focused effective values and source evidence
|
||||||
|
|
||||||
|
**Complete.** Focused show and source selection use native safe evidence,
|
||||||
|
including source inventories returned with acquisition failures. Root,
|
||||||
|
ckconfig, and ckprompt workspace suites passed with Go 1.27.0, including null,
|
||||||
|
historical-only paths, missing evidence, and delivery failures.
|
||||||
|
|
||||||
Add `config show --path POINTER` for one scope with PathShow support. Parse the
|
Add `config show --path POINTER` for one scope with PathShow support. Parse the
|
||||||
pointer before loading, call the acquired native `Explain`, and select its safe
|
pointer before loading, call the acquired native `Explain`, and select its safe
|
||||||
effective snapshot. An absent effective path is operational failure; explicitly
|
effective snapshot. An absent effective path is operational failure; explicitly
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestComparisonHelpLimitsFlagsAndDefaultsFormatToYAML(t *testing.T) {
|
|||||||
if strings.Contains(operationHelp, "--against-config") || strings.Contains(operationHelp, "--against-overlay") {
|
if strings.Contains(operationHelp, "--against-config") || strings.Contains(operationHelp, "--against-overlay") {
|
||||||
t.Errorf("%s exposes comparison-only flags:\n%s", operation, operationHelp)
|
t.Errorf("%s exposes comparison-only flags:\n%s", operation, operationHelp)
|
||||||
}
|
}
|
||||||
if operation != "explain" && strings.Contains(operationHelp, "--path") {
|
if operation != "explain" && operation != "show" && strings.Contains(operationHelp, "--path") {
|
||||||
t.Errorf("%s exposes the explain-only path flag:\n%s", operation, operationHelp)
|
t.Errorf("%s exposes the explain-only path flag:\n%s", operation, operationHelp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
126
integrations/ckconfig/focused_reports_test.go
Normal file
126
integrations/ckconfig/focused_reports_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package ckconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
commandkit "go.maximumdirect.net/commandkit"
|
||||||
|
configkit "go.maximumdirect.net/configkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFocusedShowUsesSafeNativeValue(t *testing.T) {
|
||||||
|
loads := 0
|
||||||
|
scope := FromConfigKit("app", "", func(_ context.Context, selection Selection, _, _ struct{}) (*configkit.Result[map[string]any], error) {
|
||||||
|
loads++
|
||||||
|
return configkit.Load(configkit.Mapping("fixture", map[string]any{"object": map[string]any{"x": "y"}, "text": "hello", "false": false, "zero": 0, "null": nil, "gone": map[string]any{"x": 1}}), configkit.Options[map[string]any]{Overrides: selection.Overrides})
|
||||||
|
})
|
||||||
|
scope.Features.Overrides = true
|
||||||
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{scope}}))
|
||||||
|
for _, test := range []struct {
|
||||||
|
path string
|
||||||
|
want any
|
||||||
|
}{{"/object", map[string]any{"x": "y"}}, {"/text", "hello"}, {"/false", false}, {"/zero", float64(0)}, {"/null", nil}} {
|
||||||
|
var out bytes.Buffer
|
||||||
|
result := app.Execute(context.Background(), []string{"config", "show", "--path=" + test.path, "--format=json"}, commandkit.Streams{Out: &out, Err: io.Discard})
|
||||||
|
var got any
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || json.Unmarshal(out.Bytes(), &got) != nil || !reflect.DeepEqual(got, test.want) {
|
||||||
|
t.Fatalf("%s: %#v %q", test.path, result, out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
result := app.Execute(context.Background(), []string{"config", "show", "--path=/text"}, commandkit.Streams{Out: &out, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || out.String() != "hello\n" {
|
||||||
|
t.Fatalf("automatic text: %#v %q", result, out.String())
|
||||||
|
}
|
||||||
|
out.Reset()
|
||||||
|
result = app.Execute(context.Background(), []string{"config", "show", "--path=/false", "--format=yaml"}, commandkit.Streams{Out: &out, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || strings.TrimSpace(out.String()) != "false" {
|
||||||
|
t.Fatalf("yaml false: %#v %q", result, out.String())
|
||||||
|
}
|
||||||
|
for _, args := range [][]string{{"--path=/missing"}, {"--path=/gone/x", "--set=/gone=null"}} {
|
||||||
|
result = app.Execute(context.Background(), append([]string{"config", "show"}, args...), commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeOperational {
|
||||||
|
t.Fatalf("missing %v: %#v", args, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
before := loads
|
||||||
|
for _, args := range [][]string{{"--path=bad"}, {"--scope=all", "--path=/text"}} {
|
||||||
|
result = app.Execute(context.Background(), append([]string{"config", "show"}, args...), commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeUsage || loads != before {
|
||||||
|
t.Fatalf("preflight %v: %#v", args, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFocusedShowChecksCapabilityAndFacet(t *testing.T) {
|
||||||
|
calls := 0
|
||||||
|
scope := Scope[struct{}, struct{}]{Name: "app", Operations: []Operation{Show}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||||
|
calls++
|
||||||
|
return commandkit.Acquired[View]{}, nil
|
||||||
|
}}
|
||||||
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{scope}}))
|
||||||
|
result := app.Execute(context.Background(), []string{"config", "show", "--path=/x"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeUsage || calls != 0 {
|
||||||
|
t.Fatalf("unsupported: %#v", result)
|
||||||
|
}
|
||||||
|
scope.Features.PathShow = true
|
||||||
|
app = newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{scope}}))
|
||||||
|
result = app.Execute(context.Background(), []string{"config", "show", "--path=/x"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if result.Kind != commandkit.OutcomeOperational || calls != 1 {
|
||||||
|
t.Fatalf("missing facet: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourcesFilterAndFailureEvidence(t *testing.T) {
|
||||||
|
cause := errors.New("load failed")
|
||||||
|
reader := &sourceInventory{values: []configkit.SourceInfo{{ID: "one", Label: "first"}, {ID: "two", Label: "second"}}}
|
||||||
|
var returnedErr error
|
||||||
|
var source SourceReader = reader
|
||||||
|
scope := Scope[struct{}, struct{}]{Name: "app", Operations: []Operation{Sources}, Acquire: func(context.Context, Request, struct{}, struct{}) (commandkit.Acquired[View], error) {
|
||||||
|
return commandkit.Acquired[View]{Value: View{Sources: source}}, returnedErr
|
||||||
|
}}
|
||||||
|
app := newConfigTestApp(t, Command(Suite[struct{}, struct{}]{Scopes: []Scope[struct{}, struct{}]{scope}}))
|
||||||
|
run := func(args ...string) (commandkit.Outcome, string, string) {
|
||||||
|
var out, diag bytes.Buffer
|
||||||
|
result := app.Execute(context.Background(), append([]string{"config", "sources"}, args...), commandkit.Streams{Out: &out, Err: &diag})
|
||||||
|
return result, out.String(), diag.String()
|
||||||
|
}
|
||||||
|
result, out, _ := run("--source-id=two", "--format=json")
|
||||||
|
if result.Kind != commandkit.OutcomeSuccess || !strings.Contains(out, `"id": "two"`) || strings.Contains(out, `"id": "one"`) {
|
||||||
|
t.Fatalf("selected source: %#v %q", result, out)
|
||||||
|
}
|
||||||
|
result, _, _ = run("--source-id=missing")
|
||||||
|
if result.Kind != commandkit.OutcomeOperational {
|
||||||
|
t.Fatalf("unknown source: %#v", result)
|
||||||
|
}
|
||||||
|
returnedErr = cause
|
||||||
|
result, out, _ = run("--source-id=one", "--format=json")
|
||||||
|
if !errors.Is(result.Cause, cause) || !strings.Contains(out, `"id": "one"`) || strings.Contains(out, `"id": "two"`) {
|
||||||
|
t.Fatalf("failed selection: %#v %q", result, out)
|
||||||
|
}
|
||||||
|
result, _, diag := run("--source-id=missing")
|
||||||
|
if !errors.Is(result.Cause, cause) || !strings.Contains(diag, "not found") {
|
||||||
|
t.Fatalf("failed missing source: %#v %q", result, diag)
|
||||||
|
}
|
||||||
|
source = &sourceInventory{}
|
||||||
|
result, out, _ = run("--format=json")
|
||||||
|
if !errors.Is(result.Cause, cause) || !strings.Contains(out, `"sources": []`) {
|
||||||
|
t.Fatalf("empty evidence: %#v %q", result, out)
|
||||||
|
}
|
||||||
|
source = nil
|
||||||
|
result, out, diag = run()
|
||||||
|
if !errors.Is(result.Cause, cause) || out != "" || !strings.Contains(diag, "unavailable") {
|
||||||
|
t.Fatalf("unavailable evidence: %#v %q %q", result, out, diag)
|
||||||
|
}
|
||||||
|
source = reader
|
||||||
|
result = app.Execute(context.Background(), []string{"config", "sources"}, commandkit.Streams{Out: shortWriter{}, Err: io.Discard})
|
||||||
|
if !errors.Is(result.Cause, cause) || result.Presentation.Output.State != commandkit.DeliveryIncomplete {
|
||||||
|
t.Fatalf("delivery failure: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ type configCommandInput[I, S any] struct {
|
|||||||
historical commandkit.Supplied[bool]
|
historical commandkit.Supplied[bool]
|
||||||
under commandkit.Supplied[string]
|
under commandkit.Supplied[string]
|
||||||
explicit commandkit.Supplied[bool]
|
explicit commandkit.Supplied[bool]
|
||||||
|
sourceID commandkit.Supplied[string]
|
||||||
values commandkit.FlagValues[I]
|
values commandkit.FlagValues[I]
|
||||||
againstValues commandkit.FlagValues[I]
|
againstValues commandkit.FlagValues[I]
|
||||||
plan *configOperationPlan[I, S]
|
plan *configOperationPlan[I, S]
|
||||||
@@ -38,6 +39,8 @@ type configOperationPlan[I, S any] struct {
|
|||||||
operation Operation
|
operation Operation
|
||||||
scopeName string
|
scopeName string
|
||||||
path configkit.Path
|
path configkit.Path
|
||||||
|
pathSet bool
|
||||||
|
sourceID commandkit.Supplied[string]
|
||||||
list bool
|
list bool
|
||||||
help bool
|
help bool
|
||||||
historical bool
|
historical bool
|
||||||
@@ -106,6 +109,12 @@ func newConfigOperationCommand[I, S any](prepared *preparedSuite[I, S], operatio
|
|||||||
commandkit.BoolFlag(commandkit.FlagMeta{Name: "explicit", Usage: "Filter listed current paths by explicitness."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).explicit = value }),
|
commandkit.BoolFlag(commandkit.FlagMeta{Name: "explicit", Usage: "Filter listed current paths by explicitness."}, false, func(input **configCommandInput[I, S], value commandkit.Supplied[bool]) { (*input).explicit = value }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if operation == Show {
|
||||||
|
flags = append(flags, commandkit.ValueFlag(commandkit.FlagMeta{Name: "path", Usage: "JSON Pointer to one effective value."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).path = value }))
|
||||||
|
}
|
||||||
|
if operation == Sources {
|
||||||
|
flags = append(flags, commandkit.ValueFlag(commandkit.FlagMeta{Name: "source-id", Usage: "Select one source ID from this acquisition."}, (*string)(nil), parseConfigString, func(input **configCommandInput[I, S], value commandkit.Supplied[string]) { (*input).sourceID = value }))
|
||||||
|
}
|
||||||
flags = append(flags, commandkit.CaptureFlags(prepared.bindingsByOperation[operation], func(input **configCommandInput[I, S]) *commandkit.FlagValues[I] {
|
flags = append(flags, commandkit.CaptureFlags(prepared.bindingsByOperation[operation], func(input **configCommandInput[I, S]) *commandkit.FlagValues[I] {
|
||||||
if input == nil || *input == nil {
|
if input == nil || *input == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -187,6 +196,9 @@ func prepareConfigOperation[I, S any](prepared *preparedSuite[I, S], operation O
|
|||||||
if scopeName == "all" && operation != Validate && operation != Show {
|
if scopeName == "all" && operation != Validate && operation != Show {
|
||||||
return fmt.Errorf("scope all is supported only for validate and show")
|
return fmt.Errorf("scope all is supported only for validate and show")
|
||||||
}
|
}
|
||||||
|
if scopeName == "all" && operation == Show && input.path.Set {
|
||||||
|
return fmt.Errorf("path-selected show requires one scope")
|
||||||
|
}
|
||||||
|
|
||||||
var selected []*preparedScope[I, S]
|
var selected []*preparedScope[I, S]
|
||||||
if scopeName == "all" {
|
if scopeName == "all" {
|
||||||
@@ -207,10 +219,13 @@ func prepareConfigOperation[I, S any](prepared *preparedSuite[I, S], operation O
|
|||||||
if operation == Explain && input.list.Value && !scope.scope.Features.PathDiscovery {
|
if operation == Explain && input.list.Value && !scope.scope.Features.PathDiscovery {
|
||||||
return fmt.Errorf("configuration scope %q does not support path discovery", scope.scope.Name)
|
return fmt.Errorf("configuration scope %q does not support path discovery", scope.scope.Name)
|
||||||
}
|
}
|
||||||
|
if operation == Show && input.path.Set && !scope.scope.Features.PathShow {
|
||||||
|
return fmt.Errorf("configuration scope %q does not support path-selected show", scope.scope.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var path configkit.Path
|
var path configkit.Path
|
||||||
if operation == Explain && input.path.Set {
|
if (operation == Explain || operation == Show) && input.path.Set {
|
||||||
parsed, err := configkit.ParsePath(input.path.Value)
|
parsed, err := configkit.ParsePath(input.path.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid configuration path: %w", err)
|
return fmt.Errorf("invalid configuration path: %w", err)
|
||||||
@@ -231,6 +246,8 @@ func prepareConfigOperation[I, S any](prepared *preparedSuite[I, S], operation O
|
|||||||
operation: operation,
|
operation: operation,
|
||||||
scopeName: scopeName,
|
scopeName: scopeName,
|
||||||
path: path,
|
path: path,
|
||||||
|
pathSet: input.path.Set,
|
||||||
|
sourceID: input.sourceID,
|
||||||
list: input.list.Value,
|
list: input.list.Value,
|
||||||
historical: input.historical.Value,
|
historical: input.historical.Value,
|
||||||
under: under,
|
under: under,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
commandkit "go.maximumdirect.net/commandkit"
|
commandkit "go.maximumdirect.net/commandkit"
|
||||||
)
|
)
|
||||||
@@ -31,6 +32,31 @@ func runConfigShow[I, S any](invocation commandkit.Invocation, plan *configOpera
|
|||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
return configFailure("Configuration show was canceled.", acquired.Explanation, err)
|
return configFailure("Configuration show was canceled.", acquired.Explanation, err)
|
||||||
}
|
}
|
||||||
|
if plan.pathSet {
|
||||||
|
if acquired.Value.Explanation == nil {
|
||||||
|
return configFailure("Configuration scope did not provide explanation data.", acquired.Explanation, fmt.Errorf("configuration scope %q declared path show without an explanation reader", selected.scope.scope.Name))
|
||||||
|
}
|
||||||
|
explanation, found := acquired.Value.Explanation.Explain(plan.path)
|
||||||
|
if !found || !explanation.Effective.Present {
|
||||||
|
return configFailure("The requested effective configuration path is not available.", acquired.Explanation, fmt.Errorf("configuration path %q is not effective in scope %q", plan.path.String(), selected.scope.scope.Name))
|
||||||
|
}
|
||||||
|
if err := invocation.Context.Err(); err != nil {
|
||||||
|
return configFailure("Configuration show was canceled.", acquired.Explanation, err)
|
||||||
|
}
|
||||||
|
var value any
|
||||||
|
err := reporter.RenderOutput(func(io.Writer) error {
|
||||||
|
var err error
|
||||||
|
value, err = commandkit.DecodeYAMLReport(strings.NewReader(explanation.Effective.YAML))
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
if canceled := invocation.Context.Err(); canceled != nil {
|
||||||
|
return configFailure("Configuration show was canceled.", acquired.Explanation, canceled)
|
||||||
|
}
|
||||||
|
_ = reporter.Report(commandkit.Report{Value: value})
|
||||||
|
}
|
||||||
|
return commandkit.Success()
|
||||||
|
}
|
||||||
if acquired.Value.Effective == nil {
|
if acquired.Value.Effective == nil {
|
||||||
return commandkit.Failure(fmt.Sprintf("Configuration scope %q did not provide an effective configuration writer.", selected.scope.scope.Name))
|
return commandkit.Failure(fmt.Sprintf("Configuration scope %q did not provide an effective configuration writer.", selected.scope.scope.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ckconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
commandkit "go.maximumdirect.net/commandkit"
|
commandkit "go.maximumdirect.net/commandkit"
|
||||||
@@ -69,13 +70,15 @@ func runConfigSources[I, S any](invocation commandkit.Invocation, plan *configOp
|
|||||||
Selection: selected.selection,
|
Selection: selected.selection,
|
||||||
}, selected.input, state)
|
}, selected.input, state)
|
||||||
queueConfigAcquisition(reporter, acquired)
|
queueConfigAcquisition(reporter, acquired)
|
||||||
if acquireErr != nil {
|
if acquireErr == nil && invocation.Context.Err() != nil {
|
||||||
return configFailure(fmt.Sprintf("Unable to list sources for configuration scope %q.", name), acquired.Explanation, acquireErr)
|
err := invocation.Context.Err()
|
||||||
}
|
|
||||||
if err := invocation.Context.Err(); err != nil {
|
|
||||||
return configFailure("Configuration source listing was canceled.", acquired.Explanation, err)
|
return configFailure("Configuration source listing was canceled.", acquired.Explanation, err)
|
||||||
}
|
}
|
||||||
if acquired.Value.Sources == nil {
|
if acquired.Value.Sources == nil {
|
||||||
|
if acquireErr != nil {
|
||||||
|
explanation := sourceSelectionExplanation(acquired.Explanation, "Source inventory is unavailable.")
|
||||||
|
return configFailure(fmt.Sprintf("Unable to list sources for configuration scope %q.", name), explanation, acquireErr)
|
||||||
|
}
|
||||||
cause := fmt.Errorf("configuration scope %q did not provide a source reader", name)
|
cause := fmt.Errorf("configuration scope %q did not provide a source reader", name)
|
||||||
return configFailure("Configuration scope did not provide source information.", acquired.Explanation, cause)
|
return configFailure("Configuration scope did not provide source information.", acquired.Explanation, cause)
|
||||||
}
|
}
|
||||||
@@ -83,6 +86,9 @@ func runConfigSources[I, S any](invocation commandkit.Invocation, plan *configOp
|
|||||||
sources := acquired.Value.Sources.Sources()
|
sources := acquired.Value.Sources.Sources()
|
||||||
document := sourceReport{Scope: name, Sources: make([]sourceDTO, 0, len(sources))}
|
document := sourceReport{Scope: name, Sources: make([]sourceDTO, 0, len(sources))}
|
||||||
for _, source := range sources {
|
for _, source := range sources {
|
||||||
|
if plan.sourceID.Set && source.ID != plan.sourceID.Value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
placement := "mapping"
|
placement := "mapping"
|
||||||
if source.PlacementMode == configkit.PlacementModeSequenceItem {
|
if source.PlacementMode == configkit.PlacementModeSequenceItem {
|
||||||
placement = "sequence_item"
|
placement = "sequence_item"
|
||||||
@@ -121,14 +127,36 @@ func runConfigSources[I, S any](invocation commandkit.Invocation, plan *configOp
|
|||||||
item.EmptyImportSelections = append(item.EmptyImportSelections, emptyImportSelectionDTO{Path: selection.Path.String(), Location: locationDTO{SourceID: selection.Location.SourceID, Line: selection.Location.Line, Column: selection.Location.Column}, Selector: string(selection.Selector)})
|
item.EmptyImportSelections = append(item.EmptyImportSelections, emptyImportSelectionDTO{Path: selection.Path.String(), Location: locationDTO{SourceID: selection.Location.SourceID, Line: selection.Location.Line, Column: selection.Location.Column}, Selector: string(selection.Selector)})
|
||||||
}
|
}
|
||||||
document.Sources = append(document.Sources, item)
|
document.Sources = append(document.Sources, item)
|
||||||
|
if plan.sourceID.Set {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if plan.sourceID.Set && len(document.Sources) == 0 {
|
||||||
|
message := fmt.Sprintf("Source ID %q was not found in configuration scope %q.", plan.sourceID.Value, name)
|
||||||
|
if acquireErr != nil {
|
||||||
|
return configFailure(fmt.Sprintf("Unable to list sources for configuration scope %q.", name), sourceSelectionExplanation(acquired.Explanation, message), acquireErr)
|
||||||
|
}
|
||||||
|
return configFailure(message, acquired.Explanation, fmt.Errorf("source ID %q was not found", plan.sourceID.Value))
|
||||||
|
}
|
||||||
|
if acquireErr == nil && invocation.Context.Err() != nil {
|
||||||
|
err := invocation.Context.Err()
|
||||||
return configFailure("Configuration source listing was canceled.", acquired.Explanation, err)
|
return configFailure("Configuration source listing was canceled.", acquired.Explanation, err)
|
||||||
}
|
}
|
||||||
_ = reporter.Report(commandkit.Report{Value: document, TextTemplate: sourceTextTemplate})
|
_ = reporter.Report(commandkit.Report{Value: document, TextTemplate: sourceTextTemplate})
|
||||||
|
if acquireErr != nil {
|
||||||
|
return configFailure(fmt.Sprintf("Unable to list sources for configuration scope %q.", name), acquired.Explanation, acquireErr)
|
||||||
|
}
|
||||||
return commandkit.Success()
|
return commandkit.Success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sourceSelectionExplanation(explanation commandkit.Explanation, message string) commandkit.Explanation {
|
||||||
|
explanation.Details = append(append([]commandkit.Detail(nil), explanation.Details...), func(writer io.Writer) error {
|
||||||
|
_, err := fmt.Fprintln(writer, message)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return explanation
|
||||||
|
}
|
||||||
|
|
||||||
var sourceTextTemplate = template.Must(commandkit.ParseTextTemplate("sources", `Scope: {{printf "%q" .Scope}}
|
var sourceTextTemplate = template.Must(commandkit.ParseTextTemplate("sources", `Scope: {{printf "%q" .Scope}}
|
||||||
{{if .Sources}}Sources:
|
{{if .Sources}}Sources:
|
||||||
{{range .Sources}} - id: {{printf "%q" .ID}}
|
{{range .Sources}} - id: {{printf "%q" .ID}}
|
||||||
|
|||||||
Reference in New Issue
Block a user