diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 4e04407..c3ae64c 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -116,7 +116,8 @@ Focused check: Finding: **F07**, engine representation. Owners: `internal/engine/import_spec.go`, `load.go`, `layered.go`, `load_request.go`, `acquisition.go`, `options.go`, and -their callers/fixtures. Status: **not started**. +their callers/fixtures. Status: **complete**. Validation passed: +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Import|Glob|Directory|SourceVersion'` and `git diff --check`. Root facade callers remain pending Stage 3. 1. Retain engine `ImportDefaults` with its existing five exported fields. Remove private `importDefaults`, without a compatibility alias. Update diff --git a/internal/engine/acquisition.go b/internal/engine/acquisition.go index 5135974..1b3e4c6 100644 --- a/internal/engine/acquisition.go +++ b/internal/engine/acquisition.go @@ -3,7 +3,7 @@ package engine import "strconv" type acquisitionPolicy struct { - importDefaults importDefaults + importDefaults ImportDefaults sequencePolicy sequencePolicy importLimits importLimits } diff --git a/internal/engine/directory_links_test.go b/internal/engine/directory_links_test.go index 4a46493..7a9519d 100644 --- a/internal/engine/directory_links_test.go +++ b/internal/engine/directory_links_test.go @@ -255,7 +255,7 @@ func selectDirectoryWithDiagnostics(t *testing.T, access *filesystemAccess, base if len(diagnostics) != 0 { t.Fatalf("parse diagnostics = %#v", diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatalf("policy diagnostics = %#v", diagnostics) } diff --git a/internal/engine/directory_selection_test.go b/internal/engine/directory_selection_test.go index f42c202..b24c0ff 100644 --- a/internal/engine/directory_selection_test.go +++ b/internal/engine/directory_selection_test.go @@ -34,7 +34,7 @@ func TestSelectShallowDirectoriesUnionsRootsAndPatterns(t *testing.T) { if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -82,7 +82,7 @@ func TestSelectShallowDirectoriesIncludeUnrestrictedHiddenAndExtensionlessFiles( if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -141,7 +141,7 @@ func TestSelectShallowDirectoriesApplyRequirednessToFinalUnion(t *testing.T) { if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -173,7 +173,7 @@ func TestSelectShallowDirectoryRejectsInvalidRootInsteadOfTreatingItAsMissing(t if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -188,7 +188,7 @@ func TestSelectShallowDirectoryRejectsInvalidRootInsteadOfTreatingItAsMissing(t if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics = resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics = resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -218,7 +218,7 @@ func TestSelectShallowDirectoryFiltersSymlinkAliasSpelling(t *testing.T) { if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -254,7 +254,7 @@ func TestSelectDirectoriesApplyRecursiveDepthToFileLevels(t *testing.T) { for _, test := range []struct { name string yaml string - defaults importDefaults + defaults ImportDefaults wantPaths []string }{ { @@ -280,13 +280,13 @@ func TestSelectDirectoriesApplyRecursiveDepthToFileLevels(t *testing.T) { { name: "explicit false overrides application recursion", yaml: " - dir: .\n recursive: false\n pattern: '*.yaml'\n", - defaults: importDefaults{recursive: boolPointer(true)}, + defaults: ImportDefaults{Recursive: boolPointer(true)}, wantPaths: []string{"direct.yaml"}, }, { name: "application default enables recursion for depth", yaml: " - dir: .\n depth: 2\n pattern: '*.yaml'\n", - defaults: importDefaults{recursive: boolPointer(true)}, + defaults: ImportDefaults{Recursive: boolPointer(true)}, wantPaths: []string{"child.yaml", "direct.yaml"}, }, { @@ -347,7 +347,7 @@ func TestSelectDirectoriesRetainsEmptySearchAndDirectoryVisitLimits(t *testing.T if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } diff --git a/internal/engine/glob_selection_test.go b/internal/engine/glob_selection_test.go index 3e73ef1..c32ca92 100644 --- a/internal/engine/glob_selection_test.go +++ b/internal/engine/glob_selection_test.go @@ -99,7 +99,7 @@ func TestSelectGlobAppliesExclusionsBeforeSourceAdmissionWithoutOpeningFiles(t * if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -181,7 +181,7 @@ func selectGlobForTestWithDiagnostics(t *testing.T, access *filesystemAccess, ba if len(diagnostics) != 0 { t.Fatalf("parse diagnostics = %#v", diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatalf("policy diagnostics = %#v", diagnostics) } diff --git a/internal/engine/import_selection_test.go b/internal/engine/import_selection_test.go index cf9f1a3..870b0d1 100644 --- a/internal/engine/import_selection_test.go +++ b/internal/engine/import_selection_test.go @@ -67,7 +67,7 @@ func TestSelectNamedImportsChecksRequiredTargetsBeforeExclusions(t *testing.T) { if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -118,7 +118,7 @@ func TestSelectNamedImportsDoesNotOpenExcludedContentOrApplySearchControls(t *te if len(diagnostics) != 0 { t.Fatal(diagnostics) } - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 0 { t.Fatal(diagnostics) } @@ -135,7 +135,7 @@ func TestSelectNamedImportsDoesNotOpenExcludedContentOrApplySearchControls(t *te t.Fatal(diagnostics) } hidden := false - privatePolicy, policyDiagnostics := resolveImportPolicy(private[0], importDefaults{hidden: &hidden}) + privatePolicy, policyDiagnostics := resolveImportPolicy(private[0], ImportDefaults{Hidden: &hidden}) if len(policyDiagnostics) != 0 || !privatePolicy.hidden { t.Fatalf("path selector accepted search-only defaults: %#v, %#v", privatePolicy, policyDiagnostics) } diff --git a/internal/engine/import_spec.go b/internal/engine/import_spec.go index 0c5efd8..168eb2e 100644 --- a/internal/engine/import_spec.go +++ b/internal/engine/import_spec.go @@ -42,15 +42,6 @@ type importDeclarationSpec struct { fieldSites map[string]importFieldSite } -// importDefaults are the engine's private copy of load-wide discovery defaults. -type importDefaults struct { - required *bool - follow *bool - hidden *bool - recursive *bool - pattern []string -} - // ImportDefaults transports root-owned load defaults across the facade // boundary. Load options snapshot this value before source acquisition. type ImportDefaults struct { @@ -65,26 +56,6 @@ type ImportDefaults struct { // Pattern filters directory-search filenames; nil and empty differ. Pattern []string } - -// OptionsWithImportDefaults copies root-owned defaults into ordinary options. -func OptionsWithImportDefaults[T any](options Options[T], value ImportDefaults) Options[T] { - options.importDefaults = privateImportDefaults(value) - return options -} - -// LayeredOptionsWithImportDefaults copies root-owned defaults into grouped options. -func LayeredOptionsWithImportDefaults[T any](options LayeredOptions[T], value ImportDefaults) LayeredOptions[T] { - options.importDefaults = privateImportDefaults(value) - return options -} - -func privateImportDefaults(value ImportDefaults) importDefaults { - return snapshotImportDefaults(importDefaults{ - required: value.Required, follow: value.Follow, hidden: value.Hidden, - recursive: value.Recursive, pattern: value.Pattern, - }) -} - type resolvedImportPolicy struct { required bool follow bool @@ -98,13 +69,13 @@ type resolvedImportPolicy struct { placement placementMode } -func snapshotImportDefaults(value importDefaults) importDefaults { - return importDefaults{ - required: cloneBool(value.required), - follow: cloneBool(value.follow), - hidden: cloneBool(value.hidden), - recursive: cloneBool(value.recursive), - pattern: cloneStrings(value.pattern), +func snapshotImportDefaults(value ImportDefaults) ImportDefaults { + return ImportDefaults{ + Required: cloneBool(value.Required), + Follow: cloneBool(value.Follow), + Hidden: cloneBool(value.Hidden), + Recursive: cloneBool(value.Recursive), + Pattern: cloneStrings(value.Pattern), } } @@ -123,8 +94,8 @@ func cloneStrings(values []string) []string { return append(make([]string, 0, len(values)), values...) } -func validateImportDefaults(value importDefaults) []Diagnostic { - for _, pattern := range value.pattern { +func validateImportDefaults(value ImportDefaults) []Diagnostic { + for _, pattern := range value.Pattern { if _, err := path.Match(pattern, ""); err != nil { return []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "import default pattern is invalid")} } @@ -132,10 +103,10 @@ func validateImportDefaults(value importDefaults) []Diagnostic { return nil } -func resolveImportPolicy(spec importDeclarationSpec, defaults importDefaults) (resolvedImportPolicy, []Diagnostic) { +func resolveImportPolicy(spec importDeclarationSpec, defaults ImportDefaults) (resolvedImportPolicy, []Diagnostic) { defaults = snapshotImportDefaults(defaults) policy := resolvedImportPolicy{ - required: boolOr(spec.required, defaults.required, false), + required: boolOr(spec.required, defaults.Required, false), follow: true, hidden: true, recursive: false, @@ -146,15 +117,15 @@ func resolveImportPolicy(spec importDeclarationSpec, defaults importDefaults) (r exclude: cloneStrings(spec.exclude), } if spec.selector == importSelectorDirectory || spec.selector == importSelectorGlob { - policy.follow = boolOr(spec.follow, defaults.follow, true) - policy.hidden = boolOr(spec.hidden, defaults.hidden, true) + policy.follow = boolOr(spec.follow, defaults.Follow, true) + policy.hidden = boolOr(spec.hidden, defaults.Hidden, true) } if spec.selector == importSelectorDirectory { - policy.recursive = boolOr(spec.recursive, defaults.recursive, false) + policy.recursive = boolOr(spec.recursive, defaults.Recursive, false) if spec.patternSet { policy.pattern = cloneStrings(spec.pattern) } else { - policy.pattern = cloneStrings(defaults.pattern) + policy.pattern = cloneStrings(defaults.Pattern) } } if spec.depthSet && !policy.recursive { diff --git a/internal/engine/import_spec_test.go b/internal/engine/import_spec_test.go index 7dc66dc..a05dab7 100644 --- a/internal/engine/import_spec_test.go +++ b/internal/engine/import_spec_test.go @@ -123,13 +123,13 @@ func TestParseImportDeclarationSpecValidatesDepthSyntaxAndPolicy(t *testing.T) { if len(diagnostics) != 0 { t.Fatalf("syntax-only parse diagnostics = %#v", diagnostics) } - _, diagnostics = resolveImportPolicy(specs[0], importDefaults{}) + _, diagnostics = resolveImportPolicy(specs[0], ImportDefaults{}) if len(diagnostics) != 1 || diagnostics[0].Path.String() != "/configkit/imports/0/depth" { t.Fatalf("policy diagnostics = %#v, want depth applicability error", diagnostics) } recursive := true - policy, diagnostics := resolveImportPolicy(specs[0], importDefaults{recursive: &recursive}) + policy, diagnostics := resolveImportPolicy(specs[0], ImportDefaults{Recursive: &recursive}) if len(diagnostics) != 0 || !policy.recursive || policy.depth != 1 || !policy.depthSet { t.Fatalf("application-default policy, diagnostics = %#v, %#v", policy, diagnostics) } @@ -142,7 +142,7 @@ func TestResolveImportPolicyAppliesDefaultsIndependently(t *testing.T) { if len(diagnostics) != 0 { t.Fatal(diagnostics) } - omitted, diagnostics := resolveImportPolicy(omittedSpecs[0], importDefaults{}) + omitted, diagnostics := resolveImportPolicy(omittedSpecs[0], ImportDefaults{}) if len(diagnostics) != 0 || omitted.required || omitted.recursive || !omitted.follow || !omitted.hidden || omitted.pattern != nil { t.Fatalf("library defaults = %#v, diagnostics = %#v", omitted, diagnostics) } @@ -159,12 +159,12 @@ func TestResolveImportPolicyAppliesDefaultsIndependently(t *testing.T) { t.Fatal(diagnostics) } appRequired, appRecursive, appFollow, appHidden := true, true, true, true - defaults := importDefaults{ - required: &appRequired, - recursive: &appRecursive, - follow: &appFollow, - hidden: &appHidden, - pattern: []string{"*.yaml"}, + defaults := ImportDefaults{ + Required: &appRequired, + Recursive: &appRecursive, + Follow: &appFollow, + Hidden: &appHidden, + Pattern: []string{"*.yaml"}, } policy, diagnostics := resolveImportPolicy(specs[0], defaults) if len(diagnostics) != 0 { @@ -188,16 +188,16 @@ func TestResolveImportPolicyAppliesDefaultsIndependently(t *testing.T) { } snapshot := snapshotImportDefaults(defaults) - *defaults.required = false - defaults.pattern[0] = "*.toml" - if snapshot.required == nil || !*snapshot.required || snapshot.pattern[0] != "*.yaml" { + *defaults.Required = false + defaults.Pattern[0] = "*.toml" + if snapshot.Required == nil || !*snapshot.Required || snapshot.Pattern[0] != "*.yaml" { t.Fatalf("snapshot shares mutable inputs: %#v", snapshot) } - empty := snapshotImportDefaults(importDefaults{pattern: []string{}}) - if empty.pattern == nil || len(empty.pattern) != 0 { + empty := snapshotImportDefaults(ImportDefaults{Pattern: []string{}}) + if empty.Pattern == nil || len(empty.Pattern) != 0 { t.Fatalf("snapshot lost explicit empty pattern: %#v", empty) } - if snapshotImportDefaults(importDefaults{}).pattern != nil { + if snapshotImportDefaults(ImportDefaults{}).Pattern != nil { t.Fatal("snapshot changed omitted pattern into explicit empty") } } @@ -207,7 +207,7 @@ func TestInvalidImportDefaultPatternsFailBeforeReadingSource(t *testing.T) { reader := &countingImportSpecReader{Reader: strings.NewReader("not: read\n")} _, err := Load(Reader("invalid-default-pattern.yml", reader), testSecretBinding(), Options[struct{}]{ - importDefaults: importDefaults{pattern: []string{"["}}, + ImportDefaults: ImportDefaults{Pattern: []string{"["}}, }) var loadErr *LoadError if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 || loadErr.Diagnostics()[0].Code != CodeOptionsInvalid { diff --git a/internal/engine/imports_test.go b/internal/engine/imports_test.go index fdedc07..642c1a9 100644 --- a/internal/engine/imports_test.go +++ b/internal/engine/imports_test.go @@ -884,7 +884,7 @@ func TestImportDefaultsReachOrdinaryAndGroupedDeclarations(t *testing.T) { mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n") writeImportFile(t, directory, "child.yml", "configkit: {imports: [{path: absent.yml}]}\n") _, err := testLoad[importTestConfig](File(mainPath), Options[importTestConfig]{ - Imports: true, importDefaults: importDefaults{required: &required}, + Imports: true, ImportDefaults: ImportDefaults{Required: &required}, }) var loadError *LoadError if !errors.As(err, &loadError) || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportMissing { @@ -896,7 +896,7 @@ func TestImportDefaultsReachOrdinaryAndGroupedDeclarations(t *testing.T) { _, err = loadGroupedWithSecretLookup(loadRequest[importTestConfig]{ main: loadInput{source: File(basePath)}, underlays: []loadGroup{{inputs: []loadInput{{source: File(underlayPath), imports: true}}}}, - importDefaults: importDefaults{required: &required}, + importDefaults: ImportDefaults{Required: &required}, }, testSecretBinding(), func(string) (string, bool) { return "", false }) if !errors.As(err, &loadError) || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeImportMissing { t.Fatalf("grouped underlay required default error = %#v", err) diff --git a/internal/engine/layered.go b/internal/engine/layered.go index a75a850..f087b7a 100644 --- a/internal/engine/layered.go +++ b/internal/engine/layered.go @@ -17,7 +17,7 @@ type Underlay struct{ Inputs []Input } // LayeredOptions selects lower-priority tiers and final application rules. type LayeredOptions[T any] struct { Definition Definition[T] - importDefaults importDefaults + ImportDefaults ImportDefaults SourceVersionMode sourceVersionMode Underlays []Underlay Overlay string @@ -41,7 +41,7 @@ func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOp destination: main.Destination.clone(), placementMode: main.placementMode, }, definition: options.Definition, sourceVersionMode: options.SourceVersionMode, - importDefaults: snapshotImportDefaults(options.importDefaults), + importDefaults: snapshotImportDefaults(options.ImportDefaults), overlay: options.Overlay, overlayFilesystem: options.OverlayFilesystem, overrides: append([]Override(nil), options.Overrides...), diff --git a/internal/engine/load.go b/internal/engine/load.go index 466135e..27d3b46 100644 --- a/internal/engine/load.go +++ b/internal/engine/load.go @@ -32,7 +32,7 @@ type Options[T any] struct { Filesystem *FilesystemContext Overrides []Override SourceVersionMode sourceVersionMode - importDefaults importDefaults + ImportDefaults ImportDefaults destination Path placementMode placementMode } @@ -163,7 +163,7 @@ func loadWithSecretLookupLimit[T any](source Source, secrets SecretBinding, look source: source, imports: options.Imports, filesystem: options.Filesystem, destination: options.destination.clone(), placementMode: options.placementMode, }, options.Overlay) - defaults, policy, settingsDiagnostics := loadSettings(options.importDefaults, options.Definition, options.SourceVersionMode) + defaults, policy, settingsDiagnostics := loadSettings(options.ImportDefaults, options.Definition, options.SourceVersionMode) diagnostics = append(diagnostics, settingsDiagnostics...) diagnostics = append(diagnostics, overlayOptionDiagnostics(options.Overlay)...) if len(diagnostics) != 0 { diff --git a/internal/engine/load_request.go b/internal/engine/load_request.go index a79e9d2..91b82eb 100644 --- a/internal/engine/load_request.go +++ b/internal/engine/load_request.go @@ -10,7 +10,7 @@ type loadRequest[T any] struct { main loadInput underlays []loadGroup definition Definition[T] - importDefaults importDefaults + importDefaults ImportDefaults sequencePolicy sequencePolicy sourceVersionMode sourceVersionMode effectiveVersion *VersionRule diff --git a/internal/engine/options.go b/internal/engine/options.go index bce7c05..4347ec5 100644 --- a/internal/engine/options.go +++ b/internal/engine/options.go @@ -35,7 +35,7 @@ func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagn return input, canonicalDiagnostics(diagnostics) } -func loadSettings[T any](defaults importDefaults, definition Definition[T], mode sourceVersionMode) (importDefaults, sequencePolicy, []Diagnostic) { +func loadSettings[T any](defaults ImportDefaults, definition Definition[T], mode sourceVersionMode) (ImportDefaults, sequencePolicy, []Diagnostic) { defaults = snapshotImportDefaults(defaults) var diagnostics []Diagnostic diagnostics = append(diagnostics, validateImportDefaults(defaults)...)