knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/deprecated.go (about) 1 /* 2 Copyright 2019 The Knative 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 17 package apis 18 19 import ( 20 "context" 21 "reflect" 22 "strings" 23 ) 24 25 const ( 26 deprecatedPrefix = "Deprecated" 27 ) 28 29 // CheckDeprecated checks whether the provided named deprecated fields 30 // are set in a context where deprecation is disallowed. 31 // This is a shallow check. 32 func CheckDeprecated(ctx context.Context, obj interface{}) *FieldError { 33 return CheckDeprecatedUpdate(ctx, obj, nil) 34 } 35 36 // CheckDeprecatedUpdate checks whether the provided named deprecated fields 37 // are set in a context where deprecation is disallowed. 38 // This is a json shallow check. We will recursively check inlined structs. 39 func CheckDeprecatedUpdate(ctx context.Context, obj, original interface{}) (errs *FieldError) { 40 if IsDeprecatedAllowed(ctx) { 41 // TODO: We should still run through the validation here, but do 42 // something like: 43 // defer func() { 44 // errs = errs.At(WarningLevel) 45 // }() 46 return nil 47 } 48 objFields, objInlined := getPrefixedNamedFieldValues(deprecatedPrefix, obj) 49 50 if nonZero(reflect.ValueOf(original)) { 51 originalFields, originalInlined := getPrefixedNamedFieldValues(deprecatedPrefix, original) 52 53 // We only have to walk obj Fields because the assumption is that obj 54 // and original are of the same type. 55 for name, value := range objFields { 56 if nonZero(value) { 57 if differ(originalFields[name], value) { 58 // Not allowed to update the value. 59 errs = errs.Also(ErrDisallowedUpdateDeprecatedFields(name)) 60 } 61 } 62 } 63 // Look for deprecated inlined updates. 64 if len(objInlined) > 0 { 65 for name, value := range objInlined { 66 errs = errs.Also(CheckDeprecatedUpdate(ctx, value, originalInlined[name])) 67 } 68 } 69 } else { 70 for name, value := range objFields { 71 if nonZero(value) { 72 // Not allowed to set the value. 73 errs = errs.Also(ErrDisallowedFields(name)) 74 } 75 } 76 // Look for deprecated inlined creates. 77 if len(objInlined) > 0 { 78 for _, value := range objInlined { 79 errs = errs.Also(CheckDeprecated(ctx, value)) 80 } 81 } 82 } 83 return errs 84 } 85 86 func getPrefixedNamedFieldValues(prefix string, obj interface{}) (map[string]reflect.Value, map[string]interface{}) { 87 fields := map[string]reflect.Value{} 88 inlined := map[string]interface{}{} 89 90 objValue := reflect.Indirect(reflect.ValueOf(obj)) 91 92 // If res is not valid or a struct, don't even try to use it. 93 if !objValue.IsValid() || objValue.Kind() != reflect.Struct { 94 return fields, inlined 95 } 96 97 for i := range objValue.NumField() { 98 tf := objValue.Type().Field(i) 99 if v := objValue.Field(i); v.IsValid() { 100 jTag := tf.Tag.Get("json") 101 if strings.HasPrefix(tf.Name, prefix) { 102 name := strings.Split(jTag, ",")[0] 103 if name == "" { 104 // Default to field name in go struct if no json name. 105 name = tf.Name 106 } 107 fields[name] = v 108 } else if jTag == ",inline" { 109 inlined[tf.Name] = getInterface(v) 110 } 111 } 112 } 113 return fields, inlined 114 } 115 116 // getInterface returns the interface value of the reflected object. 117 func getInterface(a reflect.Value) interface{} { 118 switch a.Kind() { 119 case reflect.Ptr: 120 if a.IsNil() { 121 return nil 122 } 123 return a.Elem().Interface() 124 125 case reflect.Map, reflect.Slice, reflect.Array: 126 return a.Elem().Interface() 127 128 // This is a nil interface{} type. 129 case reflect.Invalid: 130 return nil 131 132 default: 133 return a.Interface() 134 } 135 } 136 137 // nonZero returns true if a is nil or reflect.Zero. 138 func nonZero(a reflect.Value) bool { 139 switch a.Kind() { 140 case reflect.Ptr: 141 if a.IsNil() { 142 return false 143 } 144 return nonZero(a.Elem()) 145 146 case reflect.Map, reflect.Slice, reflect.Array: 147 if a.IsNil() { 148 return false 149 } 150 return true 151 152 // This is a nil interface{} type. 153 case reflect.Invalid: 154 return false 155 156 default: 157 if reflect.DeepEqual(a.Interface(), reflect.Zero(a.Type()).Interface()) { 158 return false 159 } 160 return true 161 } 162 } 163 164 // differ returns true if a != b 165 func differ(a, b reflect.Value) bool { 166 if a.Kind() != b.Kind() { 167 return true 168 } 169 170 switch a.Kind() { 171 case reflect.Ptr: 172 if a.IsNil() || b.IsNil() { 173 return a.IsNil() != b.IsNil() 174 } 175 return differ(a.Elem(), b.Elem()) 176 177 default: 178 if reflect.DeepEqual(a.Interface(), b.Interface()) { 179 return false 180 } 181 return true 182 } 183 }