2026-10-23 14:01:39 +00:00
package engine
2026-11-01 14:19:37 +00:00
import "go.maximumdirect.net/configkit/internal/document"
2026-10-23 14:01:39 +00:00
type sequenceMode uint8
const (
sequenceModeReplace sequenceMode = iota + 1
sequenceModeAppend
sequenceModeUpsert
)
type sequenceDeclarationSpec struct {
2026-10-25 22:18:12 +00:00
path Path
location Location
destination Path
identityFields [ ] string
identityPresent bool
identityLocation * Location
mode sequenceMode
modePresent bool
modeLocation * Location
2026-10-23 14:01:39 +00:00
}
func parseSequenceDeclarationDirective ( node * document . Node , directivePath Path ) ( [ ] sequenceDeclarationSpec , [ ] Diagnostic ) {
if node == nil || node . Kind != document . Sequence {
return nil , [ ] Diagnostic { sequenceDeclarationDiagnostic ( directivePath , "sequences must be a sequence" , node ) }
}
declarations := make ( [ ] sequenceDeclarationSpec , 0 , len ( node . Sequence ) )
var diagnostics [ ] Diagnostic
for index , declarationNode := range node . Sequence {
declaration , issues := parseSequenceDeclarationSpec ( declarationNode , directivePath . Index ( index ) )
if len ( issues ) != 0 {
diagnostics = append ( diagnostics , issues ... )
continue
}
declarations = append ( declarations , declaration )
}
return declarations , canonicalDiagnostics ( diagnostics )
}
func parseSequenceDeclarationSpec ( node * document . Node , declarationPath Path ) ( sequenceDeclarationSpec , [ ] Diagnostic ) {
if node == nil || node . Kind != document . Mapping {
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( declarationPath , "sequence declaration must be a mapping" , node ) }
}
declaration := sequenceDeclarationSpec {
path : declarationPath . clone ( ) ,
location : publicLocation ( node . Location ) ,
}
destinationPresent := false
for _ , entry := range node . Mapping {
fieldPath := declarationPath . Key ( entry . Key )
switch entry . Key {
case "destination" :
destinationPresent = true
destination , diagnostic := parseImportDestination ( entry . Value , fieldPath )
if diagnostic != nil {
diagnostic . Code = CodeSequenceInvalid
return sequenceDeclarationSpec { } , [ ] Diagnostic { * diagnostic }
}
declaration . destination = destination
case "key" :
fields , locations , errIndex , valid := parseSequenceIdentityFieldSet ( entry . Value )
if ! valid {
badPath := fieldPath
badNode := entry . Value
message := "key must be a string or nonempty list of strings"
if errIndex >= 0 && entry . Value != nil {
badPath = fieldPath . Index ( errIndex )
if errIndex < len ( entry . Value . Sequence ) {
badNode = entry . Value . Sequence [ errIndex ]
}
}
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( badPath , message , badNode ) }
}
declaration . identityFields = fields
declaration . identityPresent = true
location := publicLocation ( entry . Value . Location )
if len ( locations ) != 0 {
location = publicLocation ( locations [ 0 ] )
}
declaration . identityLocation = & location
case "mode" :
if entry . Value == nil || entry . Value . Kind != document . String {
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( fieldPath , "mode must be replace, append, or upsert" , entry . Value ) }
}
mode , ok := parseSequenceMode ( entry . Value . Text )
if ! ok {
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( fieldPath , "mode must be replace, append, or upsert" , entry . Value ) }
}
declaration . mode = mode
declaration . modePresent = true
location := publicLocation ( entry . Value . Location )
declaration . modeLocation = & location
default :
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( fieldPath , "sequence declaration field is not recognized" , entry . Value ) }
}
}
if ! destinationPresent {
return sequenceDeclarationSpec { } , [ ] Diagnostic { sequenceDeclarationDiagnostic ( declarationPath . Key ( "destination" ) , "sequence declaration requires a destination" , node ) }
}
return declaration , nil
}
func parseSequenceIdentityFieldSet ( node * document . Node ) ( [ ] string , [ ] document . Location , int , bool ) {
if node == nil {
return nil , nil , - 1 , false
}
var fields [ ] string
var locations [ ] document . Location
switch node . Kind {
case document . String :
fields = [ ] string { node . Text }
locations = [ ] document . Location { node . Location }
case document . Sequence :
if len ( node . Sequence ) == 0 {
return nil , nil , - 1 , false
}
for index , member := range node . Sequence {
if member == nil || member . Kind != document . String {
return nil , nil , index , false
}
fields = append ( fields , member . Text )
locations = append ( locations , member . Location )
}
default :
return nil , nil , - 1 , false
}
normalized , err := normalizeSequenceIdentityFields ( fields )
if err != nil {
return nil , nil , - 1 , false
}
return normalized , locations , - 1 , true
}
func parseSequenceMode ( value string ) ( sequenceMode , bool ) {
switch value {
case "replace" :
return sequenceModeReplace , true
case "append" :
return sequenceModeAppend , true
case "upsert" :
return sequenceModeUpsert , true
default :
return 0 , false
}
}
func sequenceDeclarationDiagnostic ( path Path , message string , node * document . Node ) Diagnostic {
var primary * Location
if node != nil {
location := publicLocation ( node . Location )
primary = & location
}
return Diagnostic { Code : CodeSequenceInvalid , Severity : SeverityError , Path : path , Message : message , Primary : primary }
}