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  }