2026-10-23 13:49:22 +00:00
package engine
import (
"bytes"
"encoding/binary"
"errors"
"math/big"
"sort"
"strings"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
var (
errEmptySequenceIdentityFields = errors . New ( "sequence identity requires at least one field" )
errInvalidSequenceIdentityNumber = errors . New ( "invalid sequence identity number" )
errUnsupportedSequenceIdentityValue = errors . New ( "unsupported sequence identity value" )
)
type sequenceIdentityDiagnosticCodes struct {
invalid Code
conflict Code
}
type sequenceIdentityRecord struct {
tuple string
index int
location document . Location
}
2026-10-24 00:12:31 +00:00
type sequenceIdentityContext uint8
const (
sequenceStructuralRecord sequenceIdentityContext = iota
sequencePayloadRecord
)
2026-10-23 13:49:22 +00:00
// normalizeSequenceIdentityFields treats identity field names as a set.
func normalizeSequenceIdentityFields ( fields [ ] string ) ( [ ] string , error ) {
if len ( fields ) == 0 {
return nil , errEmptySequenceIdentityFields
}
normalized := append ( [ ] string ( nil ) , fields ... )
sort . Strings ( normalized )
write := 0
for _ , field := range normalized {
if write != 0 && normalized [ write - 1 ] == field {
continue
}
normalized [ write ] = field
write ++
}
return normalized [ : write ] , nil
}
// normalizeSequenceIdentityNumber returns coefficient and exponent without
// expanding powers of ten. The represented value is coefficient * 10^exponent.
func normalizeSequenceIdentityNumber ( value string ) ( string , error ) {
if value == "" {
return "" , errInvalidSequenceIdentityNumber
}
negative := false
if value [ 0 ] == '-' {
negative = true
value = value [ 1 : ]
}
if value == "" {
return "" , errInvalidSequenceIdentityNumber
}
exponent := new ( big . Int )
if index := strings . IndexAny ( value , "eE" ) ; index >= 0 {
if strings . IndexAny ( value [ index + 1 : ] , "eE" ) >= 0 {
return "" , errInvalidSequenceIdentityNumber
}
text := value [ index + 1 : ]
if text == "" {
return "" , errInvalidSequenceIdentityNumber
}
if _ , ok := exponent . SetString ( text , 10 ) ; ! ok {
return "" , errInvalidSequenceIdentityNumber
}
value = value [ : index ]
}
point := strings . IndexByte ( value , '.' )
if point >= 0 && strings . IndexByte ( value [ point + 1 : ] , '.' ) >= 0 {
return "" , errInvalidSequenceIdentityNumber
}
integerPart := value
fractionPart := ""
if point >= 0 {
integerPart = value [ : point ]
fractionPart = value [ point + 1 : ]
}
if integerPart == "" || ( point >= 0 && fractionPart == "" ) {
return "" , errInvalidSequenceIdentityNumber
}
if len ( integerPart ) > 1 && integerPart [ 0 ] == '0' {
return "" , errInvalidSequenceIdentityNumber
}
digits := integerPart + fractionPart
for _ , digit := range digits {
if digit < '0' || digit > '9' {
return "" , errInvalidSequenceIdentityNumber
}
}
coefficient := strings . TrimLeft ( digits , "0" )
if coefficient == "" {
return "0e0" , nil
}
exponent . Sub ( exponent , big . NewInt ( int64 ( len ( fractionPart ) ) ) )
trimmed := strings . TrimRight ( coefficient , "0" )
exponent . Add ( exponent , big . NewInt ( int64 ( len ( coefficient ) - len ( trimmed ) ) ) )
coefficient = trimmed
if negative {
coefficient = "-" + coefficient
}
return coefficient + "e" + exponent . String ( ) , nil
}
func sequenceScalarIdentityComponent ( node * document . Node ) ( byte , string , error ) {
if node == nil {
return 0 , "" , errUnsupportedSequenceIdentityValue
}
switch node . Kind {
case document . String :
return 's' , node . Text , nil
case document . Number :
normalized , err := normalizeSequenceIdentityNumber ( node . Text )
return 'n' , normalized , err
case document . Boolean :
if node . Text != "true" && node . Text != "false" {
return 0 , "" , errUnsupportedSequenceIdentityValue
}
return 'b' , node . Text , nil
case document . Null :
return 'z' , "" , nil
default :
return 0 , "" , errUnsupportedSequenceIdentityValue
}
}
func sequenceIdentityTuple ( values [ ] * document . Node ) ( string , error ) {
var encoded bytes . Buffer
for _ , value := range values {
kind , component , err := sequenceScalarIdentityComponent ( value )
if err != nil {
return "" , err
}
encoded . WriteByte ( kind )
var length [ binary . MaxVarintLen64 ] byte
n := binary . PutUvarint ( length [ : ] , uint64 ( len ( component ) ) )
encoded . Write ( length [ : n ] )
encoded . WriteString ( component )
}
return encoded . String ( ) , nil
}
2026-10-24 00:12:31 +00:00
// sequenceMappingIndex is built once per record. Structural lookup excludes the
// directive's occurrence; effective payload and reference mappings keep all keys.
2026-10-23 13:49:22 +00:00
type sequenceMappingIndex struct {
entries [ ] document . Entry
byKey map [ string ] int
}
2026-10-24 00:12:31 +00:00
func newSequenceMappingIndex ( record * document . Node , context sequenceIdentityContext ) sequenceMappingIndex {
2026-10-23 13:49:22 +00:00
index := sequenceMappingIndex { byKey : make ( map [ string ] int ) }
if record == nil || record . Kind != document . Mapping {
return index
}
index . entries = record . Mapping
directiveIndex := - 1
2026-10-24 00:12:31 +00:00
if directive , exists := inheritanceDirective ( record ) ; context == sequenceStructuralRecord && exists {
2026-10-23 13:49:22 +00:00
for entryIndex := range record . Mapping {
if record . Mapping [ entryIndex ] . Key == directive . Key && record . Mapping [ entryIndex ] . KeyLocation == directive . KeyLocation {
directiveIndex = entryIndex
break
}
}
}
for entryIndex , entry := range record . Mapping {
if entryIndex == directiveIndex {
continue
}
if _ , exists := index . byKey [ entry . Key ] ; ! exists {
index . byKey [ entry . Key ] = entryIndex
}
}
return index
}
func ( index sequenceMappingIndex ) identity ( fields [ ] string ) ( string , [ ] document . Location , string , int ) {
values := make ( [ ] * document . Node , len ( fields ) )
locations := make ( [ ] document . Location , len ( fields ) )
for fieldIndex , field := range fields {
entryIndex , exists := index . byKey [ field ]
if ! exists {
return "" , nil , field , - 1
}
entry := index . entries [ entryIndex ]
if entry . Value == nil || ( entry . Value . Kind != document . String && entry . Value . Kind != document . Number && entry . Value . Kind != document . Boolean && entry . Value . Kind != document . Null ) {
return "" , nil , field , entryIndex
}
if _ , _ , err := sequenceScalarIdentityComponent ( entry . Value ) ; err != nil {
return "" , nil , field , entryIndex
}
values [ fieldIndex ] = entry . Value
locations [ fieldIndex ] = entry . ValueLocation
if entry . Value != nil && entry . Value . Location != ( document . Location { } ) {
locations [ fieldIndex ] = entry . Value . Location
}
}
tuple , err := sequenceIdentityTuple ( values )
if err != nil {
return "" , nil , fields [ 0 ] , - 2
}
return tuple , locations , "" , - 3
}
// validateSequenceIdentity checks mapping shape, direct identity fields, and
// uniqueness in one tuple-to-first-location index. It does not alter the nodes.
func validateSequenceIdentity ( sequence * document . Node , path Path , fields [ ] string , codes sequenceIdentityDiagnosticCodes ) ( map [ string ] sequenceIdentityRecord , [ ] Diagnostic ) {
2026-10-23 18:39:04 +00:00
return validateSequenceIdentityFrom ( sequence , path , fields , codes , 0 )
}
func validateSequenceIdentityFrom ( sequence * document . Node , path Path , fields [ ] string , codes sequenceIdentityDiagnosticCodes , startIndex int ) ( map [ string ] sequenceIdentityRecord , [ ] Diagnostic ) {
2026-10-23 13:49:22 +00:00
normalized , err := normalizeSequenceIdentityFields ( fields )
if err != nil {
location := ( * Location ) ( nil )
if sequence != nil {
converted := publicLocation ( sequence . Location )
location = & converted
}
return nil , [ ] Diagnostic { { Code : codes . invalid , Severity : SeverityError , Path : path , Message : "sequence identity requires at least one field" , Primary : location } }
}
2026-10-24 00:12:31 +00:00
return validateNormalizedSequenceIdentityFrom ( sequence , path , normalized , codes , startIndex , sequenceStructuralRecord )
2026-10-23 18:39:04 +00:00
}
2026-10-24 00:12:31 +00:00
func validateNormalizedSequenceIdentityFrom ( sequence * document . Node , path Path , normalized [ ] string , codes sequenceIdentityDiagnosticCodes , startIndex int , context sequenceIdentityContext ) ( map [ string ] sequenceIdentityRecord , [ ] Diagnostic ) {
2026-10-23 13:49:22 +00:00
if sequence == nil || sequence . Kind != document . Sequence {
location := ( * Location ) ( nil )
if sequence != nil {
converted := publicLocation ( sequence . Location )
location = & converted
}
return nil , [ ] Diagnostic { { Code : codes . invalid , Severity : SeverityError , Path : path , Message : "identified collection must be a sequence" , Primary : location } }
}
firstByTuple := make ( map [ string ] sequenceIdentityRecord , len ( sequence . Sequence ) )
for itemIndex , item := range sequence . Sequence {
2026-10-23 18:39:04 +00:00
itemPath := path . Index ( startIndex + itemIndex )
2026-10-23 13:49:22 +00:00
if item == nil || item . Kind != document . Mapping {
var primary * Location
if item != nil {
converted := publicLocation ( item . Location )
primary = & converted
}
return nil , [ ] Diagnostic { { Code : codes . invalid , Severity : SeverityError , Path : itemPath , Message : "identified collection items must be mappings" , Primary : primary } }
}
2026-10-24 00:12:31 +00:00
mapping := newSequenceMappingIndex ( item , context )
2026-10-23 13:49:22 +00:00
tuple , locations , missing , entryIndex := mapping . identity ( normalized )
if entryIndex == - 1 {
location := publicLocation ( item . Location )
return nil , [ ] Diagnostic { { Code : codes . invalid , Severity : SeverityError , Path : itemPath . Key ( missing ) , Message : "identified collection item is missing an identity field" , Primary : & location } }
}
if entryIndex == - 2 || entryIndex >= 0 {
location := publicLocation ( item . Location )
fieldPath := itemPath . Key ( missing )
if entryIndex >= 0 {
location = publicLocation ( item . Mapping [ entryIndex ] . ValueLocation )
if value := item . Mapping [ entryIndex ] . Value ; value != nil {
location = publicLocation ( value . Location )
}
}
return nil , [ ] Diagnostic { { Code : codes . invalid , Severity : SeverityError , Path : fieldPath , Message : "identity fields must be scalar values" , Primary : & location } }
}
if first , duplicate := firstByTuple [ tuple ] ; duplicate {
current := publicLocation ( locations [ 0 ] )
return nil , [ ] Diagnostic { { Code : codes . conflict , Severity : SeverityError , Path : path , Message : "identified collection contains duplicate identities" , Primary : ptrLocation ( publicLocation ( first . location ) ) , Related : [ ] Location { current } } }
}
firstByTuple [ tuple ] = sequenceIdentityRecord { tuple : tuple , index : itemIndex , location : locations [ 0 ] }
}
return firstByTuple , nil
}
func ptrLocation ( location Location ) * Location { return & location }