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 } // 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, ) 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) 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", )} }