sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/genyaml/populate_struct.go (about)

     1  /*
     2  Copyright 2020 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  
    17  package genyaml
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  
    25  	"github.com/sirupsen/logrus"
    26  )
    27  
    28  // PopulateStruct will recursively populate a struct via reflection for consumption by genyaml by:
    29  // * Filling all pointer fields
    30  // * Filling all slices with a one-element slice and filling that one element
    31  // * Filling all maps with a one-element map and filling that one element
    32  // NOTE: PopulateStruct will panic if not fed a pointer. Generally if you care about
    33  // the stability of the app that runs this code, it is strongly recommended to recover panics:
    34  // defer func(){if r := recover(); r != nil { fmt.Printf("Recovered panic: %v", r } }(
    35  func PopulateStruct(in interface{}) interface{} {
    36  	typeOf := reflect.TypeOf(in)
    37  	if typeOf.Kind() != reflect.Ptr {
    38  		panic(fmt.Sprintf("got nonpointer type %T", in))
    39  	}
    40  	if typeOf.Elem().Kind() != reflect.Struct {
    41  		return in
    42  	}
    43  	valueOf := reflect.ValueOf(in)
    44  	for i := 0; i < typeOf.Elem().NumField(); i++ {
    45  		// Unexported
    46  		if !valueOf.Elem().Field(i).CanSet() {
    47  			continue
    48  		}
    49  
    50  		if typeOf.Elem().Field(i).Anonymous {
    51  			// We can only warn about this, because the go stdlib and some kube types do this :/
    52  			if !strings.Contains(typeOf.Elem().Field(i).Tag.Get("json"), ",inline") {
    53  				logrus.Warningf("Found anonymous field without inline annotation, this will produce invalid results. Please add a `json:\",inline\"` annotation if you control the type. Type: %T, parentType: %T", valueOf.Elem().Field(i).Interface(), valueOf.Elem().Interface())
    54  			}
    55  		}
    56  		switch k := typeOf.Elem().Field(i).Type.Kind(); k {
    57  		// We must populate strings, because genyaml uses a custom json lib
    58  		// that omits structs that have empty values only and we have some
    59  		// structs that only have string fields
    60  		// TODO: Is that genyaml behavior intentional?
    61  		case reflect.String:
    62  			// ArrayOrString comes from Tekton and has a custom marshaller
    63  			// that errors when only setting the String field
    64  			if typeOf.Elem().Name() == "ArrayOrString" {
    65  				valueOf.Elem().FieldByName("Type").SetString("string")
    66  			}
    67  			valueOf.Elem().Field(i).SetString(" ")
    68  		// Needed because of the String field handling
    69  		case reflect.Struct:
    70  			PopulateStruct(valueOf.Elem().Field(i).Addr().Interface())
    71  		case reflect.Ptr:
    72  			ptr := createNonNilPtr(valueOf.Elem().Field(i).Type())
    73  			// Populate our ptr
    74  			if ptr.Elem().Kind() == reflect.Struct {
    75  				PopulateStruct(ptr.Interface())
    76  			}
    77  			// Set it on the parent struct
    78  			valueOf.Elem().Field(i).Set(ptr)
    79  		case reflect.Slice:
    80  			if typeOf.Elem().Field(i).Type == typeOfBytes || typeOf.Elem().Field(i).Type == typeOfJSONRawMessage {
    81  				continue
    82  			}
    83  			// Create a one element slice
    84  			slice := reflect.MakeSlice(typeOf.Elem().Field(i).Type, 1, 1)
    85  			// Get a pointer to the value
    86  			var sliceElementPtr interface{}
    87  			if slice.Index(0).Type().Kind() == reflect.Ptr {
    88  				// Slice of pointers, make it a non-nil pointer, then pass on its address
    89  				slice.Index(0).Set(createNonNilPtr(slice.Index(0).Type()))
    90  				sliceElementPtr = slice.Index(0).Interface()
    91  			} else {
    92  				// Slice of literals
    93  				sliceElementPtr = slice.Index(0).Addr().Interface()
    94  			}
    95  			PopulateStruct(sliceElementPtr)
    96  			// Set it on the parent struct
    97  			valueOf.Elem().Field(i).Set(slice)
    98  		case reflect.Map:
    99  			keyType := typeOf.Elem().Field(i).Type.Key()
   100  			valueType := typeOf.Elem().Field(i).Type.Elem()
   101  
   102  			key := reflect.New(keyType).Elem()
   103  			value := reflect.New(valueType).Elem()
   104  
   105  			var keyPtr, valPtr interface{}
   106  			if key.Kind() == reflect.Ptr {
   107  				key.Set(createNonNilPtr(key.Type()))
   108  				keyPtr = key.Interface()
   109  			} else {
   110  				keyPtr = key.Addr().Interface()
   111  			}
   112  			if value.Kind() == reflect.Ptr {
   113  				value.Set(createNonNilPtr(value.Type()))
   114  				valPtr = value.Interface()
   115  			} else {
   116  				valPtr = value.Addr().Interface()
   117  			}
   118  			PopulateStruct(keyPtr)
   119  			PopulateStruct(valPtr)
   120  
   121  			mapType := reflect.MapOf(typeOf.Elem().Field(i).Type.Key(), typeOf.Elem().Field(i).Type.Elem())
   122  			concreteMap := reflect.MakeMapWithSize(mapType, 0)
   123  			concreteMap.SetMapIndex(key, value)
   124  
   125  			valueOf.Elem().Field(i).Set(concreteMap)
   126  		case reflect.Bool:
   127  			if strings.Contains(typeOf.Elem().Field(i).Tag.Get("json"), ",omitempty") {
   128  				valueOf.Elem().Field(i).SetBool(true)
   129  			}
   130  		}
   131  
   132  	}
   133  	return in
   134  }
   135  
   136  func createNonNilPtr(in reflect.Type) reflect.Value {
   137  	// construct a new **type and call Elem() to get the *type
   138  	ptr := reflect.New(in).Elem()
   139  	// Give it a value
   140  	ptr.Set(reflect.New(ptr.Type().Elem()))
   141  
   142  	return ptr
   143  }
   144  
   145  var typeOfBytes = reflect.TypeOf([]byte(nil))
   146  var typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{})