each.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. Copyright 2024 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validate
  14. import (
  15. "context"
  16. "sort"
  17. "k8s.io/apimachinery/pkg/api/equality"
  18. "k8s.io/apimachinery/pkg/api/operation"
  19. "k8s.io/apimachinery/pkg/util/validation/field"
  20. )
  21. // MatchFunc is a function that compares two values of the same type,
  22. // according to some criteria, and returns true if they match.
  23. type MatchFunc[T any] func(T, T) bool
  24. // EachSliceVal performs validation on each element of newSlice using the provided validation function.
  25. //
  26. // For update operations, the match function finds corresponding values in oldSlice for each
  27. // value in newSlice. This comparison can be either full or partial (e.g., matching only
  28. // specific struct fields that serve as a unique identifier). If match is nil, validation
  29. // proceeds without considering old values, and the equiv function is not used.
  30. //
  31. // For update operations, the equiv function checks if a new value is equivalent to its
  32. // corresponding old value, enabling validation ratcheting. If equiv is nil but match is
  33. // provided, the match function is assumed to perform full value comparison.
  34. //
  35. // Note: The slice element type must be non-nilable.
  36. func EachSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
  37. match, equiv MatchFunc[T], validator ValidateFunc[*T]) field.ErrorList {
  38. var errs field.ErrorList
  39. for i, val := range newSlice {
  40. var old *T
  41. if match != nil && len(oldSlice) > 0 {
  42. old = lookup(oldSlice, val, match)
  43. }
  44. // If the operation is an update, for validation ratcheting, skip re-validating if the old
  45. // value exists and either:
  46. // 1. The match function provides full comparison (equiv is nil)
  47. // 2. The equiv function confirms the values are equivalent (either directly or semantically)
  48. //
  49. // The equiv function provides equality comparison when match uses partial comparison.
  50. if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, *old)) {
  51. continue
  52. }
  53. errs = append(errs, validator(ctx, op, fldPath.Index(i), &val, old)...)
  54. }
  55. return errs
  56. }
  57. // lookup returns a pointer to the first element in the list that matches the
  58. // target, according to the provided comparison function, or else nil.
  59. func lookup[T any](list []T, target T, match MatchFunc[T]) *T {
  60. for i := range list {
  61. if match(list[i], target) {
  62. return &list[i]
  63. }
  64. }
  65. return nil
  66. }
  67. // EachMapVal validates each value in newMap using the specified validation
  68. // function, passing the corresponding old value from oldMap if the key exists in oldMap.
  69. // For update operations, it implements validation ratcheting by skipping validation
  70. // when the old value exists and the equiv function confirms the values are equivalent.
  71. // The value-type of the map is assumed to not be nilable.
  72. // If equiv is nil, value-based ratcheting is disabled and all values will be validated.
  73. func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
  74. equiv MatchFunc[V], validator ValidateFunc[*V]) field.ErrorList {
  75. var errs field.ErrorList
  76. for key, val := range newMap {
  77. var old *V
  78. if o, found := oldMap[key]; found {
  79. old = &o
  80. }
  81. // If the operation is an update, for validation ratcheting, skip re-validating if the old
  82. // value is found and the equiv function confirms the values are equivalent.
  83. if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, *old) {
  84. continue
  85. }
  86. errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...)
  87. }
  88. return errs
  89. }
  90. // EachMapKey validates each element of newMap with the specified
  91. // validation function.
  92. func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
  93. validator ValidateFunc[*K]) field.ErrorList {
  94. var errs field.ErrorList
  95. for key := range newMap {
  96. var old *K
  97. if _, found := oldMap[key]; found {
  98. old = &key
  99. }
  100. // If the operation is an update, for validation ratcheting, skip re-validating if
  101. // the key is found in oldMap.
  102. if op.Type == operation.Update && old != nil {
  103. continue
  104. }
  105. // Note: the field path is the field, not the key.
  106. errs = append(errs, validator(ctx, op, fldPath, &key, nil)...)
  107. }
  108. return errs
  109. }
  110. // Unique verifies that each element of newSlice is unique, according to the
  111. // match function. It compares every element of the slice with every other
  112. // element and returns errors for non-unique items.
  113. func Unique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []T, match MatchFunc[T]) field.ErrorList {
  114. var dups []int
  115. for i, val := range newSlice {
  116. for j := i + 1; j < len(newSlice); j++ {
  117. other := newSlice[j]
  118. if match(val, other) {
  119. if dups == nil {
  120. dups = make([]int, 0, len(newSlice))
  121. }
  122. if lookup(dups, j, func(a, b int) bool { return a == b }) == nil {
  123. dups = append(dups, j)
  124. }
  125. }
  126. }
  127. }
  128. var errs field.ErrorList
  129. sort.Ints(dups)
  130. for _, i := range dups {
  131. var val any = newSlice[i]
  132. // TODO: we don't want the whole item to be logged in the error, just
  133. // the key(s). Unfortunately, the way errors are rendered, it comes out
  134. // as something like "map[string]any{...}" which is not very nice. Once
  135. // that is fixed, we can consider adding a way for this function to
  136. // specify that just the keys should be rendered in the error.
  137. errs = append(errs, field.Duplicate(fldPath.Index(i), val))
  138. }
  139. return errs
  140. }
  141. // SemanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
  142. // compare two values.
  143. // This wrapper is needed because MatchFunc requires a function that takes two
  144. // arguments of specific type T, while equality.Semantic.DeepEqual takes
  145. // arguments of type interface{}/any. The wrapper satisfies the type
  146. // constraints of MatchFunc while leveraging the underlying semantic equality
  147. // logic. It can be used by any other function that needs to call DeepEqual.
  148. func SemanticDeepEqual[T any](a, b T) bool {
  149. return equality.Semantic.DeepEqual(a, b)
  150. }
  151. // DirectEqual is a MatchFunc that uses the == operator to compare two values.
  152. // It can be used by any other function that needs to compare two values
  153. // directly.
  154. func DirectEqual[T comparable](a, b T) bool {
  155. return a == b
  156. }
  157. // DirectEqualPtr is a MatchFunc that dereferences two pointers and uses the ==
  158. // operator to compare the values. If both pointers are nil, it returns true.
  159. // If one pointer is nil and the other is not, it returns false.
  160. // It can be used by any other function that needs to compare two pointees
  161. // directly.
  162. func DirectEqualPtr[T comparable](a, b *T) bool {
  163. if a == b {
  164. return true
  165. }
  166. if a == nil || b == nil {
  167. return false
  168. }
  169. return *a == *b
  170. }