Files
ConfigKit/internal/engine/sequence_declaration.go

157 lines
5.2 KiB
Go

package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type sequenceMode uint8
const (
sequenceModeReplace sequenceMode = iota + 1
sequenceModeAppend
sequenceModeUpsert
)
type sequenceDeclarationSpec struct {
path Path
location Location
destination Path
identityFields []string
identityPresent bool
identityLocation *Location
mode sequenceMode
modePresent bool
modeLocation *Location
}
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}
}