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  }