166 lines
4.4 KiB
Go
166 lines
4.4 KiB
Go
package topdata
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
type globalCondition struct {
|
|
Field string
|
|
ID string
|
|
}
|
|
|
|
type globalConditionGroups struct {
|
|
RequirePresent []globalCondition
|
|
AnyPresent []globalCondition
|
|
UnlessPresent []globalCondition
|
|
}
|
|
|
|
var (
|
|
baseOrPlainRootKeys = []string{
|
|
"output", "key", "columns", "rows", "compare_reference",
|
|
}
|
|
canonicalModuleRootKeys = []string{
|
|
"output", "key", "columns", "entries", "overrides", "rows",
|
|
}
|
|
globalRootKeys = []string{
|
|
"columns", "entries", "overrides", "defaults", "position", "injections",
|
|
}
|
|
globalInjectionKeys = []string{
|
|
"row", "require_present", "any_present", "unless_present",
|
|
}
|
|
globalConditionKeys = []string{"field", "id"}
|
|
globalDefaultRuleKeys = []string{"match", "values"}
|
|
globalDefaultMatchKeys = []string{"source"}
|
|
globalDefaultFormatKeys = []string{"format"}
|
|
)
|
|
|
|
func unsupportedObjectKeys(obj map[string]any, supported ...string) []string {
|
|
if len(obj) == 0 {
|
|
return nil
|
|
}
|
|
supportedSet := make(map[string]struct{}, len(supported))
|
|
for _, key := range supported {
|
|
supportedSet[key] = struct{}{}
|
|
}
|
|
unsupported := make([]string, 0, len(obj))
|
|
for key := range obj {
|
|
if _, ok := supportedSet[key]; ok {
|
|
continue
|
|
}
|
|
unsupported = append(unsupported, key)
|
|
}
|
|
slices.Sort(unsupported)
|
|
return unsupported
|
|
}
|
|
|
|
func unsupportedObjectKeysError(label string, obj map[string]any, supported ...string) error {
|
|
unsupported := unsupportedObjectKeys(obj, supported...)
|
|
if len(unsupported) == 0 {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s contains unsupported key %q (supported keys: %s)", label, unsupported[0], strings.Join(supported, ", "))
|
|
}
|
|
|
|
func parseGlobalConditionGroups(injection map[string]any) (globalConditionGroups, error) {
|
|
if injection == nil {
|
|
return globalConditionGroups{}, nil
|
|
}
|
|
if err := unsupportedObjectKeysError("global injection", injection, globalInjectionKeys...); err != nil {
|
|
return globalConditionGroups{}, err
|
|
}
|
|
|
|
var groups globalConditionGroups
|
|
var err error
|
|
|
|
if raw, ok := injection["require_present"]; ok {
|
|
groups.RequirePresent, err = parseGlobalConditionList("require_present", raw, false)
|
|
if err != nil {
|
|
return globalConditionGroups{}, err
|
|
}
|
|
}
|
|
if raw, ok := injection["any_present"]; ok {
|
|
groups.AnyPresent, err = parseGlobalConditionList("any_present", raw, true)
|
|
if err != nil {
|
|
return globalConditionGroups{}, err
|
|
}
|
|
}
|
|
if raw, ok := injection["unless_present"]; ok {
|
|
groups.UnlessPresent, err = parseGlobalConditionList("unless_present", raw, false)
|
|
if err != nil {
|
|
return globalConditionGroups{}, err
|
|
}
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
func parseGlobalConditionList(name string, raw any, requireNonEmpty bool) ([]globalCondition, error) {
|
|
if raw == nil {
|
|
return nil, fmt.Errorf("%s must be an array", name)
|
|
}
|
|
|
|
conditions, ok := raw.([]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s must be an array", name)
|
|
}
|
|
if requireNonEmpty && len(conditions) == 0 {
|
|
return nil, fmt.Errorf("%s must contain at least one condition", name)
|
|
}
|
|
|
|
parsed := make([]globalCondition, 0, len(conditions))
|
|
for index, rawCondition := range conditions {
|
|
condition, ok := rawCondition.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s[%d] must be an object", name, index)
|
|
}
|
|
if err := unsupportedObjectKeysError(fmt.Sprintf("%s[%d]", name, index), condition, globalConditionKeys...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
field, _ := condition["field"].(string)
|
|
field = strings.TrimSpace(field)
|
|
if field == "" {
|
|
return nil, fmt.Errorf("%s[%d].field is required", name, index)
|
|
}
|
|
|
|
id, _ := condition["id"].(string)
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return nil, fmt.Errorf("%s[%d].id is required", name, index)
|
|
}
|
|
|
|
parsed = append(parsed, globalCondition{Field: field, ID: id})
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func globalConditionGroupsMatch(groups globalConditionGroups, rows []map[string]any) bool {
|
|
for _, condition := range groups.RequirePresent {
|
|
if !globalReferencePresent(rows, condition.Field, condition.ID) {
|
|
return false
|
|
}
|
|
}
|
|
if groups.AnyPresent != nil {
|
|
if len(groups.AnyPresent) == 0 {
|
|
return false
|
|
}
|
|
matched := false
|
|
for _, condition := range groups.AnyPresent {
|
|
if globalReferencePresent(rows, condition.Field, condition.ID) {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if !matched {
|
|
return false
|
|
}
|
|
}
|
|
for _, condition := range groups.UnlessPresent {
|
|
if globalReferencePresent(rows, condition.Field, condition.ID) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|