173 lines
6.0 KiB
Go
173 lines
6.0 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
type identifiedInheritanceTemplate struct {
|
|
item *document.Node
|
|
path Path
|
|
tuple string
|
|
parent int
|
|
hasParent bool
|
|
cancelled bool
|
|
directive *document.Node
|
|
directivePath Path
|
|
}
|
|
|
|
type identifiedInheritanceGraph struct {
|
|
scope *document.Node
|
|
scopePath Path
|
|
fields []string
|
|
nodes []identifiedInheritanceTemplate
|
|
edges []dependencyEdge
|
|
byIdentity map[string]int
|
|
}
|
|
|
|
func sequenceHasIdentifiedInheritanceReferences(sequence *document.Node) bool {
|
|
if sequence == nil || sequence.Kind != document.Sequence {
|
|
return false
|
|
}
|
|
for _, item := range sequence.Sequence {
|
|
if directive, found := inheritanceDirective(item); found && directive.Value.Kind != document.Null {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// analyzeIdentifiedInheritanceGraph validates relationships inside one
|
|
// identified sequence. It prepares a graph only; mapping-sibling expansion
|
|
// remains the active inheritance behavior until the reusable scope executor is
|
|
// activated.
|
|
func analyzeIdentifiedInheritanceGraph(sequence *document.Node, scopePath Path, identityFields []string) (*identifiedInheritanceGraph, []Diagnostic) {
|
|
if sequence == nil || sequence.Kind != document.Sequence {
|
|
var primary *Location
|
|
if sequence != nil {
|
|
location := publicLocation(sequence.Location)
|
|
primary = &location
|
|
}
|
|
return nil, []Diagnostic{{
|
|
Code: CodeSequenceInvalid, Severity: SeverityError, Path: scopePath.clone(),
|
|
Message: "identified inheritance scope must be a sequence", Primary: primary,
|
|
}}
|
|
}
|
|
fields, err := normalizeSequenceIdentityFields(identityFields)
|
|
if err != nil {
|
|
location := publicLocation(sequence.Location)
|
|
return nil, []Diagnostic{{
|
|
Code: CodeSequenceInvalid, Severity: SeverityError, Path: scopePath.clone(),
|
|
Message: "identified inheritance scope requires identity fields", Primary: &location,
|
|
}}
|
|
}
|
|
lookup, diagnostics := validateNormalizedSequenceIdentityFrom(
|
|
sequence, scopePath, fields, sequenceCombinationIdentityCodes, 0, sequenceStructuralRecord,
|
|
)
|
|
if len(diagnostics) != 0 {
|
|
return nil, diagnostics
|
|
}
|
|
graph := &identifiedInheritanceGraph{
|
|
scope: sequence, scopePath: scopePath.clone(), fields: cloneStrings(fields),
|
|
nodes: make([]identifiedInheritanceTemplate, len(sequence.Sequence)),
|
|
byIdentity: make(map[string]int, len(lookup)),
|
|
}
|
|
for tuple, record := range lookup {
|
|
graph.byIdentity[tuple] = record.index
|
|
graph.nodes[record.index] = identifiedInheritanceTemplate{
|
|
item: sequence.Sequence[record.index], path: scopePath.Index(record.index), tuple: tuple,
|
|
}
|
|
}
|
|
|
|
for index := range graph.nodes {
|
|
node := &graph.nodes[index]
|
|
directive, found := inheritanceDirective(node.item)
|
|
if !found {
|
|
continue
|
|
}
|
|
node.directive = directive.Value
|
|
node.directivePath = node.path.Key(inheritanceKey)
|
|
referenceLocation := publicLocation(directive.Value.Location)
|
|
switch directive.Value.Kind {
|
|
case document.Null:
|
|
node.cancelled = true
|
|
case document.Mapping:
|
|
reference, issues := identifiedInheritanceReference(directive.Value, node.directivePath, fields)
|
|
if len(issues) != 0 {
|
|
return nil, issues
|
|
}
|
|
parent, exists := graph.byIdentity[reference]
|
|
if !exists {
|
|
return nil, []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceMissing, node.directivePath, referenceLocation, nil,
|
|
"referenced record does not exist in this collection",
|
|
)}
|
|
}
|
|
node.parent = parent
|
|
node.hasParent = true
|
|
graph.edges = append(graph.edges, dependencyEdge{prerequisite: parent, dependent: index})
|
|
default:
|
|
return nil, []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceInvalid, node.directivePath, referenceLocation, nil,
|
|
"identified record inheritance reference must be a mapping or null",
|
|
)}
|
|
}
|
|
}
|
|
return graph, nil
|
|
}
|
|
|
|
func identifiedInheritanceReference(reference *document.Node, path Path, identityFields []string) (string, []Diagnostic) {
|
|
allowed := make(map[string]struct{}, len(identityFields))
|
|
for _, field := range identityFields {
|
|
allowed[field] = struct{}{}
|
|
}
|
|
seen := make(map[string]struct{}, len(reference.Mapping))
|
|
for _, entry := range reference.Mapping {
|
|
if _, ok := allowed[entry.Key]; !ok {
|
|
return "", []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceInvalid, path.Key(entry.Key), publicLocation(entry.Value.Location), nil,
|
|
"identified record inheritance reference has an unsupported field",
|
|
)}
|
|
}
|
|
seen[entry.Key] = struct{}{}
|
|
}
|
|
for _, field := range identityFields {
|
|
if _, ok := seen[field]; !ok {
|
|
return "", []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceInvalid, path.Key(field), publicLocation(reference.Location), nil,
|
|
"identified record inheritance reference is missing an identity field",
|
|
)}
|
|
}
|
|
}
|
|
index := newSequenceMappingIndex(reference, sequencePayloadRecord)
|
|
tuple, _, missing, invalid := index.identity(identityFields)
|
|
if missing != "" || invalid >= 0 || invalid == -2 {
|
|
value := reference
|
|
fieldPath := path.Key(missing)
|
|
if invalid >= 0 && invalid < len(reference.Mapping) {
|
|
value = reference.Mapping[invalid].Value
|
|
fieldPath = path.Key(reference.Mapping[invalid].Key)
|
|
}
|
|
location := publicLocation(value.Location)
|
|
return "", []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceInvalid, fieldPath, location, nil,
|
|
"identified record inheritance identity fields must be scalar values",
|
|
)}
|
|
}
|
|
return tuple, nil
|
|
}
|
|
|
|
func (graph *identifiedInheritanceGraph) schedule() ([]int, []Diagnostic) {
|
|
keys := make([]dependencyKey, len(graph.nodes))
|
|
for index, node := range graph.nodes {
|
|
keys[index] = dependencyKey{"record", graph.scopePath.String(), node.path.String()}
|
|
}
|
|
order, cycle := scheduleDependencies(keys, graph.edges)
|
|
if len(cycle) == 0 {
|
|
return order, nil
|
|
}
|
|
parent, child := graph.nodes[cycle[0]], graph.nodes[cycle[1]]
|
|
return nil, []Diagnostic{inheritanceDiagnostic(
|
|
CodeInheritanceCycle, child.directivePath, publicLocation(child.directive.Location),
|
|
[]Location{publicLocation(parent.item.Location)},
|
|
"identified record inheritance contains a cycle",
|
|
)}
|
|
}
|