mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 12:32:03 +00:00
Merge pull request #119453 from cici37/addTest
Refactor jsonpath parser and add tests
This commit is contained in:
commit
5e8dfe5d8b
@ -1092,9 +1092,6 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
||||
allErrs.SchemaErrors = append(allErrs.SchemaErrors, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("fieldPath"), rule.FieldPath, "fieldPath must not contain line breaks"))
|
||||
}
|
||||
if len(rule.FieldPath) > 0 {
|
||||
if errs := validateSimpleJSONPath(rule.FieldPath, fldPath.Child("x-kubernetes-validations").Index(i).Child("fieldPath")); len(errs) > 0 {
|
||||
allErrs.SchemaErrors = append(allErrs.SchemaErrors, errs...)
|
||||
}
|
||||
if !pathValid(schema, rule.FieldPath) {
|
||||
allErrs.SchemaErrors = append(allErrs.SchemaErrors, field.Invalid(fldPath.Child("x-kubernetes-validations").Index(i).Child("fieldPath"), rule.FieldPath, "fieldPath must be a valid path"))
|
||||
}
|
||||
@ -1163,7 +1160,7 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
||||
func pathValid(schema *apiextensions.JSONSchemaProps, path string) bool {
|
||||
// To avoid duplicated code and better maintain, using ValidaFieldPath func to check if the path is valid
|
||||
if ss, err := structuralschema.NewStructural(schema); err == nil {
|
||||
_, err := cel.ValidFieldPath(path, nil, ss)
|
||||
_, err := cel.ValidFieldPath(path, ss)
|
||||
return err == nil
|
||||
}
|
||||
return true
|
||||
|
@ -76,13 +76,6 @@ func (v validationMatch) matches(err *field.Error) bool {
|
||||
return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.contains)
|
||||
}
|
||||
|
||||
func (v validationMatch) empty() bool {
|
||||
if v.path == nil && v.errorType == "" && v.contains == "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
func TestValidateCustomResourceDefinition(t *testing.T) {
|
||||
@ -4140,18 +4133,45 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
||||
Type: "object",
|
||||
XValidations: apiextensions.ValidationRules{
|
||||
{
|
||||
Rule: "self.a.b.c > 0.0",
|
||||
FieldPath: ".a.b.c",
|
||||
Rule: "true",
|
||||
FieldPath: ".foo['b.c']['c\\a']",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: "['a.c']",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: ".a.c",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: ".list[0]",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: " ",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: ".",
|
||||
},
|
||||
{
|
||||
Rule: "true",
|
||||
FieldPath: "..",
|
||||
},
|
||||
},
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"a": {
|
||||
"a.c": {
|
||||
Type: "number",
|
||||
},
|
||||
"foo": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"b": {
|
||||
"b.c": {
|
||||
Type: "object",
|
||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||
"c": {
|
||||
"c\a": {
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
@ -4179,7 +4199,14 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
||||
StoredVersions: []string{"version"},
|
||||
},
|
||||
},
|
||||
errors: []validationMatch{},
|
||||
errors: []validationMatch{
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[2]", "fieldPath"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[3]", "fieldPath"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[4]", "fieldPath"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[5]", "fieldPath"),
|
||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-validations[6]", "fieldPath"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "x-kubernetes-validations have invalid fieldPath",
|
||||
@ -4368,168 +4395,156 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath string
|
||||
pathOfFieldPath *field.Path
|
||||
schema *apiextensions.JSONSchemaProps
|
||||
error validationMatch
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid .a",
|
||||
fieldPath: ".a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .a.b",
|
||||
fieldPath: ".a.bbb",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .foo.f1",
|
||||
fieldPath: ".foo.f1",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Invalid map syntax .a.b",
|
||||
fieldPath: ".a['bbb']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .a['bbb.c']",
|
||||
fieldPath: ".a['bbb.c']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Invalid .a['bbb.c'].a-b34",
|
||||
name: "Valid .a['bbb.c'].a-b34",
|
||||
fieldPath: ".a['bbb.c'].a-b34",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
},
|
||||
{
|
||||
name: "Valid .a['bbb.c']['a-b34']",
|
||||
fieldPath: ".a['bbb.c']['a-b34']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .a.bbb.c",
|
||||
fieldPath: ".a.bbb.c",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .a.bbb.34",
|
||||
fieldPath: ".a.bbb['34']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Invalid map key",
|
||||
fieldPath: ".a.foo",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Malformed map key",
|
||||
fieldPath: ".a.bbb[0]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "expected single quoted string but got 0",
|
||||
},
|
||||
{
|
||||
name: "Special field names",
|
||||
name: "number in field names",
|
||||
fieldPath: ".a.bbb.34",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
},
|
||||
{
|
||||
name: "Special field names",
|
||||
fieldPath: ".a.bbb['34']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Valid .list",
|
||||
fieldPath: ".list",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: validationMatch{},
|
||||
},
|
||||
{
|
||||
name: "Invalid .list[1]",
|
||||
fieldPath: ".list[1]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "expected single quoted string but got 1",
|
||||
},
|
||||
{
|
||||
name: "Unsopported .list.a",
|
||||
fieldPath: ".list.a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Unsupported .list['a-b.34']",
|
||||
fieldPath: ".list['a-b.34']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Invalid .list.a-b.34",
|
||||
fieldPath: ".list.a-b.34",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Missing leading dot",
|
||||
fieldPath: "a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "expected [ or . but got: a",
|
||||
},
|
||||
{
|
||||
name: "Nonexistent field",
|
||||
fieldPath: ".c",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Duplicate dots",
|
||||
fieldPath: ".a..b",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Negative array index",
|
||||
fieldPath: ".list[-1]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "expected single quoted string but got -1",
|
||||
},
|
||||
{
|
||||
name: "Floating-point array index",
|
||||
fieldPath: ".list[1.0]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &schema,
|
||||
error: invalid(path.String()),
|
||||
errMsg: "expected single quoted string but got 1",
|
||||
},
|
||||
}
|
||||
|
||||
@ -4539,13 +4554,15 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("error when converting schema to structural schema: %v", err)
|
||||
}
|
||||
_, e := celschema.ValidFieldPath(tc.fieldPath, tc.pathOfFieldPath, ss)
|
||||
if e == nil && !tc.error.empty() {
|
||||
t.Errorf("expected %v at %v but got nil", tc.error.errorType, tc.error.path.String())
|
||||
} else if e != nil && tc.error.empty() {
|
||||
t.Errorf("unexpected error: %v at %v", tc.error.errorType, tc.error.path.String())
|
||||
} else if !tc.error.empty() && !tc.error.matches(e) {
|
||||
t.Errorf("expected %v at %v, got %v", tc.error.errorType, tc.error.path.String(), err)
|
||||
_, err = celschema.ValidFieldPath(tc.fieldPath, ss)
|
||||
if err == nil && tc.errMsg != "" {
|
||||
t.Errorf("expected err contains: %v but get nil", tc.errMsg)
|
||||
}
|
||||
if err != nil && tc.errMsg == "" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), tc.errMsg) {
|
||||
t.Errorf("expected error to contain: %v, but get: %v", tc.errMsg, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ func compileRule(s *schema.Structural, rule apiextensions.ValidationRule, envSet
|
||||
compilationResult.MessageExpressionMaxCost = costEst.Max
|
||||
}
|
||||
if rule.FieldPath != "" {
|
||||
validFieldPath, err := ValidFieldPath(rule.FieldPath, nil, s)
|
||||
validFieldPath, err := ValidFieldPath(rule.FieldPath, s)
|
||||
if err == nil {
|
||||
compilationResult.NormalizedRuleFieldPath = validFieldPath.String()
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ limitations under the License.
|
||||
package cel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
"time"
|
||||
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
@ -322,101 +322,117 @@ func unescapeSingleQuote(s string) (string, error) {
|
||||
return unescaped, err
|
||||
}
|
||||
|
||||
// ValidFieldPath returns a valid field path.
|
||||
func ValidFieldPath(fieldPath string, pathOfFieldPath *field.Path, schema *schema.Structural) (validFieldPath *field.Path, err *field.Error) {
|
||||
validFieldPath = pathOfFieldPath
|
||||
if len(fieldPath) == 0 {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "must not be empty")
|
||||
}
|
||||
|
||||
invalidFieldError := field.Invalid(pathOfFieldPath, fieldPath, "does not refer to an valid field")
|
||||
|
||||
var s scanner.Scanner
|
||||
s.Init(strings.NewReader(fieldPath))
|
||||
s.Filename = pathOfFieldPath.String()
|
||||
s.Mode = scanner.ScanInts | scanner.ScanIdents | scanner.ScanChars | scanner.ScanStrings
|
||||
s.Error = func(s *scanner.Scanner, msg string) {
|
||||
field.Invalid(pathOfFieldPath, fieldPath, fmt.Sprintf("failed to parse JSON Path: %s", msg))
|
||||
}
|
||||
|
||||
found := false
|
||||
for true {
|
||||
tok := s.Scan()
|
||||
if tok == scanner.EOF {
|
||||
found = true
|
||||
break
|
||||
// ValidFieldPath validates that jsonPath is a valid JSON Path containing only field and map accessors
|
||||
// that are valid for the given schema, and returns a field.Path representation of the validated jsonPath or an error.
|
||||
func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath *field.Path, err error) {
|
||||
appendToPath := func(name string, isNamed bool) error {
|
||||
if !isNamed {
|
||||
validFieldPath = validFieldPath.Key(name)
|
||||
schema = schema.AdditionalProperties.Structural
|
||||
} else {
|
||||
validFieldPath = validFieldPath.Child(name)
|
||||
val, ok := schema.Properties[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
schema = &val
|
||||
}
|
||||
switch schema.Type {
|
||||
case "object":
|
||||
isMapSyntax := false
|
||||
if s.TokenText() == "[" {
|
||||
isMapSyntax = true
|
||||
} else if s.TokenText() != "." {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "expected [ or . but got: "+s.TokenText())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tok = s.Scan()
|
||||
if tok == scanner.EOF {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "unexpected end of JSON path")
|
||||
}
|
||||
fieldName := s.TokenText()
|
||||
if isMapSyntax {
|
||||
if tok == scanner.Char {
|
||||
newS := fieldName[1 : len(fieldName)-1]
|
||||
newS, err := unescapeSingleQuote(newS)
|
||||
if err != nil {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, fmt.Sprintf("failed to unescape: %v", err))
|
||||
validFieldPath = nil
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(jsonPath))
|
||||
|
||||
// configure the scanner to split the string into tokens.
|
||||
// The three delimiters ('.', '[', ']') will be returned as single char tokens.
|
||||
// All other text between delimiters is returned as string tokens.
|
||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if len(data) > 0 {
|
||||
for i := 0; i < len(data); i++ {
|
||||
// If in a single quoted string, look for the end of string
|
||||
// ignoring delimiters.
|
||||
if data[0] == '\'' {
|
||||
if i > 0 && data[i] == '\'' && data[i-1] != '\\' {
|
||||
// Return quoted string
|
||||
return i + 1, data[:i+1], nil
|
||||
}
|
||||
fieldName = newS
|
||||
if schema.AdditionalProperties != nil {
|
||||
validFieldPath = validFieldPath.Key(fieldName)
|
||||
continue
|
||||
}
|
||||
switch data[i] {
|
||||
case '.', '[', ']': // delimiters
|
||||
if i == 0 {
|
||||
// Return the delimiter.
|
||||
return 1, data[:1], nil
|
||||
} else {
|
||||
validFieldPath = validFieldPath.Child(fieldName)
|
||||
// Return identifier leading up to the delimiter.
|
||||
// The next call to split will return the delimiter.
|
||||
return i, data[:i], nil
|
||||
}
|
||||
} else {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "unexpected format of fieldName: "+fieldName)
|
||||
}
|
||||
} else if tok != scanner.Ident {
|
||||
return pathOfFieldPath, invalidFieldError
|
||||
} else {
|
||||
if schema.AdditionalProperties != nil {
|
||||
validFieldPath = validFieldPath.Key(fieldName)
|
||||
} else {
|
||||
validFieldPath = validFieldPath.Child(fieldName)
|
||||
}
|
||||
}
|
||||
if atEOF {
|
||||
// Return the string.
|
||||
return len(data), data, nil
|
||||
}
|
||||
}
|
||||
return 0, nil, nil
|
||||
})
|
||||
|
||||
var tok string
|
||||
var isNamed bool
|
||||
for scanner.Scan() {
|
||||
tok = scanner.Text()
|
||||
switch tok {
|
||||
case "[":
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if len(tok) < 2 || tok[0] != '\'' || tok[len(tok)-1] != '\'' {
|
||||
return nil, fmt.Errorf("expected single quoted string but got %s", tok)
|
||||
}
|
||||
unescaped, err := unescapeSingleQuote(tok[1 : len(tok)-1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid string literal: %v", err)
|
||||
}
|
||||
|
||||
if schema.Properties != nil {
|
||||
propertySchema, ok := schema.Properties[fieldName]
|
||||
if ok {
|
||||
schema = &propertySchema
|
||||
} else {
|
||||
return pathOfFieldPath, invalidFieldError
|
||||
}
|
||||
isNamed = true
|
||||
} else if schema.AdditionalProperties != nil {
|
||||
schema = schema.AdditionalProperties.Structural
|
||||
isNamed = false
|
||||
} else {
|
||||
return pathOfFieldPath, invalidFieldError
|
||||
return nil, fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
|
||||
if isMapSyntax {
|
||||
if tok == scanner.EOF {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "unexpected end of JSON path")
|
||||
}
|
||||
s.Scan()
|
||||
if s.TokenText() != "]" {
|
||||
return pathOfFieldPath, field.Invalid(pathOfFieldPath, fieldPath, "expect ] but get: "+s.TokenText())
|
||||
}
|
||||
if err := appendToPath(unescaped, isNamed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if tok != "]" {
|
||||
return nil, fmt.Errorf("expected ] but got %s", tok)
|
||||
}
|
||||
case ".":
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("unexpected end of JSON path")
|
||||
}
|
||||
tok = scanner.Text()
|
||||
if schema.Properties != nil {
|
||||
isNamed = true
|
||||
} else if schema.AdditionalProperties != nil {
|
||||
isNamed = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("does not refer to a valid field")
|
||||
}
|
||||
if err := appendToPath(tok, isNamed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return pathOfFieldPath, invalidFieldError
|
||||
return nil, fmt.Errorf("expected [ or . but got: %s", tok)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return pathOfFieldPath, invalidFieldError
|
||||
}
|
||||
|
||||
return validFieldPath, nil
|
||||
}
|
||||
|
||||
|
@ -2634,7 +2634,7 @@ func TestReasonAndFldPath(t *testing.T) {
|
||||
},
|
||||
},
|
||||
schema: withRulePtr(objectTypePtr(map[string]schema.Structural{
|
||||
"f": withReasonAndFldPath(objectType(map[string]schema.Structural{"m": integerType}), "self.m == 2", ". m", forbiddenReason),
|
||||
"f": withReasonAndFldPath(objectType(map[string]schema.Structural{"m": integerType}), "self.m == 2", ".m", forbiddenReason),
|
||||
}), "1 == 1"),
|
||||
errorType: field.ErrorTypeForbidden,
|
||||
errors: []string{"root.f.m: Forbidden"},
|
||||
@ -2701,6 +2701,16 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"white space": {
|
||||
Generic: schema.Generic{
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
"'foo'bar": {
|
||||
Generic: schema.Generic{
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
"a": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
@ -2716,6 +2726,11 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
"bb[b": {
|
||||
Generic: schema.Generic{
|
||||
Type: "number",
|
||||
},
|
||||
},
|
||||
"bbb": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
@ -2779,39 +2794,89 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath string
|
||||
pathOfFieldPath *field.Path
|
||||
schema *schema.Structural
|
||||
error validationMatch
|
||||
errDetail string
|
||||
validFieldPath *field.Path
|
||||
}{
|
||||
{
|
||||
name: "Valid .a",
|
||||
fieldPath: ". a ",
|
||||
fieldPath: ".a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("a"),
|
||||
},
|
||||
{
|
||||
name: "Valid .a.bbb",
|
||||
fieldPath: ". a. bbb",
|
||||
name: "Valid 'foo'bar",
|
||||
fieldPath: "['\\'foo\\'bar']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("a", "bbb"),
|
||||
validFieldPath: path.Child("'foo'bar"),
|
||||
},
|
||||
{
|
||||
name: "Invalid 'foo'bar",
|
||||
fieldPath: ".\\'foo\\'bar",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Invalid with whitespace",
|
||||
fieldPath: ". a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Valid with whitespace inside field",
|
||||
fieldPath: ".white space",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
},
|
||||
{
|
||||
name: "Valid with whitespace inside field",
|
||||
fieldPath: "['white space']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
},
|
||||
{
|
||||
name: "invalid dot annotation",
|
||||
fieldPath: ".a.bb[b",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "valid with .",
|
||||
fieldPath: ".a['bbb.c']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
validFieldPath: path.Child("a", "bbb.c"),
|
||||
},
|
||||
{
|
||||
name: "Unclosed ]",
|
||||
fieldPath: ".a['bbb.c'",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
errDetail: "unexpected end of JSON path",
|
||||
},
|
||||
{
|
||||
name: "Unexpected end of JSON path",
|
||||
fieldPath: ".",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
errDetail: "unexpected end of JSON path",
|
||||
},
|
||||
{
|
||||
name: "Valid map syntax .a.bbb",
|
||||
fieldPath: ".a['bbb']",
|
||||
fieldPath: ".a['bbb.c']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("a").Child("bbb"),
|
||||
validFieldPath: path.Child("a").Child("bbb.c"),
|
||||
},
|
||||
{
|
||||
name: "Valid map key",
|
||||
fieldPath: ".foo.subAdd",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("foo").Key("subAdd"),
|
||||
},
|
||||
{
|
||||
@ -2819,7 +2884,6 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath: ".foo['subAdd']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("foo").Key("subAdd"),
|
||||
},
|
||||
{
|
||||
@ -2827,14 +2891,12 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath: ".a.foo's",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
},
|
||||
{
|
||||
name: "Escaping",
|
||||
fieldPath: ".a['foo\\'s']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("a").Child("foo's"),
|
||||
},
|
||||
{
|
||||
@ -2842,7 +2904,6 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath: ".a['test\\a']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("a").Child("test\a"),
|
||||
},
|
||||
|
||||
@ -2851,35 +2912,33 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath: ".a.foo",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Malformed map key",
|
||||
fieldPath: ".a.bbb[0]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "expected single quoted string but got 0",
|
||||
},
|
||||
{
|
||||
name: "Invalid refer for special map key",
|
||||
name: "Valid refer for name has number",
|
||||
fieldPath: ".a.bbb.34",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
},
|
||||
{
|
||||
name: "Map syntax for special field names",
|
||||
fieldPath: ".a.bbb['34']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
//errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Valid .list",
|
||||
fieldPath: ". list ",
|
||||
fieldPath: ".list",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: validationMatch{},
|
||||
validFieldPath: path.Child("list"),
|
||||
},
|
||||
{
|
||||
@ -2887,94 +2946,72 @@ func TestValidateFieldPath(t *testing.T) {
|
||||
fieldPath: ".list[0]",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "expected single quoted string but got 0",
|
||||
},
|
||||
{
|
||||
name: "Invalid list reference",
|
||||
fieldPath: ".list. a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Invalid .list.a",
|
||||
fieldPath: ".list['a']",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Missing leading dot",
|
||||
fieldPath: "a",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "expected [ or . but got: a",
|
||||
},
|
||||
{
|
||||
name: "Nonexistent field",
|
||||
fieldPath: ".c",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "Duplicate dots",
|
||||
fieldPath: ".a..b",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
{
|
||||
name: "object of array",
|
||||
fieldPath: ".list.a-b.34",
|
||||
pathOfFieldPath: path,
|
||||
schema: &sts,
|
||||
error: invalid(path.String()),
|
||||
errDetail: "does not refer to a valid field",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
validField, err := ValidFieldPath(tc.fieldPath, tc.pathOfFieldPath, tc.schema)
|
||||
validField, err := ValidFieldPath(tc.fieldPath, tc.schema)
|
||||
|
||||
if err == nil && !tc.error.empty() {
|
||||
t.Errorf("expected %v at %v but got nil", tc.error.errorType, tc.error.path.String())
|
||||
} else if err != nil && tc.error.empty() {
|
||||
t.Errorf("unexpected error: %v at %v", tc.error.errorType, tc.error.path.String())
|
||||
} else if !tc.error.matches(err) {
|
||||
t.Errorf("expected %v at %v, got %v", tc.error.errorType, tc.error.path.String(), err)
|
||||
if err == nil && tc.errDetail != "" {
|
||||
t.Errorf("expected err contains: %v but get nil", tc.errDetail)
|
||||
}
|
||||
if tc.validFieldPath != nil && tc.validFieldPath.String() != validField.String() {
|
||||
t.Errorf("expected %v, got %v", tc.validFieldPath, validField)
|
||||
if err != nil && tc.errDetail == "" {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), tc.errDetail) {
|
||||
t.Errorf("expected error to contain: %v, but get: %v", tc.errDetail, err)
|
||||
}
|
||||
if tc.validFieldPath != nil && tc.validFieldPath.String() != path.Child(validField.String()).String() {
|
||||
t.Errorf("expected %v, got %v", tc.validFieldPath, path.Child(validField.String()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type validationMatch struct {
|
||||
path *field.Path
|
||||
errorType field.ErrorType
|
||||
contains string
|
||||
}
|
||||
|
||||
func invalid(path ...string) validationMatch {
|
||||
return validationMatch{path: field.NewPath(path[0], path[1:]...), errorType: field.ErrorTypeInvalid}
|
||||
}
|
||||
|
||||
func (v validationMatch) matches(err *field.Error) bool {
|
||||
if err == nil && v.empty() {
|
||||
return true
|
||||
}
|
||||
return err.Type == v.errorType && err.Field == v.path.String() && strings.Contains(err.Error(), v.contains)
|
||||
}
|
||||
|
||||
func (v validationMatch) empty() bool {
|
||||
if v.path == nil && v.errorType == "" && v.contains == "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func genString(n int, c rune) string {
|
||||
b := strings.Builder{}
|
||||
for i := 0; i < n; i++ {
|
||||
|
Loading…
Reference in New Issue
Block a user