Reuse document indexes for collection policy checks
This commit is contained in:
98
internal/engine/collection_lookup_test.go
Normal file
98
internal/engine/collection_lookup_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
)
|
||||
|
||||
func TestOverlayBroadCollectionPoliciesAndRetainedDeclarations(t *testing.T) {
|
||||
var baseBody, overlayBody strings.Builder
|
||||
basePolicies := collectionPolicyScope{}
|
||||
var declarations []sequenceDeclarationSpec
|
||||
for index := range 64 {
|
||||
name := fmt.Sprintf("group/%03d~", index)
|
||||
fmt.Fprintf(&baseBody, "%s: {items: [{id: base}], retained: [{id: retained}], scalar: pending}\n", name)
|
||||
fmt.Fprintf(&overlayBody, "%s: {items: [{id: overlay}], added: true}\n", name)
|
||||
basePolicies.set(Keys(name, "items"), collectionPolicy{
|
||||
identityFields: []string{"id"}, identityPresent: true,
|
||||
explicitMode: sequenceModeAppend, explicitModePresent: true,
|
||||
})
|
||||
for _, key := range []string{"retained", "missing", "scalar"} {
|
||||
declarations = append(declarations, sequenceDeclarationSpec{
|
||||
destination: Keys(name, key), identityFields: []string{"id"}, identityPresent: true,
|
||||
mode: sequenceModeAppend, modePresent: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
base, diagnostics := composeDocuments(parseCompositionDocument(t, "base", baseBody.String()))
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
base.collectionPolicies = basePolicies
|
||||
assembly := newAssemblyAccumulator(base)
|
||||
overlay := parseCompositionDocument(t, "overlay", overlayBody.String())
|
||||
diagnostics = assembly.applyOverlayWithCollectionPolicy(overlay, nil, collectionPolicyScope{}, nil, LayerOverlay, sequencePolicy{}, declarations)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
lookup := buildDocumentLookup(assembly.current.root)
|
||||
for index := range 64 {
|
||||
name := fmt.Sprintf("group/%03d~", index)
|
||||
items, found := documentAtPathWithLookup(assembly.current.root, Keys(name, "items"), lookup)
|
||||
if !found || items.Kind != document.Sequence || len(items.Sequence) != 2 || items.Sequence[0].Mapping[0].Value.Text != "base" || items.Sequence[1].Mapping[0].Value.Text != "overlay" {
|
||||
t.Fatalf("collection %s did not append in order", name)
|
||||
}
|
||||
for _, key := range []string{"retained", "missing", "scalar"} {
|
||||
policy, found := assembly.current.collectionPolicies.at(Keys(name, key))
|
||||
if !found || !policy.identityPresent || !policy.explicitModePresent || policy.explicitMode != sequenceModeAppend {
|
||||
t.Fatalf("retained/pending declaration lost at %s/%s", name, key)
|
||||
}
|
||||
}
|
||||
if _, found := documentAtPathWithLookup(assembly.current.root, Keys(name, "missing"), lookup); found {
|
||||
t.Fatal("pending declaration created a value")
|
||||
}
|
||||
if origin := assembly.current.origins[Keys(name, "items").Index(1).Key("id").String()]; origin.Layer != LayerOverlay {
|
||||
t.Fatal("appended item lost overlay attribution")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInheritanceBroadCollectionsInOneDefinition(t *testing.T) {
|
||||
var body strings.Builder
|
||||
policies := collectionPolicyScope{}
|
||||
for _, definition := range []string{"base", "child"} {
|
||||
fmt.Fprintf(&body, "%s:\n", definition)
|
||||
if definition == "child" {
|
||||
body.WriteString(" extends: base\n")
|
||||
}
|
||||
for index := range 64 {
|
||||
name := fmt.Sprintf("group/%03d~", index)
|
||||
fmt.Fprintf(&body, " %s: {items: [{id: %s}]}\n", name, definition)
|
||||
policy := collectionPolicy{identityFields: []string{"id"}, identityPresent: true}
|
||||
if definition == "child" {
|
||||
policy.explicitMode, policy.explicitModePresent = sequenceModeAppend, true
|
||||
}
|
||||
policies.set(Keys(definition, name, "items"), policy)
|
||||
policies.set(Keys(definition, name, "pending"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
|
||||
}
|
||||
}
|
||||
root := parseCompositionDocument(t, "wide-collections", body.String())
|
||||
result, diagnostics := expandInheritanceWithPolicies(root, nil, policies, defaultProjectionLimits())
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatal(diagnostics)
|
||||
}
|
||||
lookup := buildDocumentLookup(result.root)
|
||||
for index := range 64 {
|
||||
name := fmt.Sprintf("group/%03d~", index)
|
||||
items, found := documentAtPathWithLookup(result.root, Keys("child", name, "items"), lookup)
|
||||
if !found || len(items.Sequence) != 2 || items.Sequence[0].Mapping[0].Value.Text != "base" || items.Sequence[1].Mapping[0].Value.Text != "child" {
|
||||
t.Fatalf("inherited collection %s did not append in order", name)
|
||||
}
|
||||
if policy, found := result.collectionPolicies.at(Keys("child", name, "pending")); !found || !policy.identityPresent {
|
||||
t.Fatalf("pending inherited policy lost at %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,15 +103,19 @@ func mergeInheritanceCollectionPolicies(
|
||||
for key, entry := range child.collections {
|
||||
paths[key] = entry.destination.clone()
|
||||
}
|
||||
var lookup *documentLookup
|
||||
if len(paths) != 0 {
|
||||
lookup = buildDocumentLookup(parentValue, childValue)
|
||||
}
|
||||
var diagnostics []Diagnostic
|
||||
for _, path := range paths {
|
||||
relative, inside := relativeHistoryPath(parentRootPath, path)
|
||||
var retained, incoming *document.Node
|
||||
if inside {
|
||||
if !replaced.contains(path, false) {
|
||||
retained, _ = documentAtPath(parentValue, relative)
|
||||
retained, _ = documentAtPathWithLookup(parentValue, relative, lookup)
|
||||
}
|
||||
incoming, _ = documentAtPath(childValue, relative)
|
||||
incoming, _ = documentAtPathWithLookup(childValue, relative, lookup)
|
||||
}
|
||||
resolution, lower, _, active, issues := inheritanceCollectionResolution(path, parent, child, applicationPolicy, incoming)
|
||||
if len(issues) != 0 {
|
||||
|
||||
@@ -138,8 +138,12 @@ func mergeOverlayMappingsWithCollectionPolicy(
|
||||
for key := range declarationsByPath {
|
||||
registered[key] = true
|
||||
}
|
||||
var overlayLookup *documentLookup
|
||||
if len(result.collectionPolicies.collections) != 0 {
|
||||
overlayLookup = buildDocumentLookup(overlay)
|
||||
}
|
||||
for key := range result.collectionPolicies.collections {
|
||||
if node, found := documentAtPath(overlay, result.collectionPolicies.collections[key].destination); found && node != nil {
|
||||
if node, found := documentAtPathWithLookup(overlay, result.collectionPolicies.collections[key].destination, overlayLookup); found && node != nil {
|
||||
registered[key] = true
|
||||
registeredPaths[key] = result.collectionPolicies.collections[key].destination.clone()
|
||||
}
|
||||
@@ -210,6 +214,9 @@ func mergeOverlayMappingsWithCollectionPolicy(
|
||||
return nil, canonicalDiagnostics(combinationDiagnostics)
|
||||
}
|
||||
|
||||
// Values are now complete. Index lazily for declaration-only checks;
|
||||
// this lookup is never retained across the value-changing merge above.
|
||||
var retainedLookup *documentLookup
|
||||
for key, grouped := range declarationsByPath {
|
||||
if policyValues[key] {
|
||||
continue
|
||||
@@ -226,7 +233,10 @@ func mergeOverlayMappingsWithCollectionPolicy(
|
||||
if found {
|
||||
lowerPolicy = &lower
|
||||
}
|
||||
retained, _ := documentAtPath(assembly.current.root, path)
|
||||
if retainedLookup == nil {
|
||||
retainedLookup = buildDocumentLookup(assembly.current.root)
|
||||
}
|
||||
retained, _ := documentAtPathWithLookup(assembly.current.root, path, retainedLookup)
|
||||
if retained != nil && retained.Kind == document.Sequence && len(current.effectiveIdentityFields) != 0 {
|
||||
if _, issues := validateSequenceIdentity(retained, path, current.effectiveIdentityFields, sequenceIdentityDiagnosticCodes{invalid: CodeSequenceInvalid, conflict: CodeSequenceConflict}); len(issues) != 0 {
|
||||
combinationDiagnostics = append(combinationDiagnostics, issues...)
|
||||
|
||||
Reference in New Issue
Block a user