package engine import ( "strconv" "strings" "unicode" "unicode/utf8" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) const maxOverrides = 1024 type preparedOverride struct { path Path value *document.Node source SourceInfo bytes int64 nodes int } // preparedOverrideBatch owns all override values and synthetic source records // needed to apply one CLI batch after its preparation checks succeed. type preparedOverrideBatch struct { overrides []preparedOverride sources []SourceInfo } type overridePreparationInput struct { root *document.Node inputBytes int64 inputNodes int } // preparePostStructuralOverrides snapshots CLI input for typed assignment after // structural expansion, when inheritance directives can no longer be supplied. func preparePostStructuralOverrides(base overridePreparationInput, sources []SourceInfo, overrides []Override, rule *VersionRule, limits importLimits) (preparedOverrideBatch, []Diagnostic) { return prepareOverrideBatch(base, sources, overrides, rule, limits) } func prepareOverrideBatch(base overridePreparationInput, sources []SourceInfo, overrides []Override, rule *VersionRule, limits importLimits) (preparedOverrideBatch, []Diagnostic) { if len(overrides) > maxOverrides || len(sources)+len(overrides) > limits.sources { return preparedOverrideBatch{sources: cloneSourceInfos(sources)}, []Diagnostic{{ Code: CodeOverrideLimitExceeded, Severity: SeverityError, Message: "configuration overrides exceed the record or source-count limit", }} } var diagnostics []Diagnostic for index, override := range overrides { if !validOverrideLabel(override.Flag) { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideInvalidLabel, Severity: SeverityError, Message: "override record " + strconv.Itoa(index+1) + " requires a safe nonempty flag name", }) } } if len(diagnostics) != 0 { return preparedOverrideBatch{sources: cloneSourceInfos(sources)}, canonicalDiagnostics(diagnostics) } prepared := make([]preparedOverride, len(overrides)) allSources := cloneSourceInfos(sources) for index, override := range overrides { sourceID := "source-" + strconv.Itoa(len(sources)+index+1) info := SourceInfo{ID: sourceID, Label: override.Flag, Layer: LayerCLI, Kind: SourceKindCLI} allSources = append(allSources, info) prepared[index].source = info } seen := make(map[string]int, len(overrides)) var acceptedPaths pathPrefixIndex var baseLookup *documentLookup if len(overrides) != 0 { baseLookup = buildDocumentLookup(base.root) } var usedBytes int64 usedNodes := 0 for index, override := range overrides { location := &Location{SourceID: prepared[index].source.ID} if issue := validateOverridePath(override.Path, base.root, baseLookup); issue != nil { issue.Primary = location diagnostics = append(diagnostics, *issue) continue } pointer := override.Path.String() if previous, duplicate := seen[pointer]; duplicate { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideDuplicate, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration override path is repeated", Primary: location, Related: []Location{{SourceID: prepared[previous].source.ID}}, }) continue } seen[pointer] = index if conflicting, overlaps := acceptedPaths.earliestIntersectingID(pointer); overlaps { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideOverlap, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration override paths overlap", Primary: location, Related: []Location{{SourceID: prepared[conflicting].source.ID}}, }) continue } if rule != nil && (pathContains(override.Path, rule.Path) || pathContains(rule.Path, override.Path)) { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideVersion, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration overrides cannot replace the schema-version path", Primary: location, }) continue } if pathContainsDirective(override.Path) || pathContainsInheritanceDirective(override.Path) { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideDirective, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration overrides cannot create configuration directives", Primary: location, }) continue } acceptedPaths.insert(pointer, index) builder := valueBuilder{sourceID: prepared[index].source.ID, active: make(map[valueVisit]bool)} value, issue := builder.build(override.Value, override.Path, len(override.Path.segments)) if issue != nil { diagnostic := issue.overrideDiagnostic() diagnostic.Primary = location diagnostics = append(diagnostics, diagnostic) continue } if relative, ok := overrideStructuralDirectivePath(value, Path{}); ok { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideDirective, Severity: SeverityError, Path: appendOverridePath(override.Path, relative), Message: "configuration overrides cannot create configuration directives", Primary: location, }) continue } nodes := builder.nodes + len(override.Path.segments)*2 if nodes > limits.nodes || base.inputNodes+usedNodes+nodes > limits.nodes { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideLimitExceeded, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration overrides exceed the aggregate node limit", Primary: location, }) continue } if builder.bytes > maxSourceBytes || base.inputBytes+usedBytes+builder.bytes > limits.bytes { diagnostics = append(diagnostics, Diagnostic{ Code: CodeOverrideLimitExceeded, Severity: SeverityError, Path: override.Path.clone(), Message: "configuration overrides exceed a record or aggregate string-byte limit", Primary: location, }) continue } prepared[index].path = override.Path.clone() prepared[index].value = value prepared[index].bytes = builder.bytes prepared[index].nodes = nodes usedBytes += builder.bytes usedNodes += nodes } if len(diagnostics) != 0 { return preparedOverrideBatch{sources: allSources}, canonicalDiagnostics(diagnostics) } return preparedOverrideBatch{overrides: prepared, sources: allSources}, nil } func validOverrideLabel(label string) bool { if label == "" || strings.TrimSpace(label) != label || !utf8.ValidString(label) { return false } for _, character := range label { if unicode.IsControl(character) { return false } } return true } func validateOverridePath(path Path, root *document.Node, lookup *documentLookup) *Diagnostic { if !path.valid() || len(path.segments) == 0 { return &Diagnostic{Code: CodeOverrideInvalidPath, Severity: SeverityError, Path: path.clone(), Message: "configuration override path must be a valid non-root mapping path"} } current := root for index, segment := range path.segments { if segment.kind == pathSegmentIndex { return &Diagnostic{Code: CodeOverrideInvalidPath, Severity: SeverityError, Path: path.clone(), Message: "configuration override paths cannot contain sequence indices"} } if current == nil { continue } if current.Kind != document.Mapping { return &Diagnostic{Code: CodeOverrideInvalidPath, Severity: SeverityError, Path: path.clone(), Message: "configuration override path traverses a non-mapping value"} } position := lookup.find(current, segment.key) if index == len(path.segments)-1 { continue } if position < 0 { current = nil continue } current = current.Mapping[position].Value } return nil } func pathContainsDirective(path Path) bool { for _, segment := range path.segments { if segment.key == directiveKey { return true } } return false } func pathContainsInheritanceDirective(path Path) bool { for _, segment := range path.segments { if segment.key == inheritanceKey { return true } } return false } func appendOverridePath(base, relative Path) Path { result := base.clone() result.segments = append(result.segments, clonePathSegments(relative.segments, 0)...) return result } func overrideStructuralDirectivePath(node *document.Node, path Path) (Path, bool) { return overrideStructuralDirectivePathAt(node, path) } func overrideStructuralDirectivePathAt(node *document.Node, path Path) (Path, bool) { if node == nil { return Path{}, false } if node.Kind == document.Mapping { for _, entry := range node.Mapping { child := path.Key(entry.Key) if entry.Key == directiveKey || entry.Key == inheritanceKey { return child, true } if found, ok := overrideStructuralDirectivePathAt(entry.Value, child); ok { return found, true } } } else if node.Kind == document.Sequence { for index, child := range node.Sequence { if found, ok := overrideStructuralDirectivePathAt(child, path.Index(index)); ok { return found, true } } } return Path{}, false }