Files
configkit/internal/engine/history_select.go

198 lines
5.7 KiB
Go

package engine
import (
"slices"
"sort"
)
type historySelection struct {
path Path
phase historyPhase
payload historyPayloadRef
reportedPath Path
role historyRole
before bool
after bool
attribution historyAttribution
hasAttribution bool
previous []Origin
}
type historySensitivityCandidate struct {
path Path
sensitive bool
dependencies []Path
}
func (model *historyModel) selectHistory(path Path) []historySelection {
if model == nil || !path.valid() || !model.hasObservedShape(path) {
return nil
}
candidateSet := make(map[int]struct{})
for _, observationID := range model.observationsByPointer[path.String()] {
candidateSet[observationID] = struct{}{}
}
model.reportedPathIndex.visitAncestors(path.String(), func(observationID int) {
candidateSet[observationID] = struct{}{}
})
candidates := make([]int, 0, len(candidateSet))
for observationID := range candidateSet {
candidates = append(candidates, observationID)
}
slices.Sort(candidates)
selections := make([]historySelection, 0, len(candidates))
selectionByPayload := make(map[historyPayloadRef]int)
for _, observationID := range candidates {
observation := model.observations[observationID]
if observation.role == historyRoleInventory || !matchesPathShape(path, observation.path) {
continue
}
selectionIndex, found := selectionByPayload[observation.payload]
if !found {
selectionIndex = len(selections)
selectionByPayload[observation.payload] = selectionIndex
selection := historySelection{
path: path.clone(),
phase: observation.phase,
payload: observation.payload,
reportedPath: observation.reportedPath.clone(),
role: observation.role,
}
if attribution, ok := model.resolveAttribution(observation.attributionRef); ok {
selection.attribution = attribution
selection.hasAttribution = true
}
selections = append(selections, selection)
}
selection := &selections[selectionIndex]
selection.before = selection.before || observation.beforePresent
selection.after = selection.after || observation.afterPresent
}
if len(selections) == 0 {
return nil
}
var previous *Origin
for index := range selections {
selection := &selections[index]
if selection.hasAttribution && metadataHistoryRole(selection.role) {
if previous != nil {
selection.previous = []Origin{cloneOrigin(*previous)}
}
origin := cloneOrigin(selection.attribution.origin)
previous = &origin
}
}
return selections
}
func (model *historyModel) hasObservedShape(path Path) bool {
if model == nil || !path.valid() {
return false
}
for _, candidate := range model.shapesByPointer[path.String()] {
if matchesPathShape(path, candidate) {
return true
}
}
return false
}
func (model *historyModel) concreteShapes(pointer string) []Path {
if model == nil {
return nil
}
shapes := model.shapesByPointer[pointer]
cloned := make([]Path, len(shapes))
for index, path := range shapes {
cloned[index] = path.clone()
}
return cloned
}
func (model *historyModel) observedPaths() []Path {
if model == nil {
return nil
}
paths := make([]Path, len(model.shapeOrder))
for index, path := range model.shapeOrder {
paths[index] = path.clone()
}
return paths
}
func (model *historyModel) historicalPaths() []Path {
if model == nil {
return []Path{}
}
pointers := make([]string, 0, len(model.shapesByPointer))
for pointer := range model.shapesByPointer {
pointers = append(pointers, pointer)
}
sort.Strings(pointers)
paths := make([]Path, 0, len(pointers))
for _, pointer := range pointers {
path, err := ParsePath(pointer)
if err == nil {
paths = append(paths, path)
}
}
return paths
}
func (model *historyModel) latestHistoricalLocation(path Path) (Location, bool) {
selections := model.selectHistory(path)
for selectionIndex := len(selections) - 1; selectionIndex >= 0; selectionIndex-- {
selection := selections[selectionIndex]
if selection.hasAttribution {
if location, found := latestOriginDocumentLocation(selection.attribution.origin); found {
return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
}
}
for previousIndex := len(selection.previous) - 1; previousIndex >= 0; previousIndex-- {
if location, found := latestOriginDocumentLocation(selection.previous[previousIndex]); found {
return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
}
}
}
return Location{}, false
}
func (model *historyModel) sensitivityCandidates() []historySensitivityCandidate {
if model == nil {
return nil
}
candidates := make([]historySensitivityCandidate, 0, len(model.observations))
for _, observation := range model.observations {
candidate := historySensitivityCandidate{path: observation.path.clone(), sensitive: observation.sensitive}
if attribution, found := model.resolveAttribution(observation.attributionRef); found {
candidate.dependencies = attribution.dependencies
}
candidates = append(candidates, candidate)
}
return candidates
}
func (model *historyModel) resolveAttribution(ref historyAttributionRef) (historyAttribution, bool) {
if model == nil || ref < 0 || int(ref) >= len(model.attributions) {
return historyAttribution{}, false
}
attribution := model.attributions[ref]
return historyAttribution{
origin: cloneOrigin(attribution.origin),
reasons: append([]string(nil), attribution.reasons...),
dependencies: clonePaths(attribution.dependencies),
}, true
}
func metadataHistoryRole(role historyRole) bool {
switch role {
case historyRoleDefinition, historyRoleCompositeDefinition, historyRoleOperation, historyRoleControl:
return true
default:
return false
}
}