Prepare configuration scope metadata

This commit is contained in:
2026-10-09 01:46:19 +00:00
parent a7960e5e94
commit 62b4a48e4f
8 changed files with 242 additions and 105 deletions

109
config.go
View File

@@ -97,6 +97,14 @@ type scopesInventory struct {
Scopes []configScopeInfo `yaml:"scopes"`
}
type preparedConfigSuite[C any] struct {
suite ConfigSuite[C]
ordered []ConfigScope[C]
byName map[string]ConfigScope[C]
installed []ConfigOperation
inventory scopesInventory
}
type configScopesInput struct {
Format string
}
@@ -135,42 +143,27 @@ func RegisterConfig[C any](app *App, suite ConfigSuite[C]) error {
if _, exists := app.commands["config"]; exists {
return fmt.Errorf("command path %q is already registered", "config")
}
prepared := prepareConfigSuite(normalized)
commands := make([]Command, 0, 5)
installed := make([]ConfigOperation, 0, 4)
if configOperationDeclared(normalized.Scopes, ConfigValidate) {
validate, err := newConfigValidateCommand(normalized)
for _, operation := range prepared.installed {
var command Command
switch operation {
case ConfigValidate:
command, err = newConfigValidateCommand(prepared)
case ConfigShow:
command, err = newConfigShowCommand(prepared)
case ConfigSources:
command, err = newConfigSourcesCommand(prepared)
case ConfigExplain:
command, err = newConfigExplainCommand(prepared)
}
if err != nil {
return err
}
commands = append(commands, validate)
installed = append(installed, ConfigValidate)
commands = append(commands, command)
}
if configOperationDeclared(normalized.Scopes, ConfigShow) {
show, err := newConfigShowCommand(normalized)
if err != nil {
return err
}
commands = append(commands, show)
installed = append(installed, ConfigShow)
}
if configOperationDeclared(normalized.Scopes, ConfigSources) {
sources, err := newConfigSourcesCommand(normalized)
if err != nil {
return err
}
commands = append(commands, sources)
installed = append(installed, ConfigSources)
}
if configOperationDeclared(normalized.Scopes, ConfigExplain) {
explain, err := newConfigExplainCommand(normalized)
if err != nil {
return err
}
commands = append(commands, explain)
installed = append(installed, ConfigExplain)
}
command, err := newConfigScopesCommandWithScopes(normalized.Scopes, installed)
command, err := newConfigScopesCommandWithPrepared(prepared)
if err != nil {
return err
}
@@ -290,6 +283,8 @@ func normalizeConfigSuite[C any](suite ConfigSuite[C]) (ConfigSuite[C], error) {
scope.Operations = operations
normalized.Scopes = append(normalized.Scopes, scope)
}
seenNames := make(map[string]struct{}, len(normalized.Selectors))
seenShorts := make(map[string]struct{}, len(normalized.Selectors))
for index := range normalized.Selectors {
selector := &normalized.Selectors[index]
binding := selector.Binding.binding
@@ -321,16 +316,48 @@ func normalizeConfigSuite[C any](suite ConfigSuite[C]) (ConfigSuite[C], error) {
unique = append(unique, scopeName)
}
selector.Scopes = append([]string(nil), unique...)
for previous := 0; previous < index; previous++ {
other := normalized.Selectors[previous].Binding.binding.meta
if other.Name == binding.meta.Name || binding.meta.Short != "" && other.Short == binding.meta.Short {
if _, exists := seenNames[binding.meta.Name]; exists {
return ConfigSuite[C]{}, fmt.Errorf("duplicate configuration selector flag --%s", binding.meta.Name)
}
if binding.meta.Short != "" {
if _, exists := seenShorts[binding.meta.Short]; exists {
return ConfigSuite[C]{}, fmt.Errorf("duplicate configuration selector flag --%s", binding.meta.Name)
}
seenShorts[binding.meta.Short] = struct{}{}
}
seenNames[binding.meta.Name] = struct{}{}
}
return normalized, nil
}
func prepareConfigSuite[C any](suite ConfigSuite[C]) *preparedConfigSuite[C] {
prepared := &preparedConfigSuite[C]{
suite: suite,
ordered: orderedConfigScopes(suite.Scopes),
byName: make(map[string]ConfigScope[C], len(suite.Scopes)),
installed: make([]ConfigOperation, 0, 4),
inventory: scopesInventory{Scopes: make([]configScopeInfo, 0, len(suite.Scopes))},
}
for _, scope := range suite.Scopes {
prepared.byName[scope.Name] = scope
}
for _, operation := range []ConfigOperation{ConfigValidate, ConfigShow, ConfigSources, ConfigExplain} {
if configOperationDeclared(suite.Scopes, operation) {
prepared.installed = append(prepared.installed, operation)
}
}
for _, scope := range prepared.ordered {
operations := make([]ConfigOperation, 0, len(scope.Operations))
for _, operation := range scope.Operations {
if containsOperation(prepared.installed, operation) {
operations = append(operations, operation)
}
}
prepared.inventory.Scopes = append(prepared.inventory.Scopes, configScopeInfo{Name: scope.Name, Operations: operations})
}
return prepared
}
func normalizeConfigOperations(operations []ConfigOperation) ([]ConfigOperation, error) {
seen := make(map[ConfigOperation]struct{}, len(operations))
for _, operation := range operations {
@@ -350,7 +377,7 @@ func normalizeConfigOperations(operations []ConfigOperation) ([]ConfigOperation,
return ordered, nil
}
func newConfigScopesCommandWithScopes[C any](scopes []ConfigScope[C], installed []ConfigOperation) (Command, error) {
func newConfigScopesCommandWithPrepared[C any](prepared *preparedConfigSuite[C]) (Command, error) {
formatDefault := "text"
spec := CommandSpec[configScopesInput]{
Name: "scopes",
@@ -362,22 +389,12 @@ func newConfigScopesCommandWithScopes[C any](scopes []ConfigScope[C], installed
})},
CheckInput: func(input configScopesInput) error { return checkConfigFormat(input.Format) },
Run: func(invocation Invocation, input configScopesInput) Outcome {
inventory := scopesInventory{Scopes: make([]configScopeInfo, 0, len(scopes))}
for _, scope := range orderedConfigScopes(scopes) {
operations := make([]ConfigOperation, 0, len(scope.Operations))
for _, operation := range scope.Operations {
if containsOperation(installed, operation) {
operations = append(operations, operation)
}
}
inventory.Scopes = append(inventory.Scopes, configScopeInfo{Name: scope.Name, Operations: operations})
}
var output []byte
var err error
if input.Format == "yaml" {
output, err = yaml.Marshal(inventory)
output, err = yaml.Marshal(prepared.inventory)
} else {
output = []byte(formatScopeInventory(inventory))
output = []byte(formatScopeInventory(prepared.inventory))
}
if err != nil {
return Failure(err)

View File

@@ -85,6 +85,77 @@ func TestConfigInputDefaultsToAppAndRetainsAbsentSelections(t *testing.T) {
}
}
func TestRegisterConfigOwnsPreparedScopeAndSelectorMetadata(t *testing.T) {
type selectorInput struct{ Region Supplied[string] }
var acquired selectorInput
suite := ConfigSuite[selectorInput]{
Selectors: []Selector[selectorInput]{{
Binding: ValueFlag[selectorInput](FlagMeta{Name: "region"}, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Region = value }),
Scopes: []string{"app"},
}},
Scopes: []ConfigScope[selectorInput]{{
Name: "app",
Operations: []ConfigOperation{ConfigValidate},
Acquire: func(_ context.Context, _ ConfigRequest, input selectorInput) (Acquired[ConfigView], error) {
acquired = input
return Acquired[ConfigView]{}, nil
},
}},
}
app, err := New(Identity{Name: "example"})
if err != nil {
t.Fatal(err)
}
if err := RegisterConfig(app, suite); err != nil {
t.Fatal(err)
}
suite.Scopes[0].Name = "renamed"
suite.Scopes[0].Operations[0] = ConfigShow
suite.Selectors[0].Scopes[0] = "renamed"
var inventory bytes.Buffer
if result := app.Execute(context.Background(), []string{"config", "scopes"}, Streams{Out: &inventory}); result.Kind != OutcomeSuccess {
t.Fatalf("scope inventory outcome = %#v", result)
}
if got := inventory.String(); got != "app: validate\n" {
t.Fatalf("scope inventory after caller mutation = %q", got)
}
if result := app.Execute(context.Background(), []string{"config", "validate", "--scope=app", "--region=west"}, Streams{}); result.Kind != OutcomeSuccess {
t.Fatalf("registered scope execution after caller mutation = %#v", result)
}
if acquired.Region != (Supplied[string]{Value: "west", Set: true}) {
t.Fatalf("acquired selector after caller mutation = %#v", acquired)
}
}
func TestRegisterConfigRejectsDuplicateSelectorNamesAndShorts(t *testing.T) {
type selectorInput struct{ First, Second string }
tests := []struct {
name string
firstMeta FlagMeta
secondMeta FlagMeta
}{
{name: "name", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "region", Short: "g"}},
{name: "short", firstMeta: FlagMeta{Name: "region", Short: "r"}, secondMeta: FlagMeta{Name: "group", Short: "r"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
app, err := New(Identity{Name: "example"})
if err != nil {
t.Fatal(err)
}
suite := ConfigSuite[selectorInput]{Selectors: []Selector[selectorInput]{
{Binding: ValueFlag[selectorInput](test.firstMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.First = value.Value })},
{Binding: ValueFlag[selectorInput](test.secondMeta, nil, parseString, func(input *selectorInput, value Supplied[string]) { input.Second = value.Value })},
}}
if err := RegisterConfig(app, suite); err == nil || !strings.Contains(err.Error(), "duplicate configuration selector flag") {
t.Fatalf("RegisterConfig error = %v", err)
}
})
}
}
func TestRegisterConfigRejectsInvalidSuitesAndCollisions(t *testing.T) {
tests := []struct {
name string
@@ -147,13 +218,14 @@ func TestConfigSelectorPreflightAndScopedContextProjection(t *testing.T) {
if err != nil {
t.Fatal(err)
}
preparedSuite := prepareConfigSuite(normalized)
spec := CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}
prepared, err := prepareInput(spec, [][]string{{"all"}, {"text"}, {""}, nil, {"0"}, {"false"}, nil}, nil, true)
if err != nil {
t.Fatal(err)
}
input := prepared.build()
scopeNames, err := preflightConfig(normalized, input, ConfigValidate)
scopeNames, err := preflightConfig(preparedSuite, input, ConfigValidate)
if err != nil {
t.Fatal(err)
}
@@ -167,7 +239,7 @@ func TestConfigSelectorPreflightAndScopedContextProjection(t *testing.T) {
t.Fatalf("NewContext called during preflight %d times", baseContextCalls)
}
contexts := contextsForScopes(normalized, input, scopeNames)
contexts := contextsForScopes(preparedSuite, input, scopeNames)
if baseContextCalls != 1 {
t.Fatalf("NewContext calls = %d, want one", baseContextCalls)
}
@@ -194,11 +266,12 @@ func TestConfigPreflightRejectsUnsupportedAggregateAndInapplicableSelector(t *te
if err != nil {
t.Fatal(err)
}
preparedSuite := prepareConfigSuite(normalized)
prepared, err := prepareInput(CommandSpec[configInput[selectorInput]]{Flags: configInputFlags(normalized, "text")}, [][]string{{"all"}, {"text"}, nil, nil, nil}, nil, true)
if err != nil {
t.Fatal(err)
}
if _, err := preflightConfig(normalized, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "does not support validate") {
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "does not support validate") {
t.Fatalf("unsupported aggregate error = %v", err)
}
if newContextCalls != 0 {
@@ -209,7 +282,7 @@ func TestConfigPreflightRejectsUnsupportedAggregateAndInapplicableSelector(t *te
if err != nil {
t.Fatal(err)
}
if _, err := preflightConfig(normalized, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "not applicable") {
if _, err := preflightConfig(preparedSuite, prepared.build(), ConfigValidate); err == nil || !strings.Contains(err.Error(), "not applicable") {
t.Fatalf("inapplicable selector error = %v", err)
}
}

View File

@@ -779,7 +779,7 @@ allocation costs, with timing variation on repeated success and deep dispatch.
## Stage 10: prepare configuration scope metadata once
**Status:** not started. **Prerequisite:** Stage 9.
**Status:** complete. **Prerequisite:** Stage 9.
**Files:** [config.go](../../config.go), [selectors.go](../../selectors.go),
configuration handler files, configuration/cancellation tests, scope benchmarks,
@@ -821,7 +821,58 @@ Preserve native reporting and per-operation output timing.
scope lookup is indexed, and duplicate selector checks are linear in selector
count plus declared scope references.
**Evidence:** pending.
**Evidence:** `GOWORK=off GOTOOLCHAIN=local GOPROXY=off GONOPROXY=none
GOSUMDB=off go test -mod=readonly -count=1 .` passed, including existing
configuration, selector, discovery, and cancellation behavior. Added coverage
that caller mutations to scope and selector slices after registration do not
change discovery, capability selection, or projected inputs, and that duplicate
selector names and short names retain their error category. Registration now
normalizes owned metadata, prepares the ordered scopes, name index, installed
operation list, and discovery DTO once, then passes that private value through
validate/show/sources/explain and their preflight/run paths. Aggregate selection
checks every capability using the prepared metadata before any acquisition.
`NewContext`, acquisition, checks, native writers, and encoding are not called
while preparing the suite. Discovery renders the retained DTO at invocation
time. Scope sorting occurs once, named lookup uses the map, and duplicate
selector validation uses seen-name/seen-short maps while preserving validation
order and deduplicating scope references. Public `ConfigSuite` and `ConfigScope`
declarations and signatures are unchanged.
Measurements used Go 1.26.8, linux/amd64, Linux 6.17.0-41-generic, Intel Core
i5-7260U CPU @ 2.20 GHz, based on source revision `a7960e5` with this stage's
changes in the working tree. The five-sample command passed:
`GOWORK=off GOTOOLCHAIN=local GOPROXY=off GONOPROXY=none GOSUMDB=off go test -mod=readonly -run '^$' -bench '^BenchmarkPerformance(ConfigScopes|ConfigRegistration)$' -benchmem -benchtime=200ms -count=5 .`.
Raw output is in `/tmp/commandkit-performance-config-after-stage10.txt`;
focused repeats for selector-heavy registration and the ordinary two-scope
aggregate are in `/tmp/commandkit-performance-config-registration-repeat.txt`
and `/tmp/commandkit-performance-config-two-scope-repeat.txt`.
Against Stage 2, two-scope discovery measured 16,788–21,062 ns/op, 18,557
B/op, and 195 allocations (baseline: 23,840–30,007 ns/op, 21,161–21,162
B/op, 223 allocations). Named selection measured 18,375–21,756 ns/op,
19,621 B/op, and 207 allocations (baseline: 24,841–26,340 ns/op, 21,817
B/op, 228 allocations). Two-scope aggregate validation measured 22,977–28,152
ns/op, 19,637–19,638 B/op, and 208 allocations in the focused repeat (baseline:
25,231–31,126 ns/op, 22,361–22,362 B/op, 237 allocations).
At 32 scopes, discovery measured 18,253–20,311 ns/op, 21,070 B/op, and 199
allocations (baseline: 31,439–37,167 ns/op, 28,141–28,142 B/op, 257
allocations). Named selection measured 18,679–20,529 ns/op, 20,582 B/op, and
207 allocations (baseline: 29,123–74,374 ns/op, 22,775–22,778 B/op, 228
allocations). Aggregate validation measured 25,036–33,981 ns/op, 22,702 B/op,
and 240 allocations (baseline: 45,548–63,471 ns/op, 29,781–29,782 B/op, 269
allocations).
Registration with 4 selectors measured 11,272–14,623 ns/op, 9,310 B/op, and
107 allocations (baseline: 12,573–14,789 ns/op, 8,911 B/op, 104 allocations).
With 64 selectors, the focused repeat measured 72,002–74,712 ns/op, 90,197–
90,200 B/op, and 494 allocations (baseline: 91,675–105,548 ns/op,
82,817–82,818 B/op, 485 allocations). The retained ordered slice, lookup map,
and discovery DTO add registration storage: about 399 bytes and 3 allocations
for 4 selectors, and 7.4 KiB and 9 allocations for 64 selectors. Selector-heavy
registration timing improved in the focused repeat; execution ranges improved
for discovery and named/aggregate selection. These benchmark timings vary and
are observations, not thresholds.
## Stage 11: audit the completed target and reconcile documentation

View File

@@ -9,8 +9,8 @@ import (
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
flags := configInputFlags(suite, "text")
func newConfigExplainCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
flags := configInputFlags(prepared.suite, "text")
flags = append(flags, ValueFlag[configInput[C]](FlagMeta{
Name: "path",
Usage: "Configuration path as a JSON Pointer; an empty value selects the root.",
@@ -23,7 +23,7 @@ func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
Summary: "Explain the effective and historical value at a configuration path.",
Flags: flags,
CheckInput: func(input configInput[C]) error {
if _, err := preflightConfig(suite, input, ConfigExplain); err != nil {
if _, err := preflightConfig(prepared, input, ConfigExplain); err != nil {
return err
}
if !input.Path.Set {
@@ -35,7 +35,7 @@ func newConfigExplainCommand[C any](suite ConfigSuite[C]) (Command, error) {
return nil
},
Run: func(invocation Invocation, input configInput[C]) Outcome {
return runConfigExplanation(invocation, suite, input)
return runConfigExplanation(invocation, prepared, input)
},
}
return NewCommand(spec)
@@ -82,11 +82,11 @@ type secretReferenceDTO struct {
Name string `yaml:"name"`
}
func runConfigExplanation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
func runConfigExplanation[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
if err := invocation.Context.Err(); err != nil {
return Failure(err)
}
scopeNames, err := selectConfigScopes(suite, input, ConfigExplain)
scopeNames, err := selectConfigScopes(prepared, input, ConfigExplain)
if err != nil {
return Failure(err)
}
@@ -98,8 +98,8 @@ func runConfigExplanation[C any](invocation Invocation, suite ConfigSuite[C], in
return Usage(fmt.Errorf("invalid configuration path %q: %w", input.Path.Value, err))
}
name := scopeNames[0]
contexts := contextsForScopes(suite, input, scopeNames)
scope, ok := findConfigScope(suite.Scopes, name)
contexts := contextsForScopes(prepared, input, scopeNames)
scope, ok := findConfigScope(prepared, name)
if !ok || scope.Acquire == nil {
return Failure(fmt.Errorf("configuration scope %q has no acquisition callback", name))
}

View File

@@ -82,20 +82,20 @@ func configurationSelectionFlags[I any](setConfig, setOverlay func(*I, Supplied[
func parseString(value string) (string, error) { return value, nil }
func preflightConfig[C any](suite ConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
scopes, err := selectConfigScopes(suite, input, operation)
func preflightConfig[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
scopes, err := selectConfigScopes(prepared, input, operation)
if err != nil {
return nil, err
}
if suite.CheckInput != nil {
if err := suite.CheckInput(input.Selection, input.SelectorContext); err != nil {
if prepared.suite.CheckInput != nil {
if err := prepared.suite.CheckInput(input.Selection, input.SelectorContext); err != nil {
return nil, err
}
}
return scopes, nil
}
func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
func selectConfigScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], operation ConfigOperation) ([]string, error) {
if err := checkConfigFormat(input.Format); err != nil {
return nil, err
}
@@ -110,16 +110,16 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
return nil, fmt.Errorf("configuration scope must not be empty")
}
scopes := make([]string, 0, len(suite.Scopes))
scopes := make([]string, 0, len(prepared.ordered))
if input.Scope == "all" {
for _, scope := range orderedConfigScopes(suite.Scopes) {
for _, scope := range prepared.ordered {
scopes = append(scopes, scope.Name)
}
} else {
scope, ok := findConfigScope(suite.Scopes, input.Scope)
scope, ok := prepared.byName[input.Scope]
if !ok {
choices := make([]string, 0, len(suite.Scopes))
for _, available := range orderedConfigScopes(suite.Scopes) {
choices := make([]string, 0, len(prepared.ordered))
for _, available := range prepared.ordered {
choices = append(choices, available.Name)
}
return nil, fmt.Errorf("unknown configuration scope %q (available: %s)", input.Scope, joinStrings(choices))
@@ -132,7 +132,7 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
if input.Scope == "all" {
for _, scopeName := range scopes {
scope, _ := findConfigScope(suite.Scopes, scopeName)
scope := prepared.byName[scopeName]
if !containsOperation(scope.Operations, operation) {
return nil, unsupportedConfigOperation(scope, operation)
}
@@ -156,10 +156,10 @@ func selectConfigScopes[C any](suite ConfigSuite[C], input configInput[C], opera
return scopes, nil
}
func contextsForScopes[C any](suite ConfigSuite[C], input configInput[C], scopeNames []string) map[string]C {
func contextsForScopes[C any](prepared *preparedConfigSuite[C], input configInput[C], scopeNames []string) map[string]C {
base := *new(C)
if suite.NewContext != nil {
base = suite.NewContext()
if prepared.suite.NewContext != nil {
base = prepared.suite.NewContext()
}
contexts := make(map[string]C, len(scopeNames))
for _, name := range scopeNames {
@@ -176,13 +176,9 @@ func contextsForScopes[C any](suite ConfigSuite[C], input configInput[C], scopeN
return contexts
}
func findConfigScope[C any](scopes []ConfigScope[C], name string) (ConfigScope[C], bool) {
for _, scope := range scopes {
if scope.Name == name {
return scope, true
}
}
return ConfigScope[C]{}, false
func findConfigScope[C any](prepared *preparedConfigSuite[C], name string) (ConfigScope[C], bool) {
scope, ok := prepared.byName[name]
return scope, ok
}
func containsOperation(operations []ConfigOperation, requested ConfigOperation) bool {

16
show.go
View File

@@ -8,17 +8,17 @@ import (
"unicode"
)
func newConfigShowCommand[C any](suite ConfigSuite[C]) (Command, error) {
func newConfigShowCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
spec := CommandSpec[configInput[C]]{
Name: string(ConfigShow),
Summary: "Show effective configuration as YAML.",
Flags: configInputFlags(suite, "yaml"),
Flags: configInputFlags(prepared.suite, "yaml"),
CheckInput: func(input configInput[C]) error {
_, err := preflightConfig(suite, input, ConfigShow)
_, err := preflightConfig(prepared, input, ConfigShow)
return err
},
Run: func(invocation Invocation, input configInput[C]) Outcome {
return runConfigShow(invocation, suite, input)
return runConfigShow(invocation, prepared, input)
},
}
return NewCommand(spec)
@@ -29,19 +29,19 @@ type effectiveDocument struct {
view ConfigView
}
func runConfigShow[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
func runConfigShow[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
if err := invocation.Context.Err(); err != nil {
return Failure(err)
}
scopeNames, err := selectConfigScopes(suite, input, ConfigShow)
scopeNames, err := selectConfigScopes(prepared, input, ConfigShow)
if err != nil {
return Failure(err)
}
contexts := contextsForScopes(suite, input, scopeNames)
contexts := contextsForScopes(prepared, input, scopeNames)
documents := make([]effectiveDocument, 0, len(scopeNames))
diagnostics := make([]Diagnostic, 0)
for _, name := range scopeNames {
scope, ok := findConfigScope(suite.Scopes, name)
scope, ok := findConfigScope(prepared, name)
if !ok || scope.Acquire == nil {
return showFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, false)
}

View File

@@ -6,17 +6,17 @@ import (
"strconv"
)
func newConfigSourcesCommand[C any](suite ConfigSuite[C]) (Command, error) {
func newConfigSourcesCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
spec := CommandSpec[configInput[C]]{
Name: string(ConfigSources),
Summary: "List configuration sources used by a scope.",
Flags: configInputFlags(suite, "text"),
Flags: configInputFlags(prepared.suite, "text"),
CheckInput: func(input configInput[C]) error {
_, err := preflightConfig(suite, input, ConfigSources)
_, err := preflightConfig(prepared, input, ConfigSources)
return err
},
Run: func(invocation Invocation, input configInput[C]) Outcome {
return runConfigSources(invocation, suite, input)
return runConfigSources(invocation, prepared, input)
},
}
return NewCommand(spec)
@@ -43,17 +43,17 @@ type locationDTO struct {
Column int `yaml:"column"`
}
func runConfigSources[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
func runConfigSources[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
if err := invocation.Context.Err(); err != nil {
return Failure(err)
}
scopeNames, err := selectConfigScopes(suite, input, ConfigSources)
scopeNames, err := selectConfigScopes(prepared, input, ConfigSources)
if err != nil {
return Failure(err)
}
contexts := contextsForScopes(suite, input, scopeNames)
contexts := contextsForScopes(prepared, input, scopeNames)
name := scopeNames[0]
scope, ok := findConfigScope(suite.Scopes, name)
scope, ok := findConfigScope(prepared, name)
if !ok || scope.Acquire == nil {
return sourceReportFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), nil, false)
}

View File

@@ -17,35 +17,35 @@ func configOperationDeclared[C any](scopes []ConfigScope[C], operation ConfigOpe
return false
}
func newConfigValidateCommand[C any](suite ConfigSuite[C]) (Command, error) {
func newConfigValidateCommand[C any](prepared *preparedConfigSuite[C]) (Command, error) {
spec := CommandSpec[configInput[C]]{
Name: string(ConfigValidate),
Summary: "Validate selected configuration scopes.",
Flags: configInputFlags(suite, "text"),
Flags: configInputFlags(prepared.suite, "text"),
CheckInput: func(input configInput[C]) error {
_, err := preflightConfig(suite, input, ConfigValidate)
_, err := preflightConfig(prepared, input, ConfigValidate)
return err
},
Run: func(invocation Invocation, input configInput[C]) Outcome {
return runConfigValidation(invocation, suite, input)
return runConfigValidation(invocation, prepared, input)
},
}
return NewCommand(spec)
}
func runConfigValidation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome {
func runConfigValidation[C any](invocation Invocation, prepared *preparedConfigSuite[C], input configInput[C]) Outcome {
if err := invocation.Context.Err(); err != nil {
return Failure(err)
}
scopeNames, err := selectConfigScopes(suite, input, ConfigValidate)
scopeNames, err := selectConfigScopes(prepared, input, ConfigValidate)
if err != nil {
return Failure(err)
}
contexts := contextsForScopes(suite, input, scopeNames)
contexts := contextsForScopes(prepared, input, scopeNames)
diagnostics := make([]Diagnostic, 0)
writtenRecord := false
for _, name := range scopeNames {
scope, ok := findConfigScope(suite.Scopes, name)
scope, ok := findConfigScope(prepared, name)
if !ok || scope.Acquire == nil {
return validationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, writtenRecord)
}