Construct placement directly across the facade
This commit is contained in:
@@ -204,7 +204,8 @@ Focused checks:
|
|||||||
|
|
||||||
Finding: **F07**, placement handoff. Owners: engine `destination.go`, `load.go`,
|
Finding: **F07**, placement handoff. Owners: engine `destination.go`, `load.go`,
|
||||||
`layered.go`, normalized input and placement consumers; root `lifecycle.go`,
|
`layered.go`, normalized input and placement consumers; root `lifecycle.go`,
|
||||||
`layered.go`, and placement fixtures. Status: **not started**.
|
`layered.go`, and placement fixtures. Status: **complete**. Validation passed:
|
||||||
|
`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Destination|Placement|Item|SourceVersion'`, `env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test . -run 'Placement|Destination|SourceInfo|SourceVersion|PublicFacadeBoundary|DependencyImportBoundary'`, `env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./...`, and `git diff --check`.
|
||||||
|
|
||||||
1. Rename the existing engine enum and its constants to `PlacementMode`,
|
1. Rename the existing engine enum and its constants to `PlacementMode`,
|
||||||
`PlacementModeMapping`, and `PlacementModeSequenceItem`, preserving underlying
|
`PlacementModeMapping`, and `PlacementModeSequenceItem`, preserving underlying
|
||||||
|
|||||||
@@ -11,26 +11,13 @@ var (
|
|||||||
errDestinationLimit = errors.New("configkit: destination exceeds document limits")
|
errDestinationLimit = errors.New("configkit: destination exceeds document limits")
|
||||||
)
|
)
|
||||||
|
|
||||||
type placementMode uint8
|
type PlacementMode uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
placementModeMapping placementMode = iota
|
PlacementModeMapping PlacementMode = iota
|
||||||
placementModeSequenceItem
|
PlacementModeSequenceItem
|
||||||
)
|
)
|
||||||
|
|
||||||
// InputWithPlacement copies the root-owned mode into a private engine input.
|
|
||||||
func InputWithPlacement(input Input, value uint8) Input {
|
|
||||||
input.placementMode = placementMode(value)
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionsWithPlacement copies root-owned controls into private engine options.
|
|
||||||
func OptionsWithPlacement[T any](options Options[T], destination Path, value uint8) Options[T] {
|
|
||||||
options.destination = destination.clone()
|
|
||||||
options.placementMode = placementMode(value)
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeDestination(path Path) (Path, error) {
|
func normalizeDestination(path Path) (Path, error) {
|
||||||
if !path.valid() {
|
if !path.valid() {
|
||||||
return Path{}, errInvalidDestination
|
return Path{}, errInvalidDestination
|
||||||
@@ -67,26 +54,26 @@ func invalidDestinationDiagnostic(destination Path) Diagnostic {
|
|||||||
// placeDocument checks all per-document bounds before allocating destination
|
// placeDocument checks all per-document bounds before allocating destination
|
||||||
// wrappers. Aggregate accounting remains the caller's responsibility.
|
// wrappers. Aggregate accounting remains the caller's responsibility.
|
||||||
func placeDocument(body *document.Node, destination Path, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
|
func placeDocument(body *document.Node, destination Path, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
|
||||||
return placeDocumentWithMode(body, destination, placementModeMapping, sourceRoot, maxDepth, maxNodes)
|
return placeDocumentWithMode(body, destination, PlacementModeMapping, sourceRoot, maxDepth, maxNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeDocumentWithMode places a source mapping beneath its literal destination.
|
// placeDocumentWithMode places a source mapping beneath its literal destination.
|
||||||
// Sequence-item placement adds one sequence containing the complete mapping.
|
// Sequence-item placement adds one sequence containing the complete mapping.
|
||||||
func placeDocumentWithMode(body *document.Node, destination Path, mode placementMode, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
|
func placeDocumentWithMode(body *document.Node, destination Path, mode PlacementMode, sourceRoot document.Location, maxDepth, maxNodes int) (Path, *document.Node, error) {
|
||||||
normalized, err := normalizeDestination(destination)
|
normalized, err := normalizeDestination(destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Path{}, nil, err
|
return Path{}, nil, err
|
||||||
}
|
}
|
||||||
if mode != placementModeMapping && mode != placementModeSequenceItem {
|
if mode != PlacementModeMapping && mode != PlacementModeSequenceItem {
|
||||||
return Path{}, nil, errInvalidDestination
|
return Path{}, nil, errInvalidDestination
|
||||||
}
|
}
|
||||||
if mode == placementModeSequenceItem && len(normalized.segments) == 0 {
|
if mode == PlacementModeSequenceItem && len(normalized.segments) == 0 {
|
||||||
return Path{}, nil, errInvalidDestination
|
return Path{}, nil, errInvalidDestination
|
||||||
}
|
}
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return normalized, nil, nil
|
return normalized, nil, nil
|
||||||
}
|
}
|
||||||
if mode == placementModeSequenceItem && body.Kind != document.Mapping {
|
if mode == PlacementModeSequenceItem && body.Kind != document.Mapping {
|
||||||
return normalized, nil, errInvalidDestination
|
return normalized, nil, errInvalidDestination
|
||||||
}
|
}
|
||||||
if !destinationWithinLimitsWithMode(body, normalized, mode, maxDepth, maxNodes) {
|
if !destinationWithinLimitsWithMode(body, normalized, mode, maxDepth, maxNodes) {
|
||||||
@@ -94,7 +81,7 @@ func placeDocumentWithMode(body *document.Node, destination Path, mode placement
|
|||||||
}
|
}
|
||||||
|
|
||||||
placed := body
|
placed := body
|
||||||
if mode == placementModeSequenceItem {
|
if mode == PlacementModeSequenceItem {
|
||||||
placed = &document.Node{
|
placed = &document.Node{
|
||||||
Kind: document.Sequence, Location: sourceRoot,
|
Kind: document.Sequence, Location: sourceRoot,
|
||||||
Sequence: []*document.Node{placed},
|
Sequence: []*document.Node{placed},
|
||||||
@@ -118,20 +105,20 @@ func placeDocumentWithMode(body *document.Node, destination Path, mode placement
|
|||||||
|
|
||||||
// destinationWithinLimits expects a normalized mapping-key destination.
|
// destinationWithinLimits expects a normalized mapping-key destination.
|
||||||
func destinationWithinLimits(body *document.Node, destination Path, maxDepth, maxNodes int) bool {
|
func destinationWithinLimits(body *document.Node, destination Path, maxDepth, maxNodes int) bool {
|
||||||
return destinationWithinLimitsWithMode(body, destination, placementModeMapping, maxDepth, maxNodes)
|
return destinationWithinLimitsWithMode(body, destination, PlacementModeMapping, maxDepth, maxNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode placementMode, maxDepth, maxNodes int) bool {
|
func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode PlacementMode, maxDepth, maxNodes int) bool {
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if maxDepth < 0 || maxNodes < 0 || !destination.valid() || (mode != placementModeMapping && mode != placementModeSequenceItem) {
|
if maxDepth < 0 || maxNodes < 0 || !destination.valid() || (mode != PlacementModeMapping && mode != PlacementModeSequenceItem) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
segments := len(destination.segments)
|
segments := len(destination.segments)
|
||||||
sequenceNodes := 0
|
sequenceNodes := 0
|
||||||
sequenceDepth := 0
|
sequenceDepth := 0
|
||||||
if mode == placementModeSequenceItem {
|
if mode == PlacementModeSequenceItem {
|
||||||
if segments == 0 {
|
if segments == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -153,9 +140,9 @@ func destinationWithinLimitsWithMode(body *document.Node, destination Path, mode
|
|||||||
return countInputNodes(body) <= maxNodes-wrapperNodes
|
return countInputNodes(body) <= maxNodes-wrapperNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
func destinationWrapperNodeCountForMode(destination Path, mode placementMode) int {
|
func destinationWrapperNodeCountForMode(destination Path, mode PlacementMode) int {
|
||||||
count := destinationWrapperNodeCount(destination)
|
count := destinationWrapperNodeCount(destination)
|
||||||
if mode == placementModeSequenceItem {
|
if mode == PlacementModeSequenceItem {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ id: parent
|
|||||||
childDestination := appendDestination(destination, Keys("children"))
|
childDestination := appendDestination(destination, Keys("children"))
|
||||||
otherDestination := Keys("other")
|
otherDestination := Keys("other")
|
||||||
wantNodes := parseNodes(mainPath) + parseNodes(fragmentPath) + countInputNodes(&document.Node{Kind: document.Mapping}) +
|
wantNodes := parseNodes(mainPath) + parseNodes(fragmentPath) + countInputNodes(&document.Node{Kind: document.Mapping}) +
|
||||||
destinationWrapperNodeCountForMode(destination, placementModeSequenceItem) +
|
destinationWrapperNodeCountForMode(destination, PlacementModeSequenceItem) +
|
||||||
destinationWrapperNodeCountForMode(childDestination, placementModeSequenceItem) +
|
destinationWrapperNodeCountForMode(childDestination, PlacementModeSequenceItem) +
|
||||||
destinationWrapperNodeCountForMode(otherDestination, placementModeSequenceItem)
|
destinationWrapperNodeCountForMode(otherDestination, PlacementModeSequenceItem)
|
||||||
limits := defaultImportLimits()
|
limits := defaultImportLimits()
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
@@ -108,9 +108,9 @@ id: parent
|
|||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta}
|
accounting := &importAccounting{nodes: limits.nodes - wantNodes + test.delta}
|
||||||
acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{
|
acquired, diagnostics := testAcquireUnderlayGroup(loadGroup{inputs: []loadInput{{
|
||||||
source: File(mainPath), imports: true, destination: destination, placementMode: placementModeSequenceItem,
|
source: File(mainPath), imports: true, destination: destination, placementMode: PlacementModeSequenceItem,
|
||||||
}, {
|
}, {
|
||||||
source: Mapping("other", map[string]any{"id": "other"}), destination: otherDestination, placementMode: placementModeSequenceItem,
|
source: Mapping("other", map[string]any{"id": "other"}), destination: otherDestination, placementMode: PlacementModeSequenceItem,
|
||||||
}}}, 1, nil, accounting)
|
}}}, 1, nil, accounting)
|
||||||
if test.wantError {
|
if test.wantError {
|
||||||
if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded {
|
if len(diagnostics) == 0 || diagnostics[0].Code != CodeSourceLimitExceeded {
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ func TestLayeredItemVersionProtectionTracksAssignedPeerIndexes(t *testing.T) {
|
|||||||
sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("catalog"), literalSequenceSelector("entries")}, identityFields: []string{"id"}}},
|
sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("catalog"), literalSequenceSelector("entries")}, identityFields: []string{"id"}}},
|
||||||
}
|
}
|
||||||
item := func(label string, value map[string]any) Input {
|
item := func(label string, value map[string]any) Input {
|
||||||
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}
|
return Input{Source: Mapping(label, value), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem}
|
||||||
}
|
}
|
||||||
options := func(inputs ...Input) LayeredOptions[map[string]any] {
|
options := func(inputs ...Input) LayeredOptions[map[string]any] {
|
||||||
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}
|
return LayeredOptions[map[string]any]{Definition: definition, SourceVersionMode: sourceVersionModeIfPresent, Underlays: []Underlay{{Inputs: inputs}}}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
loaded, err := LoadLayered[destinationItemConfig](
|
loaded, err := LoadLayered[destinationItemConfig](
|
||||||
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem},
|
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
||||||
testSecretBinding(),
|
testSecretBinding(),
|
||||||
LayeredOptions[destinationItemConfig]{Underlays: []Underlay{{Inputs: []Input{{
|
LayeredOptions[destinationItemConfig]{Underlays: []Underlay{{Inputs: []Input{{
|
||||||
Source: Mapping("empty-item", map[string]any{}), Destination: Keys("catalog", "entries"),
|
Source: Mapping("empty-item", map[string]any{}), Destination: Keys("catalog", "entries"),
|
||||||
placementMode: placementModeSequenceItem,
|
PlacementMode: PlacementModeSequenceItem,
|
||||||
}}}}},
|
}}}}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,7 +40,7 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
|
|||||||
t.Fatal("sequence index was treated as a literal mapping key during metadata lookup")
|
t.Fatal("sequence index was treated as a literal mapping key during metadata lookup")
|
||||||
}
|
}
|
||||||
sources := result.Sources()
|
sources := result.Sources()
|
||||||
if len(sources) != 1 || sources[0].placementMode != placementModeSequenceItem || sources[0].destinationUnresolved {
|
if len(sources) != 1 || sources[0].placementMode != PlacementModeSequenceItem || sources[0].destinationUnresolved {
|
||||||
t.Fatalf("source placement evidence = %#v", sources)
|
t.Fatalf("source placement evidence = %#v", sources)
|
||||||
}
|
}
|
||||||
if got := sources[0].Destination.String(); got != "/catalog/entries" {
|
if got := sources[0].Destination.String(); got != "/catalog/entries" {
|
||||||
@@ -48,7 +48,7 @@ func TestLayeredLoadPlacesCompletedMappingAsOneItem(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absent, err := LoadLayered[destinationItemConfig](
|
absent, err := LoadLayered[destinationItemConfig](
|
||||||
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem},
|
Input{Source: NoSource(), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
||||||
testSecretBinding(),
|
testSecretBinding(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -64,17 +64,17 @@ func TestLayeredItemPlacementPreflightsModeAndDestination(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
mode placementMode
|
mode PlacementMode
|
||||||
path Path
|
path Path
|
||||||
}{
|
}{
|
||||||
{name: "root item", mode: placementModeSequenceItem},
|
{name: "root item", mode: PlacementModeSequenceItem},
|
||||||
{name: "unknown mode", mode: placementMode(255), path: Keys("catalog")},
|
{name: "unknown mode", mode: PlacementMode(255), path: Keys("catalog")},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
reader := bytes.NewBufferString("value: unused\n")
|
reader := bytes.NewBufferString("value: unused\n")
|
||||||
_, err := LoadLayered[destinationItemConfig](
|
_, err := LoadLayered[destinationItemConfig](
|
||||||
Input{Source: Reader("reader", reader), Destination: test.path, placementMode: test.mode},
|
Input{Source: Reader("reader", reader), Destination: test.path, PlacementMode: test.mode},
|
||||||
testSecretBinding(),
|
testSecretBinding(),
|
||||||
)
|
)
|
||||||
if err == nil || reader.Len() == 0 {
|
if err == nil || reader.Len() == 0 {
|
||||||
@@ -93,7 +93,7 @@ func TestLoadOptionsPlaceOneMappingItem(t *testing.T) {
|
|||||||
|
|
||||||
loaded, err := Load[destinationItemConfig](
|
loaded, err := Load[destinationItemConfig](
|
||||||
Mapping("ordinary", map[string]any{}), testSecretBinding(),
|
Mapping("ordinary", map[string]any{}), testSecretBinding(),
|
||||||
Options[destinationItemConfig]{destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem},
|
Options[destinationItemConfig]{Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Load: %v", err)
|
t.Fatalf("Load: %v", err)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func TestMappingPlacementModeMatchesZeroValue(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
explicitPath, explicit, err := placeDocumentWithMode(body, destination, placementModeMapping, body.Location, 5, 32)
|
explicitPath, explicit, err := placeDocumentWithMode(body, destination, PlacementModeMapping, body.Location, 5, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
|
|||||||
root := document.Location{SourceID: "item", Line: 1, Column: 1}
|
root := document.Location{SourceID: "item", Line: 1, Column: 1}
|
||||||
body := &document.Node{Kind: document.Mapping, Location: root}
|
body := &document.Node{Kind: document.Mapping, Location: root}
|
||||||
destination := Keys("catalog", "entries")
|
destination := Keys("catalog", "entries")
|
||||||
placedPath, placed, err := placeDocumentWithMode(body, destination, placementModeSequenceItem, root, 3, 6)
|
placedPath, placed, err := placeDocumentWithMode(body, destination, PlacementModeSequenceItem, root, 3, 6)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -253,13 +253,13 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
|
|||||||
t.Fatal("synthetic sequence or authored mapping lost source coordinates")
|
t.Fatal("synthetic sequence or authored mapping lost source coordinates")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, err := placeDocumentWithMode(body, Path{}, placementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
if _, _, err := placeDocumentWithMode(body, Path{}, PlacementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
||||||
t.Fatalf("root item placement error = %v, want invalid destination", err)
|
t.Fatalf("root item placement error = %v, want invalid destination", err)
|
||||||
}
|
}
|
||||||
if _, _, err := placeDocumentWithMode(&document.Node{Kind: document.String}, destination, placementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
if _, _, err := placeDocumentWithMode(&document.Node{Kind: document.String}, destination, PlacementModeSequenceItem, root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
||||||
t.Fatalf("non-mapping item placement error = %v, want invalid destination", err)
|
t.Fatalf("non-mapping item placement error = %v, want invalid destination", err)
|
||||||
}
|
}
|
||||||
if _, _, err := placeDocumentWithMode(body, destination, placementMode(255), root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
if _, _, err := placeDocumentWithMode(body, destination, PlacementMode(255), root, 3, 6); !errors.Is(err, errInvalidDestination) {
|
||||||
t.Fatalf("unknown placement mode error = %v, want invalid destination", err)
|
t.Fatalf("unknown placement mode error = %v, want invalid destination", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ func TestPlaceDocumentSequenceItemAndMountedBounds(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, _, err := placeDocumentWithMode(body, destination, placementModeSequenceItem, root, test.depth, test.nodes)
|
_, _, err := placeDocumentWithMode(body, destination, PlacementModeSequenceItem, root, test.depth, test.nodes)
|
||||||
if test.wantAccept && err != nil {
|
if test.wantAccept && err != nil {
|
||||||
t.Fatalf("exact bounds rejected: %v", err)
|
t.Fatalf("exact bounds rejected: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ settings: {main: selected}
|
|||||||
importPath := writeImportFile(t, root, "fragments/extra.yml", "settings: {imported: value}\n")
|
importPath := writeImportFile(t, root, "fragments/extra.yml", "settings: {imported: value}\n")
|
||||||
search := SearchConfig("app", SearchDirectory(root))
|
search := SearchConfig("app", SearchDirectory(root))
|
||||||
options := Options[discoveryLoadConfig]{
|
options := Options[discoveryLoadConfig]{
|
||||||
Imports: true, Filesystem: &FilesystemContext{Root: root}, destination: Keys("service"),
|
Imports: true, Filesystem: &FilesystemContext{Root: root}, Destination: Keys("service"),
|
||||||
}
|
}
|
||||||
searched, err := testLoad(search, options)
|
searched, err := testLoad(search, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -172,7 +172,7 @@ func TestSearchProbeFailureRetainsFileEvidence(t *testing.T) {
|
|||||||
load func(Source) (*Loaded[discoveryLoadConfig], error)
|
load func(Source) (*Loaded[discoveryLoadConfig], error)
|
||||||
}{
|
}{
|
||||||
{"ordinary", "source-1", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
{"ordinary", "source-1", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
||||||
return Load(source, testSecretBinding(), Options[discoveryLoadConfig]{destination: Keys("settings")})
|
return Load(source, testSecretBinding(), Options[discoveryLoadConfig]{Destination: Keys("settings")})
|
||||||
}},
|
}},
|
||||||
{"underlay after acquired sources", "source-3", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
{"underlay after acquired sources", "source-3", func(source Source) (*Loaded[discoveryLoadConfig], error) {
|
||||||
return LoadLayered(Input{Source: File(main)}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{
|
return LoadLayered(Input{Source: File(main)}, testSecretBinding(), LayeredOptions[discoveryLoadConfig]{
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func acquireImportedWithAccess(request importAcquisitionRequest, policy *acquisi
|
|||||||
}
|
}
|
||||||
mainInfo := cloneSourceInfo(*main.source)
|
mainInfo := cloneSourceInfo(*main.source)
|
||||||
mainInfo.Destination = destination.clone()
|
mainInfo.Destination = destination.clone()
|
||||||
mainIsItem := main.source.placementMode == placementModeSequenceItem
|
mainIsItem := main.source.placementMode == PlacementModeSequenceItem
|
||||||
if mainPath != "" {
|
if mainPath != "" {
|
||||||
mainInfo.Path = mainPath
|
mainInfo.Path = mainPath
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ func (s *importTraversal) walkDeclarations(basePath string, declarations []impor
|
|||||||
if len(diagnostics) != 0 {
|
if len(diagnostics) != 0 {
|
||||||
return diagnostics
|
return diagnostics
|
||||||
}
|
}
|
||||||
if policy.placement == placementModeSequenceItem && len(appendDestination(parentDestination, policy.destination).segments) == 0 {
|
if policy.placement == PlacementModeSequenceItem && len(appendDestination(parentDestination, policy.destination).segments) == 0 {
|
||||||
site := spec.fieldSites["placement"]
|
site := spec.fieldSites["placement"]
|
||||||
return []Diagnostic{{Code: CodeImportInvalid, Severity: SeverityError, Path: site.path.clone(), Message: "sequence-item placement requires a nonempty destination", Primary: cloneLocation(&site.location)}}
|
return []Diagnostic{{Code: CodeImportInvalid, Severity: SeverityError, Path: site.path.clone(), Message: "sequence-item placement requires a nonempty destination", Primary: cloneLocation(&site.location)}}
|
||||||
}
|
}
|
||||||
@@ -305,8 +305,8 @@ func (s *importTraversal) admitImportSource(canonical string, identity resourceI
|
|||||||
label = s.access.label + "/" + canonical
|
label = s.access.label + "/" + canonical
|
||||||
}
|
}
|
||||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||||
if policy.placement == placementModeSequenceItem {
|
if policy.placement == PlacementModeSequenceItem {
|
||||||
info.placementMode = placementModeSequenceItem
|
info.placementMode = PlacementModeSequenceItem
|
||||||
}
|
}
|
||||||
// A source's own item placement still has an exact sequence-container
|
// A source's own item placement still has an exact sequence-container
|
||||||
// destination. Only an unresolved enclosing item index makes the path a
|
// destination. Only an unresolved enclosing item index makes the path a
|
||||||
@@ -433,17 +433,17 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
|
|||||||
admitted := admission.source
|
admitted := admission.source
|
||||||
|
|
||||||
placementDestination := destination
|
placementDestination := destination
|
||||||
mode := placementModeMapping
|
mode := PlacementModeMapping
|
||||||
if policy.placement == placementModeSequenceItem {
|
if policy.placement == PlacementModeSequenceItem {
|
||||||
mode = placementModeSequenceItem
|
mode = PlacementModeSequenceItem
|
||||||
}
|
}
|
||||||
role := importRoleMapping
|
role := importRoleMapping
|
||||||
if mode == placementModeSequenceItem {
|
if mode == PlacementModeSequenceItem {
|
||||||
role = importRoleItem
|
role = importRoleItem
|
||||||
}
|
}
|
||||||
s.importGraph.addNode(importGraphNode{key: admitted.nodeKey, canonical: admitted.canonical, sourceID: admitted.source.ID, role: role})
|
s.importGraph.addNode(importGraphNode{key: admitted.nodeKey, canonical: admitted.canonical, sourceID: admitted.source.ID, role: role})
|
||||||
s.importGraph.addEdge(importerKey, admitted.nodeKey, location)
|
s.importGraph.addEdge(importerKey, admitted.nodeKey, location)
|
||||||
if mode == placementModeSequenceItem {
|
if mode == PlacementModeSequenceItem {
|
||||||
route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey}
|
route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey}
|
||||||
s.active[admitted.identity] = len(s.stack)
|
s.active[admitted.identity] = len(s.stack)
|
||||||
s.tier.visited[admitted.identity] = route
|
s.tier.visited[admitted.identity] = route
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ func projectAcquiredDestinations(composed *composedDocument, scope acquisitionSc
|
|||||||
itemIndex = projected
|
itemIndex = projected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if contribution.source.placementMode == placementModeSequenceItem {
|
if contribution.source.placementMode == PlacementModeSequenceItem {
|
||||||
container := appendDestination(contributionPrefix, contribution.destination)
|
container := appendDestination(contributionPrefix, contribution.destination)
|
||||||
contribution.source.Destination = container.clone()
|
contribution.source.Destination = container.clone()
|
||||||
contribution.source.destinationUnresolved = false
|
contribution.source.destinationUnresolved = false
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPoli
|
|||||||
}
|
}
|
||||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
|
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
|
||||||
projectNestedEvidence(completed, localScope)
|
projectNestedEvidence(completed, localScope)
|
||||||
_, placed, err := placeDocumentWithMode(completed.root, main.destination, placementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
_, placed, err := placeDocumentWithMode(completed.root, main.destination, PlacementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
|
||||||
}
|
}
|
||||||
if !main.placementCharged {
|
if !main.placementCharged {
|
||||||
placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem)
|
placementNodes := destinationWrapperNodeCountForMode(main.destination, PlacementModeSequenceItem)
|
||||||
if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes {
|
if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes {
|
||||||
return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)}
|
return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)}
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits,
|
|||||||
if contribution.pendingItem == nil {
|
if contribution.pendingItem == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem)
|
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, PlacementModeSequenceItem)
|
||||||
if accounting.nodes+placementNodes > limits.nodes {
|
if accounting.nodes+placementNodes > limits.nodes {
|
||||||
diagnostic := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)
|
diagnostic := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)
|
||||||
return &diagnostic
|
return &diagnostic
|
||||||
@@ -228,11 +228,11 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
|
|||||||
// item has its effective coordinates, not against its local field names.
|
// item has its effective coordinates, not against its local field names.
|
||||||
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)}
|
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)}
|
||||||
projectNestedEvidence(completed, *itemScope)
|
projectNestedEvidence(completed, *itemScope)
|
||||||
_, placed, err := placeDocumentWithMode(completed.root, contribution.destination, placementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
_, placed, err := placeDocumentWithMode(completed.root, contribution.destination, PlacementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)}
|
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)}
|
||||||
}
|
}
|
||||||
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem)
|
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, PlacementModeSequenceItem)
|
||||||
if !contribution.placementCharged {
|
if !contribution.placementCharged {
|
||||||
if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes {
|
if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes {
|
||||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)}
|
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type importDeclarationSpec struct {
|
|||||||
depth int
|
depth int
|
||||||
depthSet bool
|
depthSet bool
|
||||||
destination Path
|
destination Path
|
||||||
placement placementMode
|
placement PlacementMode
|
||||||
fieldSites map[string]importFieldSite
|
fieldSites map[string]importFieldSite
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ type resolvedImportPolicy struct {
|
|||||||
depthSet bool
|
depthSet bool
|
||||||
exclude []string
|
exclude []string
|
||||||
destination Path
|
destination Path
|
||||||
placement placementMode
|
placement PlacementMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotImportDefaults(value ImportDefaults) ImportDefaults {
|
func snapshotImportDefaults(value ImportDefaults) ImportDefaults {
|
||||||
@@ -173,7 +173,7 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo
|
|||||||
spec := importDeclarationSpec{
|
spec := importDeclarationSpec{
|
||||||
path: declarationPath.clone(),
|
path: declarationPath.clone(),
|
||||||
location: publicLocation(node.Location),
|
location: publicLocation(node.Location),
|
||||||
placement: placementModeMapping,
|
placement: PlacementModeMapping,
|
||||||
fieldSites: make(map[string]importFieldSite, len(node.Mapping)),
|
fieldSites: make(map[string]importFieldSite, len(node.Mapping)),
|
||||||
}
|
}
|
||||||
selectors := 0
|
selectors := 0
|
||||||
@@ -281,19 +281,19 @@ func parseImportWildcardAssignment(spec *importDeclarationSpec, field string, no
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseImportPlacement(node *document.Node, fieldPath Path) (placementMode, *Diagnostic) {
|
func parseImportPlacement(node *document.Node, fieldPath Path) (PlacementMode, *Diagnostic) {
|
||||||
if node.Kind != document.String {
|
if node.Kind != document.String {
|
||||||
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
|
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
|
||||||
return placementModeMapping, &diagnostic
|
return PlacementModeMapping, &diagnostic
|
||||||
}
|
}
|
||||||
switch node.Text {
|
switch node.Text {
|
||||||
case "mapping":
|
case "mapping":
|
||||||
return placementModeMapping, nil
|
return PlacementModeMapping, nil
|
||||||
case "sequence-item":
|
case "sequence-item":
|
||||||
return placementModeSequenceItem, nil
|
return PlacementModeSequenceItem, nil
|
||||||
default:
|
default:
|
||||||
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
|
diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node)
|
||||||
return placementModeMapping, &diagnostic
|
return PlacementModeMapping, &diagnostic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func TestParseImportDeclarationSpecNormalizesPresenceAndPlacement(t *testing.T)
|
|||||||
if first.selector != importSelectorPath || len(first.paths) != 2 || first.paths[0] != "one.yaml" || first.paths[1] != "two.yaml" {
|
if first.selector != importSelectorPath || len(first.paths) != 2 || first.paths[0] != "one.yaml" || first.paths[1] != "two.yaml" {
|
||||||
t.Fatalf("path selector = %#v", first)
|
t.Fatalf("path selector = %#v", first)
|
||||||
}
|
}
|
||||||
if first.required == nil || *first.required || first.destination.String() != "/services" || first.placement != placementModeSequenceItem {
|
if first.required == nil || *first.required || first.destination.String() != "/services" || first.placement != PlacementModeSequenceItem {
|
||||||
t.Fatalf("explicit declaration values = %#v", first)
|
t.Fatalf("explicit declaration values = %#v", first)
|
||||||
}
|
}
|
||||||
if first.path.String() != "/configkit/imports/0" || first.fieldSites["required"].path.String() != "/configkit/imports/0/required" {
|
if first.path.String() != "/configkit/imports/0" || first.fieldSites["required"].path.String() != "/configkit/imports/0/required" {
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ label: helper
|
|||||||
loaded, err := LoadLayered(
|
loaded, err := LoadLayered(
|
||||||
Input{Source: NoSource(), Imports: true}, testSecretBinding(),
|
Input{Source: NoSource(), Imports: true}, testSecretBinding(),
|
||||||
LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{
|
LayeredOptions[config]{Underlays: []Underlay{{Inputs: []Input{{
|
||||||
Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
Source: File(itemPath), Imports: true, Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
|
||||||
}}}}},
|
}}}}},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -503,7 +503,7 @@ func TestLoadItemGlobKeepsDefaultModeIndependentAndOptionalMissesAbsent(t *testi
|
|||||||
t.Fatalf("config = %#v", result.Config)
|
t.Fatalf("config = %#v", result.Config)
|
||||||
}
|
}
|
||||||
sources := result.Sources()
|
sources := result.Sources()
|
||||||
if len(sources) != 3 || sources[1].Path != canonicalTestPath(t, emptyPath) || sources[1].Destination.String() != "/items" || sources[1].placementMode != placementModeSequenceItem || sources[2].placementMode != placementModeMapping || sources[2].Destination.String() != "/other" {
|
if len(sources) != 3 || sources[1].Path != canonicalTestPath(t, emptyPath) || sources[1].Destination.String() != "/items" || sources[1].placementMode != PlacementModeSequenceItem || sources[2].placementMode != PlacementModeMapping || sources[2].Destination.String() != "/other" {
|
||||||
t.Fatalf("source placement evidence = %#v", sources)
|
t.Fatalf("source placement evidence = %#v", sources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -521,7 +521,7 @@ func TestLoadMainItemPlacementKeepsItsHelpersInTheItem(t *testing.T) {
|
|||||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: helper.yml\nid: main\n")
|
mainPath := writeImportFile(t, directory, "main.yml", "configkit:\n imports:\n - path: helper.yml\nid: main\n")
|
||||||
helperPath := writeImportFile(t, directory, "helper.yml", "value: helper\n")
|
helperPath := writeImportFile(t, directory, "helper.yml", "value: helper\n")
|
||||||
result, err := testLoad[config](File(mainPath), Options[config]{
|
result, err := testLoad[config](File(mainPath), Options[config]{
|
||||||
Imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem,
|
Imports: true, Destination: Keys("items"), PlacementMode: PlacementModeSequenceItem,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("load main item: %v", err)
|
t.Fatalf("load main item: %v", err)
|
||||||
@@ -530,7 +530,7 @@ func TestLoadMainItemPlacementKeepsItsHelpersInTheItem(t *testing.T) {
|
|||||||
t.Fatalf("main item = %#v", result.Config.Items)
|
t.Fatalf("main item = %#v", result.Config.Items)
|
||||||
}
|
}
|
||||||
sources := result.Sources()
|
sources := result.Sources()
|
||||||
if len(sources) != 2 || sources[0].Destination.String() != "/items" || sources[0].placementMode != placementModeSequenceItem || sources[1].Destination.String() != "/items/0" || sources[1].placementMode != placementModeMapping {
|
if len(sources) != 2 || sources[0].Destination.String() != "/items" || sources[0].placementMode != PlacementModeSequenceItem || sources[1].Destination.String() != "/items/0" || sources[1].placementMode != PlacementModeMapping {
|
||||||
t.Fatalf("main item sources = %#v", sources)
|
t.Fatalf("main item sources = %#v", sources)
|
||||||
}
|
}
|
||||||
if sources[1].Path != canonicalTestPath(t, helperPath) {
|
if sources[1].Path != canonicalTestPath(t, helperPath) {
|
||||||
@@ -549,7 +549,7 @@ func TestProjectAcquiredDestinationsUsesMappedSequenceItemIndex(t *testing.T) {
|
|||||||
items: []sequenceItemProjection{{inputIndex: 0, outputIndex: 2}},
|
items: []sequenceItemProjection{{inputIndex: 0, outputIndex: 2}},
|
||||||
}}}
|
}}}
|
||||||
scope := acquisitionScope{contributions: []acquiredContribution{{
|
scope := acquisitionScope{contributions: []acquiredContribution{{
|
||||||
source: SourceInfo{ID: "outer-source", placementMode: placementModeSequenceItem},
|
source: SourceInfo{ID: "outer-source", placementMode: PlacementModeSequenceItem},
|
||||||
destination: Keys("records"), nested: child,
|
destination: Keys("records"), nested: child,
|
||||||
}}}
|
}}}
|
||||||
sources := []SourceInfo{{ID: "outer-source"}, {ID: "nested-source"}}
|
sources := []SourceInfo{{ID: "outer-source"}, {ID: "nested-source"}}
|
||||||
@@ -760,7 +760,7 @@ func TestParseImportPlacementDefaultsPerDeclaration(t *testing.T) {
|
|||||||
- glob: records/*.yml
|
- glob: records/*.yml
|
||||||
placement: sequence-item
|
placement: sequence-item
|
||||||
`)
|
`)
|
||||||
if len(declarations) != 2 || declarations[0].placement != placementModeMapping || declarations[1].placement != placementModeSequenceItem {
|
if len(declarations) != 2 || declarations[0].placement != PlacementModeMapping || declarations[1].placement != PlacementModeSequenceItem {
|
||||||
t.Fatalf("declaration placements = %#v", declarations)
|
t.Fatalf("declaration placements = %#v", declarations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1107,7 +1107,7 @@ id: item
|
|||||||
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: helper.yml}]}\nid: main\n")
|
mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: helper.yml}]}\nid: main\n")
|
||||||
writeImportFile(t, directory, "helper.yml", "id: helper\n")
|
writeImportFile(t, directory, "helper.yml", "id: helper\n")
|
||||||
options := Options[importTestConfig]{
|
options := Options[importTestConfig]{
|
||||||
Imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem,
|
Imports: true, Destination: Keys("items"), PlacementMode: PlacementModeSequenceItem,
|
||||||
}
|
}
|
||||||
loadError := requireLoadCode(t, File(mainPath), options, CodeComposeConflict)
|
loadError := requireLoadCode(t, File(mainPath), options, CodeComposeConflict)
|
||||||
diagnostic := loadError.Diagnostics()[0]
|
diagnostic := loadError.Diagnostics()[0]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type Input struct {
|
|||||||
Imports bool
|
Imports bool
|
||||||
Filesystem *FilesystemContext
|
Filesystem *FilesystemContext
|
||||||
Destination Path
|
Destination Path
|
||||||
placementMode placementMode
|
PlacementMode PlacementMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underlay contains strictly composed contributions at one precedence tier.
|
// Underlay contains strictly composed contributions at one precedence tier.
|
||||||
@@ -38,7 +38,7 @@ func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOp
|
|||||||
request := loadRequest[T]{
|
request := loadRequest[T]{
|
||||||
main: loadInput{
|
main: loadInput{
|
||||||
source: main.Source, imports: main.Imports, filesystem: main.Filesystem,
|
source: main.Source, imports: main.Imports, filesystem: main.Filesystem,
|
||||||
destination: main.Destination.clone(), placementMode: main.placementMode,
|
destination: main.Destination.clone(), placementMode: main.PlacementMode,
|
||||||
},
|
},
|
||||||
definition: options.Definition, sourceVersionMode: options.SourceVersionMode,
|
definition: options.Definition, sourceVersionMode: options.SourceVersionMode,
|
||||||
importDefaults: options.ImportDefaults,
|
importDefaults: options.ImportDefaults,
|
||||||
@@ -52,7 +52,7 @@ func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOp
|
|||||||
for inputIndex, input := range underlay.Inputs {
|
for inputIndex, input := range underlay.Inputs {
|
||||||
request.underlays[index].inputs[inputIndex] = loadInput{
|
request.underlays[index].inputs[inputIndex] = loadInput{
|
||||||
source: input.Source, imports: input.Imports, filesystem: input.Filesystem,
|
source: input.Source, imports: input.Imports, filesystem: input.Filesystem,
|
||||||
destination: input.Destination.clone(), placementMode: input.placementMode,
|
destination: input.Destination.clone(), placementMode: input.PlacementMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ type Options[T any] struct {
|
|||||||
Overrides []Override
|
Overrides []Override
|
||||||
SourceVersionMode sourceVersionMode
|
SourceVersionMode sourceVersionMode
|
||||||
ImportDefaults ImportDefaults
|
ImportDefaults ImportDefaults
|
||||||
destination Path
|
Destination Path
|
||||||
placementMode placementMode
|
PlacementMode PlacementMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesystemContext supplies filesystem roots for imports and overlays. For an
|
// FilesystemContext supplies filesystem roots for imports and overlays. For an
|
||||||
@@ -161,7 +161,7 @@ func loadWithSecretLookupLimit[T any](source Source, secrets SecretBinding, look
|
|||||||
}
|
}
|
||||||
input, diagnostics := normalizeInput(loadInput{
|
input, diagnostics := normalizeInput(loadInput{
|
||||||
source: source, imports: options.Imports, filesystem: options.Filesystem,
|
source: source, imports: options.Imports, filesystem: options.Filesystem,
|
||||||
destination: options.destination.clone(), placementMode: options.placementMode,
|
destination: options.Destination.clone(), placementMode: options.PlacementMode,
|
||||||
}, options.Overlay)
|
}, 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, settingsDiagnostics...)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type loadInput struct {
|
|||||||
imports bool
|
imports bool
|
||||||
filesystem *FilesystemContext
|
filesystem *FilesystemContext
|
||||||
destination Path
|
destination Path
|
||||||
placementMode placementMode
|
placementMode PlacementMode
|
||||||
}
|
}
|
||||||
|
|
||||||
type loadGroup struct{ inputs []loadInput }
|
type loadGroup struct{ inputs []loadInput }
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ func TestUnderlayItemConflictUsesScheduledGroupIndex(t *testing.T) {
|
|||||||
request := loadRequest[map[string]any]{
|
request := loadRequest[map[string]any]{
|
||||||
main: loadInput{source: NoSource()},
|
main: loadInput{source: NoSource()},
|
||||||
underlays: []loadGroup{{inputs: []loadInput{
|
underlays: []loadGroup{{inputs: []loadInput{
|
||||||
{source: File(zItem), destination: Keys("items"), placementMode: placementModeSequenceItem},
|
{source: File(zItem), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
|
||||||
{source: File(aItem), destination: Keys("items"), placementMode: placementModeSequenceItem},
|
{source: File(aItem), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
|
||||||
}}},
|
}}},
|
||||||
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
|
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
|
||||||
}
|
}
|
||||||
@@ -205,8 +205,8 @@ func TestUnderlayItemHelperConflictUsesScheduledGroupIndex(t *testing.T) {
|
|||||||
request := loadRequest[map[string]any]{
|
request := loadRequest[map[string]any]{
|
||||||
main: loadInput{source: NoSource()},
|
main: loadInput{source: NoSource()},
|
||||||
underlays: []loadGroup{{inputs: []loadInput{
|
underlays: []loadGroup{{inputs: []loadInput{
|
||||||
{source: File(zBad), imports: true, destination: Keys("items"), placementMode: placementModeSequenceItem},
|
{source: File(zBad), imports: true, destination: Keys("items"), placementMode: PlacementModeSequenceItem},
|
||||||
{source: File(aGood), destination: Keys("items"), placementMode: placementModeSequenceItem},
|
{source: File(aGood), destination: Keys("items"), placementMode: PlacementModeSequenceItem},
|
||||||
}}},
|
}}},
|
||||||
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
|
definition: Definition[map[string]any]{sequenceIdentityRegistrations: []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("items")}, identityFields: []string{"id"}}}},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package engine
|
|||||||
|
|
||||||
func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagnostic) {
|
func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagnostic) {
|
||||||
var diagnostics []Diagnostic
|
var diagnostics []Diagnostic
|
||||||
if input.placementMode != placementModeMapping && input.placementMode != placementModeSequenceItem {
|
if input.placementMode != PlacementModeMapping && input.placementMode != PlacementModeSequenceItem {
|
||||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "input placement mode is invalid"))
|
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "input placement mode is invalid"))
|
||||||
}
|
}
|
||||||
if normalized, err := normalizeDestination(input.destination); err != nil {
|
if normalized, err := normalizeDestination(input.destination); err != nil {
|
||||||
@@ -10,7 +10,7 @@ func normalizeInput(input loadInput, ordinaryOverlay string) (loadInput, []Diagn
|
|||||||
} else {
|
} else {
|
||||||
input.destination = normalized
|
input.destination = normalized
|
||||||
}
|
}
|
||||||
if input.placementMode == placementModeSequenceItem && len(input.destination.segments) == 0 {
|
if input.placementMode == PlacementModeSequenceItem && len(input.destination.segments) == 0 {
|
||||||
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "sequence-item placement requires a nonempty destination"))
|
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "sequence-item placement requires a nonempty destination"))
|
||||||
}
|
}
|
||||||
if input.filesystem != nil {
|
if input.filesystem != nil {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ type SourceInfo struct {
|
|||||||
VersionCheck *SourceVersionCheck
|
VersionCheck *SourceVersionCheck
|
||||||
importSelections []importSelectionEvidence
|
importSelections []importSelectionEvidence
|
||||||
namespace *namespaceToken
|
namespace *namespaceToken
|
||||||
placementMode placementMode
|
placementMode PlacementMode
|
||||||
destinationUnresolved bool
|
destinationUnresolved bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func TestLoadingClosesNativeFilesAcrossPipelineOutcomes(t *testing.T) {
|
|||||||
child: "configkit: {imports: [{path: helper.yml}]}\nid: child\nvalue: child\n",
|
child: "configkit: {imports: [{path: helper.yml}]}\nid: child\nvalue: child\n",
|
||||||
helper: "value: helper\n",
|
helper: "value: helper\n",
|
||||||
options: Options[config]{
|
options: Options[config]{
|
||||||
Imports: true, destination: Keys("groups"), placementMode: placementModeSequenceItem,
|
Imports: true, Destination: Keys("groups"), PlacementMode: PlacementModeSequenceItem,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func loadSelectedOverlay(request selectedOverlayRequest, access *filesystemAcces
|
|||||||
var failures []Diagnostic
|
var failures []Diagnostic
|
||||||
if versionState != nil {
|
if versionState != nil {
|
||||||
processed, failures = versionState.processWithDirectiveContext(
|
processed, failures = versionState.processWithDirectiveContext(
|
||||||
overlayRoot, sourceVersionContribution, Path{}, sourceID, placementModeMapping, rootDirectiveContextOverlay,
|
overlayRoot, sourceVersionContribution, Path{}, sourceID, PlacementModeMapping, rootDirectiveContextOverlay,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
processed = processSourceVersionWithDirectiveContext(
|
processed = processSourceVersionWithDirectiveContext(
|
||||||
|
|||||||
@@ -117,19 +117,19 @@ func (evidence *sourceVersionEvidence) sensitivityPaths() []Path {
|
|||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement placementMode) (sourceVersionProcessingResult, []Diagnostic) {
|
func (state *sourceVersionState) process(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement PlacementMode) (sourceVersionProcessingResult, []Diagnostic) {
|
||||||
if state == nil || state.mode == sourceVersionModeDefault {
|
if state == nil || state.mode == sourceVersionModeDefault {
|
||||||
return sourceVersionProcessingResult{}, nil
|
return sourceVersionProcessingResult{}, nil
|
||||||
}
|
}
|
||||||
return state.processWithDirectiveContext(root, role, destination, sourceID, placement, rootDirectiveContextImportsOnly)
|
return state.processWithDirectiveContext(root, role, destination, sourceID, placement, rootDirectiveContextImportsOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *sourceVersionState) processWithDirectiveContext(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement placementMode, directiveContext rootDirectiveContext) (sourceVersionProcessingResult, []Diagnostic) {
|
func (state *sourceVersionState) processWithDirectiveContext(root *document.Node, role sourceVersionRole, destination Path, sourceID string, placement PlacementMode, directiveContext rootDirectiveContext) (sourceVersionProcessingResult, []Diagnostic) {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return sourceVersionProcessingResult{}, nil
|
return sourceVersionProcessingResult{}, nil
|
||||||
}
|
}
|
||||||
projectedDestination := destination.clone()
|
projectedDestination := destination.clone()
|
||||||
if placement == placementModeSequenceItem {
|
if placement == PlacementModeSequenceItem {
|
||||||
projectedDestination = projectedDestination.Index(0)
|
projectedDestination = projectedDestination.Index(0)
|
||||||
}
|
}
|
||||||
result := processSourceVersionWithDirectiveContext(root, state.localRule, role, projectedDestination, state.mode, directiveContext)
|
result := processSourceVersionWithDirectiveContext(root, state.localRule, role, projectedDestination, state.mode, directiveContext)
|
||||||
@@ -158,10 +158,10 @@ type sourceVersionProcessingResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule {
|
func effectiveSourceVersionRule(local *VersionRule, destination Path, mode sourceVersionMode) *VersionRule {
|
||||||
return effectiveSourceVersionRuleForPlacement(local, destination, mode, placementModeMapping)
|
return effectiveSourceVersionRuleForPlacement(local, destination, mode, PlacementModeMapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement placementMode) *VersionRule {
|
func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path, mode sourceVersionMode, placement PlacementMode) *VersionRule {
|
||||||
if local == nil {
|
if local == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ func effectiveSourceVersionRuleForPlacement(local *VersionRule, destination Path
|
|||||||
}
|
}
|
||||||
effective := *local
|
effective := *local
|
||||||
prefix := destination.clone()
|
prefix := destination.clone()
|
||||||
if placement == placementModeSequenceItem {
|
if placement == PlacementModeSequenceItem {
|
||||||
prefix = prefix.Index(0)
|
prefix = prefix.Index(0)
|
||||||
}
|
}
|
||||||
effective.Path = Path{
|
effective.Path = Path{
|
||||||
|
|||||||
@@ -142,11 +142,11 @@ func TestFragmentVersionClaimUsesAssignedSequenceProjection(t *testing.T) {
|
|||||||
t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node)
|
t.Fatalf("destructive sequence ancestor claim = %#v, want mapping node", node)
|
||||||
}
|
}
|
||||||
|
|
||||||
effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, placementModeSequenceItem)
|
effective := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeRequired, PlacementModeSequenceItem)
|
||||||
if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want {
|
if got, want := effective.Path.String(), "/catalog/entries/0/version"; got != want {
|
||||||
t.Fatalf("item effective version path = %q, want %q", got, want)
|
t.Fatalf("item effective version path = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, placementModeSequenceItem)
|
defaultRule := effectiveSourceVersionRuleForPlacement(&VersionRule{Path: Keys("version")}, Keys("catalog", "entries"), sourceVersionModeDefault, PlacementModeSequenceItem)
|
||||||
if got, want := defaultRule.Path.String(), "/version"; got != want {
|
if got, want := defaultRule.Path.String(), "/version"; got != want {
|
||||||
t.Fatalf("default effective version path = %q, want %q", got, want)
|
t.Fatalf("default effective version path = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
@@ -628,7 +628,7 @@ func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *t
|
|||||||
load: func(definition Definition[map[string]any]) error {
|
load: func(definition Definition[map[string]any]) error {
|
||||||
_, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{
|
_, err := Load(Mapping("main", map[string]any{"version": "v1", "name": "ordinary"}), testSecretBinding(), Options[map[string]any]{
|
||||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||||
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
|
||||||
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
@@ -637,7 +637,7 @@ func TestSourceVersionItemPlacementKeepsLocalChecksAndProtectsProjectedPath(t *t
|
|||||||
{
|
{
|
||||||
name: "layered",
|
name: "layered",
|
||||||
load: func(definition Definition[map[string]any]) error {
|
load: func(definition Definition[map[string]any]) error {
|
||||||
_, err := LoadLayered(Input{Source: Mapping("main", map[string]any{"version": "v1", "name": "layered"}), Destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem}, testSecretBinding(), LayeredOptions[map[string]any]{
|
_, err := LoadLayered(Input{Source: Mapping("main", map[string]any{"version": "v1", "name": "layered"}), Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem}, testSecretBinding(), LayeredOptions[map[string]any]{
|
||||||
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
Definition: definition, SourceVersionMode: sourceVersionModeRequired,
|
||||||
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
Overrides: []Override{{Path: Keys("catalog", "entries"), Value: []any{}, Flag: "--entries"}},
|
||||||
})
|
})
|
||||||
@@ -682,7 +682,7 @@ func TestSourceVersionItemEvidenceUsesProjectedSensitivityPath(t *testing.T) {
|
|||||||
Secret: func(path Path) bool { return path.String() == secretPath.String() },
|
Secret: func(path Path) bool { return path.String() == secretPath.String() },
|
||||||
},
|
},
|
||||||
SourceVersionMode: sourceVersionModeRequired,
|
SourceVersionMode: sourceVersionModeRequired,
|
||||||
destination: Keys("catalog", "entries"), placementMode: placementModeSequenceItem,
|
Destination: Keys("catalog", "entries"), PlacementMode: PlacementModeSequenceItem,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("item source-version load: %v", err)
|
t.Fatalf("item source-version load: %v", err)
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ func toEngineInput(input Input) engine.Input {
|
|||||||
result := engine.Input{
|
result := engine.Input{
|
||||||
Source: input.Source.toEngine(), Imports: input.Imports,
|
Source: input.Source.toEngine(), Imports: input.Imports,
|
||||||
Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination),
|
Filesystem: toEngineFilesystemContext(input.Filesystem), Destination: toEnginePath(input.Destination),
|
||||||
|
PlacementMode: engine.PlacementMode(input.PlacementMode),
|
||||||
}
|
}
|
||||||
return engine.InputWithPlacement(result, uint8(input.PlacementMode))
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func toEngineLayeredOptions[T any](options LayeredOptions[T]) engine.LayeredOptions[T] {
|
func toEngineLayeredOptions[T any](options LayeredOptions[T]) engine.LayeredOptions[T] {
|
||||||
|
|||||||
@@ -318,8 +318,9 @@ func toEngineOptions[T any](options Options[T]) engine.Options[T] {
|
|||||||
Overlay: options.Overlay,
|
Overlay: options.Overlay,
|
||||||
SourceVersionMode: engine.SourceVersionModeFromFacade(uint8(options.SourceVersionMode)),
|
SourceVersionMode: engine.SourceVersionModeFromFacade(uint8(options.SourceVersionMode)),
|
||||||
ImportDefaults: toEngineImportDefaults(options.ImportDefaults),
|
ImportDefaults: toEngineImportDefaults(options.ImportDefaults),
|
||||||
|
Destination: toEnginePath(options.Destination.clone()),
|
||||||
|
PlacementMode: engine.PlacementMode(options.PlacementMode),
|
||||||
}
|
}
|
||||||
result = engine.OptionsWithPlacement(result, toEnginePath(options.Destination.clone()), uint8(options.PlacementMode))
|
|
||||||
if options.Filesystem != nil {
|
if options.Filesystem != nil {
|
||||||
result.Filesystem = toEngineFilesystemContext(options.Filesystem)
|
result.Filesystem = toEngineFilesystemContext(options.Filesystem)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ func TestOverlayPublicValues(t *testing.T) {
|
|||||||
func TestSourceInfoDestinationConversion(t *testing.T) {
|
func TestSourceInfoDestinationConversion(t *testing.T) {
|
||||||
destination := Keys("collections", "a/b", "0")
|
destination := Keys("collections", "a/b", "0")
|
||||||
path := writeRecordConfig(t, "value: main\n")
|
path := writeRecordConfig(t, "value: main\n")
|
||||||
options := engine.OptionsWithPlacement(engine.Options[map[string]any]{}, engine.Keys("collections", "a/b", "0"), uint8(PlacementModeSequenceItem))
|
options := engine.Options[map[string]any]{
|
||||||
|
Destination: engine.Keys("collections", "a/b", "0"), PlacementMode: engine.PlacementMode(PlacementModeSequenceItem),
|
||||||
|
}
|
||||||
internal := engineSourceInfoFromLoad(t, path, "source-1", options)
|
internal := engineSourceInfoFromLoad(t, path, "source-1", options)
|
||||||
converted := fromEngineSourceInfo(internal)
|
converted := fromEngineSourceInfo(internal)
|
||||||
if converted.Destination.String() != destination.String() || converted.PlacementMode != PlacementModeSequenceItem || converted.DestinationUnresolved {
|
if converted.Destination.String() != destination.String() || converted.PlacementMode != PlacementModeSequenceItem || converted.DestinationUnresolved {
|
||||||
|
|||||||
Reference in New Issue
Block a user