package engine import ( "path" "strings" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) const directiveKey = "configkit" type rootDirectiveContext uint8 const ( rootDirectiveContextImportsOnly rootDirectiveContext = iota + 1 rootDirectiveContextSource rootDirectiveContextOverlay rootDirectiveContextLayered ) type rootDirectiveExtraction struct { body *document.Node imports []importDeclarationSpec sequences []sequenceDeclarationSpec importsPresent bool sequencesPresent bool } func splitImportDirectives(root *document.Node) (*document.Node, []importDeclarationSpec, []Diagnostic) { extracted, diagnostics := extractRootDirectives(root, rootDirectiveContextImportsOnly) if len(diagnostics) != 0 { return nil, nil, diagnostics } return extracted.body, extracted.imports, nil } func extractRootDirectives(root *document.Node, context rootDirectiveContext) (rootDirectiveExtraction, []Diagnostic) { if root == nil { return rootDirectiveExtraction{}, nil } body := document.Clone(root) directiveIndex := -1 for index, entry := range body.Mapping { if entry.Key != directiveKey { continue } directiveIndex = index extracted, diagnostics := parseRootDirectiveBlock(entry.Value, Keys(directiveKey), context) if len(diagnostics) != 0 { return rootDirectiveExtraction{}, diagnostics } extracted.body = body if directiveIndex >= 0 { extracted.body.Mapping = append(extracted.body.Mapping[:directiveIndex], extracted.body.Mapping[directiveIndex+1:]...) } if diagnostics := nestedDirectiveDiagnostics(extracted.body, Path{}); len(diagnostics) != 0 { return rootDirectiveExtraction{}, diagnostics } return extracted, nil } if diagnostics := nestedDirectiveDiagnostics(body, Path{}); len(diagnostics) != 0 { return rootDirectiveExtraction{}, diagnostics } return rootDirectiveExtraction{body: body}, nil } func parseRootDirectiveBlock(node *document.Node, directivePath Path, context rootDirectiveContext) (rootDirectiveExtraction, []Diagnostic) { if node == nil || node.Kind != document.Mapping { return rootDirectiveExtraction{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, directivePath, "configuration directive must be a mapping", node)} } var extracted rootDirectiveExtraction var diagnostics []Diagnostic for _, entry := range node.Mapping { fieldPath := directivePath.Key(entry.Key) switch entry.Key { case "imports": extracted.importsPresent = true if context == rootDirectiveContextOverlay { diagnostics = append(diagnostics, nodeImportDiagnostic(CodeOverlayDirective, fieldPath, "overlays cannot contain imports", entry.Value)) continue } if context != rootDirectiveContextImportsOnly && context != rootDirectiveContextSource && context != rootDirectiveContextLayered { diagnostics = append(diagnostics, nodeImportDiagnostic(CodeImportInvalid, fieldPath, "directive extraction context is invalid", entry.Value)) continue } parsed, issues := parseImportDeclarations(entry.Value, fieldPath) extracted.imports = append(extracted.imports, parsed...) diagnostics = append(diagnostics, issues...) case "sequences": extracted.sequencesPresent = true if context == rootDirectiveContextImportsOnly { diagnostics = append(diagnostics, sequenceDeclarationDiagnostic(fieldPath, "sequence declarations are not enabled in this extraction context", entry.Value)) continue } if context != rootDirectiveContextSource && context != rootDirectiveContextOverlay && context != rootDirectiveContextLayered { diagnostics = append(diagnostics, sequenceDeclarationDiagnostic(fieldPath, "directive extraction context is invalid", entry.Value)) continue } parsed, issues := parseSequenceDeclarationDirective(entry.Value, fieldPath) extracted.sequences = append(extracted.sequences, parsed...) diagnostics = append(diagnostics, issues...) default: diagnostics = append(diagnostics, nodeImportDiagnostic(CodeImportInvalid, fieldPath, "configuration directive field is not recognized", entry.Value)) } } if len(diagnostics) != 0 { return rootDirectiveExtraction{}, canonicalDiagnostics(diagnostics) } return extracted, nil } func nestedDirectiveDiagnostics(node *document.Node, current Path) []Diagnostic { if node.Kind == document.Mapping { for _, entry := range node.Mapping { childPath := current.Key(entry.Key) if entry.Key == directiveKey { location := publicLocation(entry.KeyLocation) return []Diagnostic{{Code: CodeImportInvalid, Severity: SeverityError, Path: childPath, Message: "configuration directives are permitted only at the document root", Primary: &location}} } if diagnostics := nestedDirectiveDiagnostics(entry.Value, childPath); len(diagnostics) != 0 { return diagnostics } } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { if diagnostics := nestedDirectiveDiagnostics(child, current.Index(index)); len(diagnostics) != 0 { return diagnostics } } } return nil } func fragmentVersionClaim(root *document.Node, rule *VersionRule) *document.Node { return fragmentVersionClaimProjected(root, rule, -1, nil) } func fragmentVersionClaimProjected(root *document.Node, rule *VersionRule, sourceIndex int, ranges []sequenceRange) *document.Node { return fragmentVersionClaimWithIndex(root, rule, sourceIndex, indexSequenceRanges(ranges)) } func fragmentVersionClaimWithIndex(root *document.Node, rule *VersionRule, sourceIndex int, ranges sequenceRangeIndex) *document.Node { if rule == nil { return nil } current := root currentPath := Path{} if len(rule.Path.segments) == 0 { return current } for _, segment := range rule.Path.segments { switch current.Kind { case document.Mapping: if segment.kind == pathSegmentIndex { return current } index := documentEntryIndex(current.Mapping, segment.key) if index < 0 { return nil } current = current.Mapping[index].Value currentPath = currentPath.Key(segment.key) case document.Sequence: index, valid := schemaIndex(segment) if !valid { return current } if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: currentPath.String()}]; found { projected, ok := assigned.inputForOutput(index) if !ok { return nil } index = projected } if index < 0 || index >= len(current.Sequence) { return nil } current = current.Sequence[index] currentPath = currentPath.Index(index) default: // A scalar or null at an ancestor replaces the protected subtree. return current } } return current } func validImportPath(value string) bool { if value == "" || strings.TrimSpace(value) != value || path.IsAbs(value) || strings.HasPrefix(value, "//") { return false } return !hasImportScheme(value) } func hasImportScheme(value string) bool { colon := strings.IndexByte(value, ':') slash := strings.IndexByte(value, '/') if colon <= 0 || (slash >= 0 && slash < colon) { return false } for index := 0; index < colon; index++ { character := value[index] if index == 0 { if (character < 'a' || character > 'z') && (character < 'A' || character > 'Z') { return false } continue } if (character < 'a' || character > 'z') && (character < 'A' || character > 'Z') && (character < '0' || character > '9') && character != '+' && character != '-' && character != '.' { return false } } return true } func nodeImportDiagnostic(code Code, path Path, message string, node *document.Node) Diagnostic { location := publicLocation(node.Location) return Diagnostic{Code: code, Severity: SeverityError, Path: path, Message: message, Primary: &location} } func overlayDirectiveDiagnostics(node *document.Node, current Path, allowSequences ...bool) []Diagnostic { sequencesAllowed := len(allowSequences) != 0 && allowSequences[0] if node.Kind == document.Mapping { for _, entry := range node.Mapping { childPath := current.Key(entry.Key) if entry.Key == directiveKey { if sequencesAllowed && len(current.segments) == 0 && entry.Value != nil && entry.Value.Kind == document.Mapping && len(entry.Value.Mapping) == 1 && entry.Value.Mapping[0].Key == "sequences" { continue } location := publicLocation(entry.KeyLocation) return []Diagnostic{{ Code: CodeOverlayDirective, Severity: SeverityError, Path: childPath, Message: "overlays may contain only root collection sequence declarations", Primary: &location, }} } if diagnostics := overlayDirectiveDiagnostics(entry.Value, childPath, sequencesAllowed); len(diagnostics) != 0 { return diagnostics } } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { if diagnostics := overlayDirectiveDiagnostics(child, current.Index(index), sequencesAllowed); len(diagnostics) != 0 { return diagnostics } } } return nil } func disabledDirectiveDiagnostics(node *document.Node, path Path) []Diagnostic { var diagnostics []Diagnostic if node.Kind == document.Mapping { for _, entry := range node.Mapping { childPath := path.Key(entry.Key) if entry.Key == directiveKey { location := Location{SourceID: entry.KeyLocation.SourceID, Line: entry.KeyLocation.Line, Column: entry.KeyLocation.Column} diagnostics = append(diagnostics, Diagnostic{ Code: CodeImportDisabled, Severity: SeverityError, Path: childPath, Message: "configuration directives require imports to be enabled", Primary: &location, }) } diagnostics = append(diagnostics, disabledDirectiveDiagnostics(entry.Value, childPath)...) } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { diagnostics = append(diagnostics, disabledDirectiveDiagnostics(child, path.Index(index))...) } } return diagnostics } func disabledRootImportDiagnostics(node *document.Node) []Diagnostic { if node == nil || node.Kind != document.Mapping { return nil } var rootDirective *document.Node var rootLocation document.Location var foundRootDirective bool for index := range node.Mapping { if node.Mapping[index].Key == directiveKey { rootDirective = node.Mapping[index].Value rootLocation = node.Mapping[index].KeyLocation foundRootDirective = true break } } if foundRootDirective { if rootDirective == nil || rootDirective.Kind != document.Mapping { return disabledDirectiveDiagnostic(rootLocation) } for _, directive := range rootDirective.Mapping { if directive.Key == "imports" { return disabledDirectiveDiagnostic(rootLocation) } } } for _, entry := range node.Mapping { if entry.Key == directiveKey { continue } if diagnostic := disabledNestedDirectiveDiagnostics(entry.Value, Path{}.Key(entry.Key)); len(diagnostic) != 0 { return diagnostic } } return nil } func disabledNestedDirectiveDiagnostics(node *document.Node, path Path) []Diagnostic { if node.Kind == document.Mapping { for _, entry := range node.Mapping { childPath := path.Key(entry.Key) if entry.Key == directiveKey { location := Location{SourceID: entry.KeyLocation.SourceID, Line: entry.KeyLocation.Line, Column: entry.KeyLocation.Column} return []Diagnostic{{ Code: CodeImportDisabled, Severity: SeverityError, Path: childPath, Message: "configuration directives require imports to be enabled", Primary: &location, }} } if diagnostics := disabledNestedDirectiveDiagnostics(entry.Value, childPath); len(diagnostics) != 0 { return diagnostics } } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { if diagnostics := disabledNestedDirectiveDiagnostics(child, path.Index(index)); len(diagnostics) != 0 { return diagnostics } } } return nil } func disabledDirectiveDiagnostic(location document.Location) []Diagnostic { public := Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column} return []Diagnostic{{ Code: CodeImportDisabled, Severity: SeverityError, Path: Keys(directiveKey), Message: "configuration directives require imports to be enabled", Primary: &public, }} }