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} }