157 lines
5.2 KiB
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}
|
|
}
|