github.com/kubeshop/testkube@v1.17.23/pkg/tcl/expressionstcl/generic.go (about) 1 // Copyright 2024 Testkube. 2 // 3 // Licensed as a Testkube Pro file under the Testkube Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt 8 9 package expressionstcl 10 11 import ( 12 "fmt" 13 "reflect" 14 "strings" 15 16 "github.com/pkg/errors" 17 "k8s.io/apimachinery/pkg/util/intstr" 18 ) 19 20 type tagData struct { 21 key string 22 value string 23 } 24 25 func parseTag(tag string) tagData { 26 s := strings.Split(tag, ",") 27 if len(s) > 1 { 28 return tagData{key: s[0], value: s[1]} 29 } 30 return tagData{value: s[0]} 31 } 32 33 func hasUnexportedFields(v reflect.Value) bool { 34 if v.Kind() != reflect.Struct { 35 return false 36 } 37 t := v.Type() 38 for i := 0; i < t.NumField(); i++ { 39 if !t.Field(i).IsExported() { 40 return true 41 } 42 } 43 return false 44 } 45 46 func clone(v reflect.Value) reflect.Value { 47 if v.Kind() == reflect.String { 48 s := v.String() 49 return reflect.ValueOf(&s).Elem() 50 } else if v.Kind() == reflect.Struct { 51 r := reflect.New(v.Type()).Elem() 52 t := v.Type() 53 for i := 0; i < r.NumField(); i++ { 54 if t.Field(i).IsExported() { 55 r.Field(i).Set(v.Field(i)) 56 } 57 } 58 return r 59 } 60 return v 61 } 62 63 func resolve(v reflect.Value, t tagData, m []Machine, force bool, finalize bool) (changed bool, err error) { 64 if t.value == "force" { 65 force = true 66 } 67 if t.key == "" && t.value == "" && !force { 68 return 69 } 70 71 ptr := v 72 for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface { 73 if v.IsNil() { 74 return 75 } 76 ptr = v 77 v = v.Elem() 78 } 79 80 if v.IsZero() || !v.IsValid() || (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { 81 return 82 } 83 84 switch v.Kind() { 85 case reflect.Struct: 86 // TODO: Cache the tags for structs for better performance 87 vv, ok := v.Interface().(intstr.IntOrString) 88 if ok { 89 if vv.Type == intstr.String { 90 return resolve(v.FieldByName("StrVal"), t, m, force, finalize) 91 } 92 } else if t.value == "include" || force { 93 tt := v.Type() 94 for i := 0; i < tt.NumField(); i++ { 95 f := tt.Field(i) 96 tagStr := f.Tag.Get("expr") 97 tag := parseTag(tagStr) 98 if !f.IsExported() { 99 if tagStr != "" && tagStr != "-" { 100 return changed, errors.New(f.Name + ": private property marked with `expr` clause") 101 } 102 continue 103 } 104 value := v.FieldByName(f.Name) 105 var ch bool 106 ch, err = resolve(value, tag, m, force, finalize) 107 if ch { 108 changed = true 109 } 110 if err != nil { 111 return changed, errors.Wrap(err, f.Name) 112 } 113 } 114 } 115 return 116 case reflect.Slice: 117 if t.value == "" && !force { 118 return changed, nil 119 } 120 for i := 0; i < v.Len(); i++ { 121 ch, err := resolve(v.Index(i), t, m, force, finalize) 122 if ch { 123 changed = true 124 } 125 if err != nil { 126 return changed, errors.Wrap(err, fmt.Sprintf("%d", i)) 127 } 128 } 129 return 130 case reflect.Map: 131 if t.value == "" && t.key == "" && !force { 132 return changed, nil 133 } 134 for _, k := range v.MapKeys() { 135 if (t.value != "" || force) && !hasUnexportedFields(v.MapIndex(k)) { 136 // It's not possible to get a pointer to map element, 137 // so we need to copy it and reassign 138 item := clone(v.MapIndex(k)) 139 var ch bool 140 ch, err = resolve(item, t, m, force, finalize) 141 if ch { 142 changed = true 143 } 144 if err != nil { 145 return changed, errors.Wrap(err, k.String()) 146 } 147 v.SetMapIndex(k, item) 148 } 149 if (t.key != "" || force) && !hasUnexportedFields(k) && !hasUnexportedFields(v.MapIndex(k)) { 150 key := clone(k) 151 var ch bool 152 ch, err = resolve(key, tagData{value: t.key}, m, force, finalize) 153 if ch { 154 changed = true 155 } 156 if err != nil { 157 return changed, errors.Wrap(err, "key("+k.String()+")") 158 } 159 if !key.Equal(k) { 160 item := clone(v.MapIndex(k)) 161 v.SetMapIndex(k, reflect.Value{}) 162 v.SetMapIndex(key.Convert(k.Type()), item) 163 } 164 } 165 } 166 return 167 case reflect.String: 168 if t.value == "expression" { 169 var expr Expression 170 str := v.String() 171 expr, err = CompileAndResolve(str, m...) 172 if err != nil { 173 return changed, err 174 } 175 var vv string 176 if finalize { 177 expr2, err := expr.Resolve(FinalizerFail) 178 if err != nil { 179 return changed, errors.Wrap(err, "resolving the value") 180 } 181 vv, _ = expr2.Static().StringValue() 182 } else { 183 vv = expr.String() 184 } 185 changed = vv != str 186 if ptr.Kind() == reflect.String { 187 v.SetString(vv) 188 } else { 189 ptr.Set(reflect.ValueOf(&vv)) 190 } 191 } else if (t.value == "template" && !IsTemplateStringWithoutExpressions(v.String())) || force { 192 var expr Expression 193 str := v.String() 194 expr, err = CompileAndResolveTemplate(str, m...) 195 if err != nil { 196 return changed, err 197 } 198 var vv string 199 if finalize { 200 expr2, err := expr.Resolve(FinalizerFail) 201 if err != nil { 202 return changed, errors.Wrap(err, "resolving the value") 203 } 204 vv, _ = expr2.Static().StringValue() 205 } else { 206 vv = expr.Template() 207 } 208 changed = vv != str 209 if ptr.Kind() == reflect.String { 210 v.SetString(vv) 211 } else { 212 instance := reflect.New(v.Type()) 213 instance.Elem().SetString(vv) 214 ptr.Set(instance) 215 } 216 } 217 return 218 } 219 220 // Ignore unrecognized values 221 return 222 } 223 224 func simplify(t interface{}, tag tagData, m ...Machine) error { 225 v := reflect.ValueOf(t) 226 if v.Kind() != reflect.Pointer { 227 return errors.New("pointer needs to be passed to Simplify function") 228 } 229 changed, err := resolve(v, tag, m, false, false) 230 i := 1 231 for changed && err == nil { 232 if i > maxCallStack { 233 return fmt.Errorf("maximum call stack exceeded while simplifying struct") 234 } 235 changed, err = resolve(v, tag, m, false, false) 236 i++ 237 } 238 return err 239 } 240 241 func finalize(t interface{}, tag tagData, m ...Machine) error { 242 v := reflect.ValueOf(t) 243 if v.Kind() != reflect.Pointer { 244 return errors.New("pointer needs to be passed to Finalize function") 245 } 246 _, err := resolve(v, tag, m, false, true) 247 return err 248 } 249 250 func Simplify(t interface{}, m ...Machine) error { 251 return simplify(t, tagData{value: "include"}, m...) 252 } 253 254 func SimplifyForce(t interface{}, m ...Machine) error { 255 return simplify(t, tagData{value: "force"}, m...) 256 } 257 258 func Finalize(t interface{}, m ...Machine) error { 259 return finalize(t, tagData{value: "include"}, m...) 260 } 261 262 func FinalizeForce(t interface{}, m ...Machine) error { 263 return finalize(t, tagData{value: "force"}, m...) 264 }