Project sequence origins onto appended indexes
This commit is contained in:
@@ -116,7 +116,7 @@ func composeDocumentsWithPolicy(policy sequencePolicy, inputs ...*document.Node)
|
||||
for _, input := range inputs {
|
||||
applications = append(applications, historyApplication{layer: LayerBase, value: input})
|
||||
}
|
||||
origins := compositionOrigins(collected.ordered)
|
||||
origins := projectedCompositionOrigins(inputs, ranges)
|
||||
completeCompositionOrigins(root, Path{}, origins)
|
||||
return &composedDocument{
|
||||
root: root,
|
||||
@@ -337,18 +337,51 @@ func documentEntryIndex(entries []document.Entry, key string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func compositionOrigins(claims []compositionClaim) map[string]Origin {
|
||||
origins := make(map[string]Origin, len(claims))
|
||||
for _, claim := range claims {
|
||||
pointer := claim.path.String()
|
||||
origin := origins[pointer]
|
||||
origin.Layer = LayerBase
|
||||
origin.Locations = append(origin.Locations, claim.location)
|
||||
origins[pointer] = origin
|
||||
func projectedCompositionOrigins(inputs []*document.Node, ranges []sequenceRange) map[string]Origin {
|
||||
origins := make(map[string]Origin)
|
||||
rangeBySourcePath := make(map[sequenceRangeKey]int, len(ranges))
|
||||
for index, assigned := range ranges {
|
||||
rangeBySourcePath[sequenceRangeKey{sourceIndex: assigned.sourceIndex, path: assigned.path.String()}] = index
|
||||
}
|
||||
for sourceIndex, input := range inputs {
|
||||
collectProjectedCompositionOrigins(input, Path{}, Path{}, sourceIndex, ranges, rangeBySourcePath, origins)
|
||||
}
|
||||
return origins
|
||||
}
|
||||
|
||||
type sequenceRangeKey struct {
|
||||
sourceIndex int
|
||||
path string
|
||||
}
|
||||
|
||||
// Keep local paths for matching Stage 2 ranges and effective paths for emitted
|
||||
// evidence. The paths are transformed segment by segment; pointer strings are
|
||||
// only used as map keys after projection is complete.
|
||||
func collectProjectedCompositionOrigins(node *document.Node, localPath, effectivePath Path, sourceIndex int, ranges []sequenceRange, rangeBySourcePath map[sequenceRangeKey]int, origins map[string]Origin) {
|
||||
pointer := effectivePath.String()
|
||||
origin := origins[pointer]
|
||||
origin.Layer = LayerBase
|
||||
origin.Locations = append(origin.Locations, publicLocation(node.Location))
|
||||
origins[pointer] = origin
|
||||
|
||||
switch node.Kind {
|
||||
case document.Mapping:
|
||||
for _, entry := range node.Mapping {
|
||||
collectProjectedCompositionOrigins(entry.Value, localPath.Key(entry.Key), effectivePath.Key(entry.Key), sourceIndex, ranges, rangeBySourcePath, origins)
|
||||
}
|
||||
case document.Sequence:
|
||||
start := 0
|
||||
if rangeIndex, ok := rangeBySourcePath[sequenceRangeKey{sourceIndex: sourceIndex, path: localPath.String()}]; ok {
|
||||
start = ranges[rangeIndex].start
|
||||
}
|
||||
for index, child := range node.Sequence {
|
||||
localChild := localPath.Index(index)
|
||||
effectiveChild := effectivePath.Index(start + index)
|
||||
collectProjectedCompositionOrigins(child, localChild, effectiveChild, sourceIndex, ranges, rangeBySourcePath, origins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyCompositionOrigins(decoded *decodedConfiguration, origins map[string]Origin) {
|
||||
for pointer, origin := range origins {
|
||||
if _, present := decoded.presence[pointer]; present {
|
||||
|
||||
@@ -220,6 +220,96 @@ func TestComposeDocumentsAppendsRegisteredSequencesAndReturnsRanges(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDocumentsProjectsSequenceOriginsToAssignedIndexes(t *testing.T) {
|
||||
first := parseCompositionDocument(t, "source-1", `mounted:
|
||||
records:
|
||||
- id: first
|
||||
name: first-name
|
||||
escaped/~:
|
||||
"7":
|
||||
- id: escaped-first
|
||||
name: escaped-first-name
|
||||
`)
|
||||
second := parseCompositionDocument(t, "source-2", `mounted:
|
||||
records:
|
||||
- id: second
|
||||
name: second-name
|
||||
escaped/~:
|
||||
"7":
|
||||
- id: escaped-second
|
||||
name: escaped-second-name
|
||||
`)
|
||||
firstBefore, secondBefore := document.Clone(first), document.Clone(second)
|
||||
policy := sequencePolicy{identityFields: map[string]string{
|
||||
Keys("mounted", "records").String(): "id",
|
||||
Keys("escaped/~", "7").String(): "id",
|
||||
}}
|
||||
|
||||
composed, diagnostics := composeDocumentsWithPolicy(policy, first, second)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("composeDocumentsWithPolicy() diagnostics = %#v", diagnostics)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
path Path
|
||||
want Location
|
||||
}{
|
||||
{path: Keys("mounted", "records").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 4, Column: 13}},
|
||||
{path: Keys("escaped/~", "7").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 8, Column: 13}},
|
||||
} {
|
||||
path, want := test.path, test.want
|
||||
origin, ok := composed.origins[path.String()]
|
||||
if !ok || !reflect.DeepEqual(origin.Locations, []Location{want}) {
|
||||
t.Errorf("origin at %s = %#v, want source-specific location %#v", path, origin, want)
|
||||
}
|
||||
}
|
||||
for _, test := range []struct {
|
||||
path Path
|
||||
wantSources []string
|
||||
}{
|
||||
{path: Keys("mounted", "records"), wantSources: []string{"source-1", "source-2"}},
|
||||
{path: Keys("escaped/~", "7"), wantSources: []string{"source-1", "source-2"}},
|
||||
} {
|
||||
path, wantSources := test.path, test.wantSources
|
||||
origin := composed.origins[path.String()]
|
||||
gotSources := make([]string, len(origin.Locations))
|
||||
for index, location := range origin.Locations {
|
||||
gotSources[index] = location.SourceID
|
||||
}
|
||||
if !reflect.DeepEqual(gotSources, wantSources) {
|
||||
t.Errorf("container origin at %s = %#v, want contributors %#v", path, gotSources, wantSources)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) {
|
||||
t.Fatal("composition projection mutated an input")
|
||||
}
|
||||
|
||||
copy := cloneComposedDocument(composed)
|
||||
copy.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID = "changed"
|
||||
for index := range copy.claims {
|
||||
if copy.claims[index].path.String() == "/mounted/records" {
|
||||
copy.claims[index].path.segments[0].key = "changed"
|
||||
break
|
||||
}
|
||||
}
|
||||
copy.sequenceRanges[0].path.segments[0].key = "changed"
|
||||
if got := composed.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID; got != "source-2" {
|
||||
t.Fatalf("copied origin mutation changed source evidence: %q", got)
|
||||
}
|
||||
foundClaim := false
|
||||
for _, claim := range composed.claims {
|
||||
if claim.path.String() == "/mounted/records" {
|
||||
foundClaim = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundClaim {
|
||||
t.Fatal("copied claim mutation changed source path")
|
||||
}
|
||||
if got := composed.sequenceRanges[0].path.String(); got != "/mounted/records" {
|
||||
t.Fatalf("copied range mutation changed assigned path: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDocumentsValidatesRegisteredSequenceSingletons(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user