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{})