k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/markers_test.go (about)

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  package generators_test
    17  
    18  import (
    19  	"testing"
    20  
    21  	"github.com/stretchr/testify/require"
    22  	"k8s.io/gengo/v2/types"
    23  	"k8s.io/kube-openapi/pkg/generators"
    24  	"k8s.io/kube-openapi/pkg/validation/spec"
    25  	"k8s.io/utils/ptr"
    26  )
    27  
    28  var structKind *types.Type = &types.Type{Kind: types.Struct, Name: types.Name{Name: "struct"}}
    29  var mapType *types.Type = &types.Type{Kind: types.Map, Name: types.Name{Name: "map[string]int"}}
    30  var arrayType *types.Type = &types.Type{Kind: types.Slice, Name: types.Name{Name: "[]int"}}
    31  
    32  func TestParseCommentTags(t *testing.T) {
    33  
    34  	cases := []struct {
    35  		t        *types.Type
    36  		name     string
    37  		comments []string
    38  		expected *spec.Schema
    39  
    40  		// regex pattern matching the error, or empty string/unset if no error
    41  		// is expected
    42  		expectedError string
    43  	}{
    44  		{
    45  			t:    structKind,
    46  			name: "basic example",
    47  			comments: []string{
    48  				"comment",
    49  				"another + comment",
    50  				"+k8s:validation:minimum=10.0",
    51  				"+k8s:validation:maximum=20.0",
    52  				"+k8s:validation:minLength=20",
    53  				"+k8s:validation:maxLength=30",
    54  				`+k8s:validation:pattern="asdf"`,
    55  				"+k8s:validation:multipleOf=1.0",
    56  				"+k8s:validation:minItems=1",
    57  				"+k8s:validation:maxItems=2",
    58  				"+k8s:validation:uniqueItems=true",
    59  				"exclusiveMaximum=true",
    60  				"not+k8s:validation:Minimum=0.0",
    61  			},
    62  			expected: &spec.Schema{
    63  				SchemaProps: spec.SchemaProps{
    64  					Maximum:     ptr.To(20.0),
    65  					Minimum:     ptr.To(10.0),
    66  					MinLength:   ptr.To[int64](20),
    67  					MaxLength:   ptr.To[int64](30),
    68  					Pattern:     "asdf",
    69  					MultipleOf:  ptr.To(1.0),
    70  					MinItems:    ptr.To[int64](1),
    71  					MaxItems:    ptr.To[int64](2),
    72  					UniqueItems: true,
    73  				},
    74  			},
    75  		},
    76  		{
    77  			t:        structKind,
    78  			name:     "empty",
    79  			expected: &spec.Schema{},
    80  		},
    81  		{
    82  			t:    types.Float64,
    83  			name: "single",
    84  			comments: []string{
    85  				"+k8s:validation:minimum=10.0",
    86  			},
    87  			expected: &spec.Schema{
    88  				SchemaProps: spec.SchemaProps{
    89  					Minimum: ptr.To(10.0),
    90  				},
    91  			},
    92  		},
    93  		{
    94  			t:    types.Float64,
    95  			name: "multiple",
    96  			comments: []string{
    97  				"+k8s:validation:minimum=10.0",
    98  				"+k8s:validation:maximum=20.0",
    99  			},
   100  			expected: &spec.Schema{
   101  				SchemaProps: spec.SchemaProps{
   102  					Maximum: ptr.To(20.0),
   103  					Minimum: ptr.To(10.0),
   104  				},
   105  			},
   106  		},
   107  		{
   108  			t:    types.Float64,
   109  			name: "invalid duplicate key",
   110  			comments: []string{
   111  				"+k8s:validation:minimum=10.0",
   112  				"+k8s:validation:maximum=20.0",
   113  				"+k8s:validation:minimum=30.0",
   114  			},
   115  			expectedError: `failed to parse marker comments: cannot have multiple values for key 'minimum'`,
   116  		},
   117  		{
   118  			t:    structKind,
   119  			name: "unrecognized key is ignored",
   120  			comments: []string{
   121  				"+ignored=30.0",
   122  			},
   123  			expected: &spec.Schema{},
   124  		},
   125  		{
   126  			t:    types.Float64,
   127  			name: "invalid: non-JSON value",
   128  			comments: []string{
   129  				`+k8s:validation:minimum=asdf`,
   130  			},
   131  			expectedError: `failed to parse marker comments: failed to parse value for key minimum as JSON: invalid character 'a' looking for beginning of value`,
   132  		},
   133  		{
   134  			t:    types.Float64,
   135  			name: "invalid: invalid value type",
   136  			comments: []string{
   137  				`+k8s:validation:minimum="asdf"`,
   138  			},
   139  			expectedError: `failed to unmarshal marker comments: json: cannot unmarshal string into Go struct field commentTags.minimum of type float64`,
   140  		},
   141  		{
   142  
   143  			t:    structKind,
   144  			name: "invalid: empty key",
   145  			comments: []string{
   146  				"+k8s:validation:",
   147  			},
   148  			expectedError: `failed to parse marker comments: cannot have empty key for marker comment`,
   149  		},
   150  		{
   151  			t: types.Float64,
   152  			// temporary test. ref support may be added in the future
   153  			name: "ignore refs",
   154  			comments: []string{
   155  				"+k8s:validation:pattern=ref(asdf)",
   156  			},
   157  			expected: &spec.Schema{},
   158  		},
   159  		{
   160  			t:    types.Float64,
   161  			name: "cel rule",
   162  			comments: []string{
   163  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   164  				`+k8s:validation:cel[0]:message="immutable field"`,
   165  			},
   166  			expected: &spec.Schema{
   167  				VendorExtensible: spec.VendorExtensible{
   168  					Extensions: map[string]interface{}{
   169  						"x-kubernetes-validations": []interface{}{
   170  							map[string]interface{}{
   171  								"rule":    "oldSelf == self",
   172  								"message": "immutable field",
   173  							},
   174  						},
   175  					},
   176  				},
   177  			},
   178  		},
   179  		{
   180  			t:    types.Float64,
   181  			name: "skipped CEL rule",
   182  			comments: []string{
   183  				// This should parse, but return an error in validation since
   184  				// index 1 is missing
   185  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   186  				`+k8s:validation:cel[0]:message="immutable field"`,
   187  				`+k8s:validation:cel[2]:rule="self > 5"`,
   188  				`+k8s:validation:cel[2]:message="must be greater than 5"`,
   189  			},
   190  			expectedError: `failed to parse marker comments: error parsing cel[2]:rule="self > 5": non-consecutive index 2 for key 'cel'`,
   191  		},
   192  		{
   193  			t:    types.Float64,
   194  			name: "multiple CEL params",
   195  			comments: []string{
   196  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   197  				`+k8s:validation:cel[0]:message="immutable field"`,
   198  				`+k8s:validation:cel[1]:rule="self > 5"`,
   199  				`+k8s:validation:cel[1]:optionalOldSelf=true`,
   200  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   201  			},
   202  			expected: &spec.Schema{
   203  				VendorExtensible: spec.VendorExtensible{
   204  					Extensions: map[string]interface{}{
   205  						"x-kubernetes-validations": []interface{}{
   206  							map[string]interface{}{
   207  								"rule":    "oldSelf == self",
   208  								"message": "immutable field",
   209  							},
   210  							map[string]interface{}{
   211  								"rule":            "self > 5",
   212  								"optionalOldSelf": true,
   213  								"message":         "must be greater than 5",
   214  							},
   215  						},
   216  					},
   217  				},
   218  			},
   219  		},
   220  		{
   221  			t:    types.Float64,
   222  			name: "multiple rules with multiple params",
   223  			comments: []string{
   224  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   225  				`+k8s:validation:cel[0]:optionalOldSelf`,
   226  				`+k8s:validation:cel[0]:messageExpression="self + ' must be equal to old value'"`,
   227  				`+k8s:validation:cel[1]:rule="self > 5"`,
   228  				`+k8s:validation:cel[1]:optionalOldSelf=true`,
   229  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   230  			},
   231  			expected: &spec.Schema{
   232  				VendorExtensible: spec.VendorExtensible{
   233  					Extensions: map[string]interface{}{
   234  						"x-kubernetes-validations": []interface{}{
   235  							map[string]interface{}{
   236  								"rule":              "oldSelf == self",
   237  								"optionalOldSelf":   true,
   238  								"messageExpression": "self + ' must be equal to old value'",
   239  							},
   240  							map[string]interface{}{
   241  								"rule":            "self > 5",
   242  								"optionalOldSelf": true,
   243  								"message":         "must be greater than 5",
   244  							},
   245  						},
   246  					},
   247  				},
   248  			},
   249  		},
   250  		{
   251  			t:    types.Float64,
   252  			name: "skipped array index",
   253  			comments: []string{
   254  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   255  				`+k8s:validation:cel[0]:optionalOldSelf`,
   256  				`+k8s:validation:cel[0]:messageExpression="self + ' must be equal to old value'"`,
   257  				`+k8s:validation:cel[2]:rule="self > 5"`,
   258  				`+k8s:validation:cel[2]:optionalOldSelf=true`,
   259  				`+k8s:validation:cel[2]:message="must be greater than 5"`,
   260  			},
   261  			expectedError: `failed to parse marker comments: error parsing cel[2]:rule="self > 5": non-consecutive index 2 for key 'cel'`,
   262  		},
   263  		{
   264  			t:    types.Float64,
   265  			name: "non-consecutive array index",
   266  			comments: []string{
   267  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   268  				`+k8s:validation:cel[1]:rule="self > 5"`,
   269  				`+k8s:validation:cel[1]:message="self > 5"`,
   270  				`+k8s:validation:cel[0]:optionalOldSelf=true`,
   271  				`+k8s:validation:cel[0]:message="must be greater than 5"`,
   272  			},
   273  			expectedError: "failed to parse marker comments: error parsing cel[0]:optionalOldSelf=true: non-consecutive index 0 for key 'cel'",
   274  		},
   275  		{
   276  			t:    types.Float64,
   277  			name: "interjected array index",
   278  			comments: []string{
   279  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   280  				`+k8s:validation:cel[0]:message="cant change"`,
   281  				`+k8s:validation:cel[1]:rule="self > 5"`,
   282  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   283  				`+k8s:validation:minimum=5`,
   284  				`+k8s:validation:cel[2]:rule="a rule"`,
   285  				`+k8s:validation:cel[2]:message="message 2"`,
   286  			},
   287  			expectedError: "failed to parse marker comments: error parsing cel[2]:rule=\"a rule\": non-consecutive index 2 for key 'cel'",
   288  		},
   289  		{
   290  			t:    types.Float64,
   291  			name: "interjected array index with non-prefixed comment",
   292  			comments: []string{
   293  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   294  				`+k8s:validation:cel[0]:message="cant change"`,
   295  				`+k8s:validation:cel[1]:rule="self > 5"`,
   296  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   297  				`+minimum=5`,
   298  				`+k8s:validation:cel[2]:rule="a rule"`,
   299  				`+k8s:validation:cel[2]:message="message 2"`,
   300  			},
   301  			expectedError: "failed to parse marker comments: error parsing cel[2]:rule=\"a rule\": non-consecutive index 2 for key 'cel'",
   302  		},
   303  		{
   304  			t:    types.Float64,
   305  			name: "non-consecutive raw string indexing",
   306  			comments: []string{
   307  				`+k8s:validation:cel[0]:rule> raw string rule`,
   308  				`+k8s:validation:cel[1]:rule="self > 5"`,
   309  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   310  				`+k8s:validation:cel[0]:message>another raw string message`,
   311  			},
   312  			expectedError: "failed to parse marker comments: error parsing cel[0]:message>another raw string message: non-consecutive index 0 for key 'cel'",
   313  		},
   314  		{
   315  			t:    types.String,
   316  			name: "non-consecutive string indexing false positive",
   317  			comments: []string{
   318  				`+k8s:validation:cel[0]:message>[3]string rule [1]`,
   319  				`+k8s:validation:cel[0]:rule="string rule [1]"`,
   320  				`+k8s:validation:pattern="self[3] == 'hi'"`,
   321  			},
   322  			expected: &spec.Schema{
   323  				SchemaProps: spec.SchemaProps{
   324  					Pattern: `self[3] == 'hi'`,
   325  				},
   326  				VendorExtensible: spec.VendorExtensible{
   327  					Extensions: map[string]interface{}{
   328  						"x-kubernetes-validations": []interface{}{
   329  							map[string]interface{}{
   330  								"rule":    "string rule [1]",
   331  								"message": "[3]string rule [1]",
   332  							},
   333  						},
   334  					},
   335  				},
   336  			},
   337  		},
   338  		{
   339  			t:    types.String,
   340  			name: "non-consecutive raw string indexing false positive",
   341  			comments: []string{
   342  				`+k8s:validation:cel[0]:message>[3]raw string message with subscirpt [3]"`,
   343  				`+k8s:validation:cel[0]:rule> raw string rule [1]`,
   344  				`+k8s:validation:pattern>"self[3] == 'hi'"`,
   345  			},
   346  			expected: &spec.Schema{
   347  				SchemaProps: spec.SchemaProps{
   348  					Pattern: `"self[3] == 'hi'"`,
   349  				},
   350  				VendorExtensible: spec.VendorExtensible{
   351  					Extensions: map[string]interface{}{
   352  						"x-kubernetes-validations": []interface{}{
   353  							map[string]interface{}{
   354  								"rule":    "raw string rule [1]",
   355  								"message": "[3]raw string message with subscirpt [3]\"",
   356  							},
   357  						},
   358  					},
   359  				},
   360  			},
   361  		},
   362  		{
   363  			t:    types.Float64,
   364  			name: "boolean key at invalid index",
   365  			comments: []string{
   366  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   367  				`+k8s:validation:cel[0]:message="cant change"`,
   368  				`+k8s:validation:cel[2]:optionalOldSelf`,
   369  			},
   370  			expectedError: `failed to parse marker comments: error parsing cel[2]:optionalOldSelf: non-consecutive index 2 for key 'cel'`,
   371  		},
   372  		{
   373  			t:    types.Float64,
   374  			name: "boolean key after non-prefixed comment",
   375  			comments: []string{
   376  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   377  				`+k8s:validation:cel[0]:message="cant change"`,
   378  				`+k8s:validation:cel[1]:rule="self > 5"`,
   379  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   380  				`+minimum=5`,
   381  				`+k8s:validation:cel[1]:optionalOldSelf`,
   382  			},
   383  			expectedError: `failed to parse marker comments: error parsing cel[1]:optionalOldSelf: non-consecutive index 1 for key 'cel'`,
   384  		},
   385  		{
   386  			t:    types.Float64,
   387  			name: "boolean key at index allowed",
   388  			comments: []string{
   389  				`+k8s:validation:cel[0]:rule="oldSelf == self"`,
   390  				`+k8s:validation:cel[0]:message="cant change"`,
   391  				`+k8s:validation:cel[1]:rule="self > 5"`,
   392  				`+k8s:validation:cel[1]:message="must be greater than 5"`,
   393  				`+k8s:validation:cel[1]:optionalOldSelf`,
   394  			},
   395  			expected: &spec.Schema{
   396  				VendorExtensible: spec.VendorExtensible{
   397  					Extensions: map[string]interface{}{
   398  						"x-kubernetes-validations": []interface{}{
   399  							map[string]interface{}{
   400  								"rule":    "oldSelf == self",
   401  								"message": "cant change",
   402  							},
   403  							map[string]interface{}{
   404  								"rule":            "self > 5",
   405  								"message":         "must be greater than 5",
   406  								"optionalOldSelf": true,
   407  							},
   408  						},
   409  					},
   410  				},
   411  			},
   412  		},
   413  		{
   414  			t:    types.Float64,
   415  			name: "raw string rule",
   416  			comments: []string{
   417  				`+k8s:validation:cel[0]:rule> raw string rule`,
   418  				`+k8s:validation:cel[0]:message="raw string message"`,
   419  			},
   420  			expected: &spec.Schema{
   421  				VendorExtensible: spec.VendorExtensible{
   422  					Extensions: map[string]interface{}{
   423  						"x-kubernetes-validations": []interface{}{
   424  							map[string]interface{}{
   425  								"rule":    "raw string rule",
   426  								"message": "raw string message",
   427  							},
   428  						},
   429  					},
   430  				},
   431  			},
   432  		},
   433  		{
   434  			t:    types.Float64,
   435  			name: "multiline string rule",
   436  			comments: []string{
   437  				`+k8s:validation:cel[0]:rule> self.length() % 2 == 0`,
   438  				`+k8s:validation:cel[0]:rule>   ? self.field == self.name + ' is even'`,
   439  				`+k8s:validation:cel[0]:rule>   : self.field == self.name + ' is odd'`,
   440  				`+k8s:validation:cel[0]:message>raw string message`,
   441  			},
   442  			expected: &spec.Schema{
   443  				VendorExtensible: spec.VendorExtensible{
   444  					Extensions: map[string]interface{}{
   445  						"x-kubernetes-validations": []interface{}{
   446  							map[string]interface{}{
   447  								"rule":    "self.length() % 2 == 0\n? self.field == self.name + ' is even'\n: self.field == self.name + ' is odd'",
   448  								"message": "raw string message",
   449  							},
   450  						},
   451  					},
   452  				},
   453  			},
   454  		},
   455  		{
   456  			t:    types.Float64,
   457  			name: "mix raw and non-raw string marker",
   458  			comments: []string{
   459  				`+k8s:validation:cel[0]:message>raw string message`,
   460  				`+k8s:validation:cel[0]:rule="self.length() % 2 == 0"`,
   461  				`+k8s:validation:cel[0]:rule>  ? self.field == self.name + ' is even'`,
   462  				`+k8s:validation:cel[0]:rule>  : self.field == self.name + ' is odd'`,
   463  			},
   464  			expected: &spec.Schema{
   465  				VendorExtensible: spec.VendorExtensible{
   466  					Extensions: map[string]interface{}{
   467  						"x-kubernetes-validations": []interface{}{
   468  							map[string]interface{}{
   469  								"rule":    "self.length() % 2 == 0\n? self.field == self.name + ' is even'\n: self.field == self.name + ' is odd'",
   470  								"message": "raw string message",
   471  							},
   472  						},
   473  					},
   474  				},
   475  			},
   476  		},
   477  		{
   478  			name: "raw string with different key in between",
   479  			t:    types.Float64,
   480  			comments: []string{
   481  				`+k8s:validation:cel[0]:message>raw string message`,
   482  				`+k8s:validation:cel[0]:rule="self.length() % 2 == 0"`,
   483  				`+k8s:validation:cel[0]:message>raw string message 2`,
   484  			},
   485  			expectedError: `failed to parse marker comments: concatenations to key 'cel[0]:message' must be consecutive with its assignment`,
   486  		},
   487  		{
   488  			name: "raw string with different raw string key in between",
   489  			t:    types.Float64,
   490  			comments: []string{
   491  				`+k8s:validation:cel[0]:message>raw string message`,
   492  				`+k8s:validation:cel[0]:rule>self.length() % 2 == 0`,
   493  				`+k8s:validation:cel[0]:message>raw string message 2`,
   494  			},
   495  			expectedError: `failed to parse marker comments: concatenations to key 'cel[0]:message' must be consecutive with its assignment`,
   496  		},
   497  	}
   498  
   499  	for _, tc := range cases {
   500  		t.Run(tc.name, func(t *testing.T) {
   501  			actual, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:")
   502  			if tc.expectedError != "" {
   503  				require.Error(t, err)
   504  				require.EqualError(t, err, tc.expectedError)
   505  				return
   506  			} else {
   507  				require.NoError(t, err)
   508  			}
   509  
   510  			require.Equal(t, tc.expected, actual)
   511  		})
   512  	}
   513  }
   514  
   515  // Test comment tag validation function
   516  func TestCommentTags_Validate(t *testing.T) {
   517  
   518  	testCases := []struct {
   519  		name         string
   520  		comments     []string
   521  		t            *types.Type
   522  		errorMessage string
   523  	}{
   524  		{
   525  			name: "invalid minimum type",
   526  			comments: []string{
   527  				`+k8s:validation:minimum=10.5`,
   528  			},
   529  			t:            types.String,
   530  			errorMessage: "minimum can only be used on numeric types",
   531  		},
   532  		{
   533  			name: "invalid minLength type",
   534  			comments: []string{
   535  				`+k8s:validation:minLength=10`,
   536  			},
   537  			t:            types.Bool,
   538  			errorMessage: "minLength can only be used on string types",
   539  		},
   540  		{
   541  			name: "invalid minItems type",
   542  			comments: []string{
   543  				`+k8s:validation:minItems=10`,
   544  			},
   545  			t:            types.String,
   546  			errorMessage: "minItems can only be used on array types",
   547  		},
   548  		{
   549  			name: "invalid minProperties type",
   550  			comments: []string{
   551  				`+k8s:validation:minProperties=10`,
   552  			},
   553  			t:            types.String,
   554  			errorMessage: "minProperties can only be used on map types",
   555  		},
   556  		{
   557  			name: "invalid exclusiveMinimum type",
   558  			comments: []string{
   559  				`+k8s:validation:exclusiveMinimum=true`,
   560  			},
   561  			t:            arrayType,
   562  			errorMessage: "exclusiveMinimum can only be used on numeric types",
   563  		},
   564  		{
   565  			name: "invalid maximum type",
   566  			comments: []string{
   567  				`+k8s:validation:maximum=10.5`,
   568  			},
   569  			t:            arrayType,
   570  			errorMessage: "maximum can only be used on numeric types",
   571  		},
   572  		{
   573  			name: "invalid maxLength type",
   574  			comments: []string{
   575  				`+k8s:validation:maxLength=10`,
   576  			},
   577  			t:            mapType,
   578  			errorMessage: "maxLength can only be used on string types",
   579  		},
   580  		{
   581  			name: "invalid maxItems type",
   582  			comments: []string{
   583  				`+k8s:validation:maxItems=10`,
   584  			},
   585  			t:            types.Bool,
   586  			errorMessage: "maxItems can only be used on array types",
   587  		},
   588  		{
   589  			name: "invalid maxProperties type",
   590  			comments: []string{
   591  				`+k8s:validation:maxProperties=10`,
   592  			},
   593  			t:            types.Bool,
   594  			errorMessage: "maxProperties can only be used on map types",
   595  		},
   596  		{
   597  			name: "invalid exclusiveMaximum type",
   598  			comments: []string{
   599  				`+k8s:validation:exclusiveMaximum=true`,
   600  			},
   601  			t:            mapType,
   602  			errorMessage: "exclusiveMaximum can only be used on numeric types",
   603  		},
   604  		{
   605  			name: "invalid pattern type",
   606  			comments: []string{
   607  				`+k8s:validation:pattern=".*"`,
   608  			},
   609  			t:            types.Int,
   610  			errorMessage: "pattern can only be used on string types",
   611  		},
   612  		{
   613  			name: "invalid multipleOf type",
   614  			comments: []string{
   615  				`+k8s:validation:multipleOf=10.5`,
   616  			},
   617  			t:            types.String,
   618  			errorMessage: "multipleOf can only be used on numeric types",
   619  		},
   620  		{
   621  			name: "invalid uniqueItems type",
   622  			comments: []string{
   623  				`+k8s:validation:uniqueItems=true`,
   624  			},
   625  			t:            types.Int,
   626  			errorMessage: "uniqueItems can only be used on array types",
   627  		},
   628  		{
   629  			name: "negative minLength",
   630  			comments: []string{
   631  				`+k8s:validation:minLength=-10`,
   632  			},
   633  			t:            types.String,
   634  			errorMessage: "minLength cannot be negative",
   635  		},
   636  		{
   637  			name: "negative minItems",
   638  			comments: []string{
   639  				`+k8s:validation:minItems=-10`,
   640  			},
   641  			t:            arrayType,
   642  			errorMessage: "minItems cannot be negative",
   643  		},
   644  		{
   645  			name: "negative minProperties",
   646  			comments: []string{
   647  				`+k8s:validation:minProperties=-10`,
   648  			},
   649  			t:            mapType,
   650  			errorMessage: "minProperties cannot be negative",
   651  		},
   652  		{
   653  			name: "negative maxLength",
   654  			comments: []string{
   655  				`+k8s:validation:maxLength=-10`,
   656  			},
   657  			t:            types.String,
   658  			errorMessage: "maxLength cannot be negative",
   659  		},
   660  		{
   661  			name: "negative maxItems",
   662  			comments: []string{
   663  				`+k8s:validation:maxItems=-10`,
   664  			},
   665  			t:            arrayType,
   666  			errorMessage: "maxItems cannot be negative",
   667  		},
   668  		{
   669  			name: "negative maxProperties",
   670  			comments: []string{
   671  				`+k8s:validation:maxProperties=-10`,
   672  			},
   673  			t:            mapType,
   674  			errorMessage: "maxProperties cannot be negative",
   675  		},
   676  		{
   677  			name: "minimum > maximum",
   678  			comments: []string{
   679  				`+k8s:validation:minimum=10.5`,
   680  				`+k8s:validation:maximum=5.5`,
   681  			},
   682  			t:            types.Float64,
   683  			errorMessage: "minimum 10.500000 is greater than maximum 5.500000",
   684  		},
   685  		{
   686  			name: "exclusiveMinimum when minimum == maximum",
   687  			comments: []string{
   688  				`+k8s:validation:minimum=10.5`,
   689  				`+k8s:validation:maximum=10.5`,
   690  				`+k8s:validation:exclusiveMinimum=true`,
   691  			},
   692  			t:            types.Float64,
   693  			errorMessage: "exclusiveMinimum/Maximum cannot be set when minimum == maximum",
   694  		},
   695  		{
   696  			name: "exclusiveMaximum when minimum == maximum",
   697  			comments: []string{
   698  				`+k8s:validation:minimum=10.5`,
   699  				`+k8s:validation:maximum=10.5`,
   700  				`+k8s:validation:exclusiveMaximum=true`,
   701  			},
   702  			t:            types.Float64,
   703  			errorMessage: "exclusiveMinimum/Maximum cannot be set when minimum == maximum",
   704  		},
   705  		{
   706  			name: "minLength > maxLength",
   707  			comments: []string{
   708  				`+k8s:validation:minLength=10`,
   709  				`+k8s:validation:maxLength=5`,
   710  			},
   711  			t:            types.String,
   712  			errorMessage: "minLength 10 is greater than maxLength 5",
   713  		},
   714  		{
   715  			name: "minItems > maxItems",
   716  			comments: []string{
   717  				`+k8s:validation:minItems=10`,
   718  				`+k8s:validation:maxItems=5`,
   719  			},
   720  			t:            arrayType,
   721  			errorMessage: "minItems 10 is greater than maxItems 5",
   722  		},
   723  		{
   724  			name: "minProperties > maxProperties",
   725  			comments: []string{
   726  				`+k8s:validation:minProperties=10`,
   727  				`+k8s:validation:maxProperties=5`,
   728  			},
   729  			t:            mapType,
   730  			errorMessage: "minProperties 10 is greater than maxProperties 5",
   731  		},
   732  		{
   733  			name: "invalid pattern",
   734  			comments: []string{
   735  				`+k8s:validation:pattern="([a-z]+"`,
   736  			},
   737  			t:            types.String,
   738  			errorMessage: "invalid pattern \"([a-z]+\": error parsing regexp: missing closing ): `([a-z]+`",
   739  		},
   740  		{
   741  			name: "multipleOf = 0",
   742  			comments: []string{
   743  				`+k8s:validation:multipleOf=0.0`,
   744  			},
   745  			t:            types.Int,
   746  			errorMessage: "multipleOf cannot be 0",
   747  		},
   748  		{
   749  			name: "valid comment tags with no invalid validations",
   750  			comments: []string{
   751  				`+k8s:validation:pattern=".*"`,
   752  			},
   753  			t:            types.String,
   754  			errorMessage: "",
   755  		},
   756  	}
   757  
   758  	for _, tc := range testCases {
   759  		t.Run(tc.name, func(t *testing.T) {
   760  			_, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:")
   761  			if tc.errorMessage != "" {
   762  				require.Error(t, err)
   763  				require.Equal(t, "invalid marker comments: "+tc.errorMessage, err.Error())
   764  			} else {
   765  				require.NoError(t, err)
   766  			}
   767  		})
   768  	}
   769  }