Parse collection sequence declarations
This commit is contained in:
158
internal/engine/sequence_declaration.go
Normal file
158
internal/engine/sequence_declaration.go
Normal file
@@ -0,0 +1,158 @@
|
||||
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
|
||||
destinationLocation Location
|
||||
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
|
||||
declaration.destinationLocation = publicLocation(entry.Value.Location)
|
||||
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}
|
||||
}
|
||||
Reference in New Issue
Block a user