2026-09-12 15:58:06 +00:00
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
}
2026-10-23 21:02:46 +00:00
// 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
}
func overridePreparationInputFor ( base * composedDocument ) overridePreparationInput {
if base == nil {
return overridePreparationInput { }
}
return overridePreparationInput { root : base . root , inputBytes : base . inputBytes , inputNodes : base . inputNodes }
}
func prepareOverrides ( base * composedDocument , sources [ ] SourceInfo , overrides [ ] Override , rule * VersionRule , limits importLimits ) ( preparedOverrideBatch , [ ] Diagnostic ) {
return prepareOverrideBatch ( overridePreparationInputFor ( base ) , sources , overrides , rule , limits , false )
}
// preparePostStructuralOverrides prepares the same detached batch for the
// lifecycle after structural expansion, where record inheritance directives
// are no longer valid CLI input.
func preparePostStructuralOverrides ( base overridePreparationInput , sources [ ] SourceInfo , overrides [ ] Override , rule * VersionRule , limits importLimits ) ( preparedOverrideBatch , [ ] Diagnostic ) {
return prepareOverrideBatch ( base , sources , overrides , rule , limits , true )
}
func prepareOverrideBatch ( base overridePreparationInput , sources [ ] SourceInfo , overrides [ ] Override , rule * VersionRule , limits importLimits , rejectInheritanceDirectives bool ) ( preparedOverrideBatch , [ ] Diagnostic ) {
2026-09-12 15:58:06 +00:00
if len ( overrides ) > maxOverrides || len ( sources ) + len ( overrides ) > limits . sources {
2026-10-23 21:02:46 +00:00
return preparedOverrideBatch { sources : cloneSourceInfos ( sources ) } , [ ] Diagnostic { {
2026-09-12 15:58:06 +00:00
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 {
2026-10-23 21:02:46 +00:00
return preparedOverrideBatch { sources : cloneSourceInfos ( sources ) } , canonicalDiagnostics ( diagnostics )
2026-09-12 15:58:06 +00:00
}
prepared := make ( [ ] preparedOverride , len ( overrides ) )
allSources := cloneSourceInfos ( sources )
for index , override := range overrides {
sourceID := "source-" + strconv . Itoa ( len ( sources ) + index + 1 )
2026-09-29 17:36:20 +00:00
info := SourceInfo { ID : sourceID , Label : override . Flag , Layer : LayerCLI , Kind : SourceKindCLI }
2026-09-12 15:58:06 +00:00
allSources = append ( allSources , info )
prepared [ index ] . source = info
}
seen := make ( map [ string ] int , len ( overrides ) )
2026-10-09 03:45:43 +00:00
var acceptedPaths pathPrefixIndex
var baseLookup * documentLookup
if len ( overrides ) != 0 {
baseLookup = buildDocumentLookup ( base . root )
}
2026-09-12 15:58:06 +00:00
var usedBytes int64
usedNodes := 0
for index , override := range overrides {
location := & Location { SourceID : prepared [ index ] . source . ID }
2026-10-09 03:45:43 +00:00
if issue := validateOverridePath ( override . Path , base . root , baseLookup ) ; issue != nil {
2026-09-12 15:58:06 +00:00
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
2026-10-09 03:45:43 +00:00
if conflicting , overlaps := acceptedPaths . earliestIntersectingID ( pointer ) ; overlaps {
2026-09-12 15:58:06 +00:00
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
}
2026-10-23 21:02:46 +00:00
if pathContainsDirective ( override . Path ) || ( rejectInheritanceDirectives && pathContainsInheritanceDirective ( override . Path ) ) {
2026-09-12 15:58:06 +00:00
diagnostics = append ( diagnostics , Diagnostic {
Code : CodeOverrideDirective , Severity : SeverityError , Path : override . Path . clone ( ) ,
Message : "configuration overrides cannot create configuration directives" , Primary : location ,
} )
continue
}
2026-10-09 03:45:43 +00:00
acceptedPaths . insert ( pointer , index )
2026-09-12 15:58:06 +00:00
2026-09-29 17:43:19 +00:00
builder := valueBuilder { sourceID : prepared [ index ] . source . ID , active : make ( map [ valueVisit ] bool ) }
2026-09-12 15:58:06 +00:00
value , issue := builder . build ( override . Value , override . Path , len ( override . Path . segments ) )
if issue != nil {
2026-09-29 17:43:19 +00:00
diagnostic := issue . overrideDiagnostic ( )
diagnostic . Primary = location
diagnostics = append ( diagnostics , diagnostic )
2026-09-12 15:58:06 +00:00
continue
}
2026-10-23 21:02:46 +00:00
findDirective := overrideDirectivePath
if rejectInheritanceDirectives {
findDirective = overrideStructuralDirectivePath
}
if relative , ok := findDirective ( value , Path { } ) ; ok {
2026-09-12 15:58:06 +00:00
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 {
2026-10-23 21:02:46 +00:00
return preparedOverrideBatch { sources : allSources } , canonicalDiagnostics ( diagnostics )
2026-09-12 15:58:06 +00:00
}
2026-10-23 21:02:46 +00:00
return preparedOverrideBatch { overrides : prepared , sources : allSources } , nil
2026-09-12 15:58:06 +00:00
}
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
}
2026-10-09 03:45:43 +00:00
func validateOverridePath ( path Path , root * document . Node , lookup * documentLookup ) * Diagnostic {
2026-09-12 15:58:06 +00:00
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" }
}
2026-10-09 03:45:43 +00:00
position := lookup . find ( current , segment . key )
2026-09-12 15:58:06 +00:00
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
}
2026-10-23 21:02:46 +00:00
func pathContainsInheritanceDirective ( path Path ) bool {
for _ , segment := range path . segments {
if segment . key == inheritanceKey {
return true
}
}
return false
}
2026-09-12 15:58:06 +00:00
func appendOverridePath ( base , relative Path ) Path {
result := base . clone ( )
result . segments = append ( result . segments , clonePathSegments ( relative . segments , 0 ) ... )
return result
}
func overrideDirectivePath ( node * document . Node , path Path ) ( Path , bool ) {
2026-10-23 21:02:46 +00:00
return overrideDirectivePathWithInheritance ( node , path , false )
}
func overrideStructuralDirectivePath ( node * document . Node , path Path ) ( Path , bool ) {
return overrideDirectivePathWithInheritance ( node , path , true )
}
func overrideDirectivePathWithInheritance ( node * document . Node , path Path , rejectInheritanceDirectives bool ) ( Path , bool ) {
2026-09-12 15:58:06 +00:00
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
child := path . Key ( entry . Key )
2026-10-23 21:02:46 +00:00
if entry . Key == directiveKey || ( rejectInheritanceDirectives && entry . Key == inheritanceKey ) {
2026-09-12 15:58:06 +00:00
return child , true
}
2026-10-23 21:02:46 +00:00
if found , ok := overrideDirectivePathWithInheritance ( entry . Value , child , rejectInheritanceDirectives ) ; ok {
2026-09-12 15:58:06 +00:00
return found , true
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
2026-10-23 21:02:46 +00:00
if found , ok := overrideDirectivePathWithInheritance ( child , path . Index ( index ) , rejectInheritanceDirectives ) ; ok {
2026-09-12 15:58:06 +00:00
return found , true
}
}
}
return Path { } , false
}