mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-22 17:51:28 +00:00
Add ListSelector in validation Context
We already have "Member" which is a way to "forward" information from stem validators (each, subfield, item) to leaf validators. That works for fields in a struct but not items in a list. Now we have the list selection criteria. This simplifies union a bit and gets rid of the need to regex parse the field path.
This commit is contained in:
@@ -414,8 +414,10 @@ func (evtv eachValTagValidator) GetValidations(context Context, tag codetags.Tag
|
||||
switch nt.Kind {
|
||||
case types.Slice, types.Array:
|
||||
elemContext.Scope = ScopeListVal
|
||||
elemContext.ListSelector = []ListSelectorTerm{} // empty == "all"
|
||||
case types.Map:
|
||||
elemContext.Scope = ScopeMapVal
|
||||
// TODO: We may need map selectors at some point.
|
||||
}
|
||||
if tag.ValueTag == nil {
|
||||
return Validations{}, fmt.Errorf("missing validation tag")
|
||||
|
||||
@@ -256,13 +256,15 @@ func (iv itemValidator) GetValidations(context Context) (Validations, error) {
|
||||
}
|
||||
|
||||
// Extract validations from the stored tag
|
||||
subContextPath := generateFieldPathForMap(item.criteria)
|
||||
itemKey := generateFieldPathForMap(item.criteria)
|
||||
itemPath := context.Path.Key(itemKey)
|
||||
itemSelector := generateSelector(item.criteria)
|
||||
subContext := Context{
|
||||
Scope: ScopeListVal,
|
||||
Type: elemT,
|
||||
Path: context.Path.Key(subContextPath),
|
||||
Member: nil, // NA for list items
|
||||
ParentPath: context.Path,
|
||||
Scope: ScopeListVal,
|
||||
Type: elemT,
|
||||
Path: itemPath,
|
||||
ListSelector: itemSelector,
|
||||
ParentPath: context.Path,
|
||||
}
|
||||
|
||||
validations, err := iv.validator.ExtractValidations(subContext, item.valueTag)
|
||||
@@ -409,3 +411,14 @@ func formatValueForPath(value any) string {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func generateSelector(criteria []keyValuePair) []ListSelectorTerm {
|
||||
terms := make([]ListSelectorTerm, len(criteria))
|
||||
for i, pair := range criteria {
|
||||
terms[i] = ListSelectorTerm{
|
||||
Field: pair.key,
|
||||
Value: pair.value,
|
||||
}
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package validators
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
@@ -193,7 +192,7 @@ type union struct {
|
||||
// key is the virtual field path (eg: "<path>/Pipeline.Tasks[{"name": "succeeded"}]"),
|
||||
// value is the parsed matcher map (eg: {"name": "succeeded"}).
|
||||
// Represents union members that are list items matching specific criteria
|
||||
itemMatchers map[string]map[string]any
|
||||
itemMatchers map[string][]ListSelectorTerm
|
||||
}
|
||||
|
||||
type unionMember struct {
|
||||
@@ -207,7 +206,8 @@ type unions map[string]*union
|
||||
// newUnion initializes a new union instance
|
||||
func newUnion() *union {
|
||||
return &union{
|
||||
itemMatchers: make(map[string]map[string]any),
|
||||
// slice fields can be nil
|
||||
itemMatchers: make(map[string][]ListSelectorTerm),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,12 +332,12 @@ func createMemberExtractor(ptrType *types.Type, member *types.Member) FunctionLi
|
||||
|
||||
// createItemExtractor creates an extractor function for list item union members.
|
||||
// It generates code that loops through the list to check if an item matching the criteria exists.
|
||||
func createItemExtractor(listType *types.Type, elemType *types.Type, matcher map[string]any) (FunctionLiteral, error) {
|
||||
func createItemExtractor(listType *types.Type, elemType *types.Type, matcher []ListSelectorTerm) (FunctionLiteral, error) {
|
||||
var criteria []keyValuePair
|
||||
for key, value := range matcher {
|
||||
for _, term := range matcher {
|
||||
criteria = append(criteria, keyValuePair{
|
||||
key: key,
|
||||
value: fmt.Sprint(value),
|
||||
key: term.Field,
|
||||
value: fmt.Sprint(term.Value),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -354,12 +354,13 @@ func createItemExtractor(listType *types.Type, elemType *types.Type, matcher map
|
||||
extractor := FunctionLiteral{
|
||||
Parameters: []ParamResult{{Name: "list", Type: listType}},
|
||||
Results: []ParamResult{{Type: types.Bool}},
|
||||
Body: fmt.Sprintf(`for i := range list {
|
||||
if %s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false`, condition),
|
||||
Body: fmt.Sprintf(
|
||||
`for i := range list {
|
||||
if %s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false`, condition),
|
||||
}
|
||||
|
||||
return extractor, nil
|
||||
@@ -433,11 +434,13 @@ func processMemberValidations(shared map[string]unions, context Context, tag cod
|
||||
u.fields = append(u.fields, unionMember{fieldName, memberName})
|
||||
|
||||
if context.Scope == ScopeListVal {
|
||||
matcher, err := extractMatcherFromPath(fieldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract matcher from path %s: %w", fieldName, err)
|
||||
if context.ListSelector == nil {
|
||||
return fmt.Errorf("list-item union member has no list selector in context")
|
||||
}
|
||||
u.itemMatchers[fieldName] = matcher
|
||||
if _, found := u.itemMatchers[fieldName]; found {
|
||||
return fmt.Errorf("list-item union member %q already exists", fieldName)
|
||||
}
|
||||
u.itemMatchers[fieldName] = context.ListSelector
|
||||
} else {
|
||||
u.fieldMembers = append(u.fieldMembers, context.Member)
|
||||
}
|
||||
@@ -481,18 +484,3 @@ func sanitizeName(name string) string {
|
||||
re := regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
return re.ReplaceAllString(name, "_")
|
||||
}
|
||||
|
||||
// extractMatcherFromPath extracts the matcher criteria from a path like "Pipeline.Tasks[{"name": "succeeded"}]"
|
||||
func extractMatcherFromPath(path string) (map[string]any, error) {
|
||||
re := regexp.MustCompile(`\[({.*?})\]`)
|
||||
matches := re.FindStringSubmatch(path)
|
||||
if len(matches) < 2 {
|
||||
return nil, fmt.Errorf("no matcher criteria found in path")
|
||||
}
|
||||
|
||||
var matcher map[string]any
|
||||
if err := json.Unmarshal([]byte(matches[1]), &matcher); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse matcher JSON: %w", err)
|
||||
}
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user