-
Notifications
You must be signed in to change notification settings - Fork 136
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(TKC-1465): expressions improvements - resolving structs, improve…
…d finalizer (#5067) * fix(TKC-1465): recognition of 'none' static value * feat(TKC-1465): add helpers to compile and and resolve the expression immediately * feat(TKC-1465): add mechanism to allow to resolve expressions in the objects via tags * feat(TKC-1465): escape "{{" nicely * chore(TKC-1465): avoid unnecessary string computation while resolving struct * chore(TKC-1465): rename Resolve to SimplifyStruct * feat(TKC-1465): add option to provide extended accessor in the expressions machine * feat(TKC-1465): simplify expression finalizers, add expression machine utils
- Loading branch information
Showing
19 changed files
with
512 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright 2024 Testkube. | ||
// | ||
// Licensed as a Testkube Pro file under the Testkube Community | ||
// License (the "License"); you may not use this file except in compliance with | ||
// the License. You may obtain a copy of the License at | ||
// | ||
// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt | ||
|
||
package expressionstcl | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
"k8s.io/apimachinery/pkg/util/intstr" | ||
) | ||
|
||
type tagData struct { | ||
key string | ||
value string | ||
} | ||
|
||
func parseTag(tag string) tagData { | ||
s := strings.Split(tag, ",") | ||
if len(s) > 1 { | ||
return tagData{key: s[0], value: s[1]} | ||
} | ||
return tagData{value: s[0]} | ||
} | ||
|
||
var unrecognizedErr = errors.New("unsupported value passed for resolving expressions") | ||
|
||
func clone(v reflect.Value) reflect.Value { | ||
if v.Kind() == reflect.String { | ||
s := v.String() | ||
return reflect.ValueOf(&s).Elem() | ||
} else if v.Kind() == reflect.Struct { | ||
r := reflect.New(v.Type()).Elem() | ||
for i := 0; i < r.NumField(); i++ { | ||
r.Field(i).Set(v.Field(i)) | ||
} | ||
return r | ||
} | ||
return v | ||
} | ||
|
||
func resolve(v reflect.Value, t tagData, m []Machine) (err error) { | ||
if t.key == "" && t.value == "" { | ||
return | ||
} | ||
|
||
ptr := v | ||
for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface { | ||
if v.IsNil() { | ||
return | ||
} | ||
ptr = v | ||
v = v.Elem() | ||
} | ||
|
||
if v.IsZero() || !v.IsValid() || (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() { | ||
return | ||
} | ||
|
||
switch v.Kind() { | ||
case reflect.Struct: | ||
// TODO: Cache the tags for structs for better performance | ||
vv, ok := v.Interface().(intstr.IntOrString) | ||
if ok { | ||
if vv.Type == intstr.String { | ||
return resolve(v.FieldByName("StrVal"), t, m) | ||
} | ||
} else if t.value == "include" { | ||
tt := v.Type() | ||
for i := 0; i < tt.NumField(); i++ { | ||
f := tt.Field(i) | ||
tag := parseTag(f.Tag.Get("expr")) | ||
value := v.FieldByName(f.Name) | ||
err = resolve(value, tag, m) | ||
if err != nil { | ||
return errors.Wrap(err, f.Name) | ||
} | ||
} | ||
} | ||
return | ||
case reflect.Slice: | ||
if t.value == "" { | ||
return nil | ||
} | ||
for i := 0; i < v.Len(); i++ { | ||
err := resolve(v.Index(i), t, m) | ||
if err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("%d", i)) | ||
} | ||
} | ||
return | ||
case reflect.Map: | ||
if t.value == "" && t.key == "" { | ||
return nil | ||
} | ||
for _, k := range v.MapKeys() { | ||
if t.value != "" { | ||
// It's not possible to get a pointer to map element, | ||
// so we need to copy it and reassign | ||
item := clone(v.MapIndex(k)) | ||
err = resolve(item, t, m) | ||
v.SetMapIndex(k, item) | ||
if err != nil { | ||
return errors.Wrap(err, k.String()) | ||
} | ||
} | ||
if t.key != "" { | ||
key := clone(k) | ||
err = resolve(key, tagData{value: t.key}, m) | ||
if !key.Equal(k) { | ||
item := clone(v.MapIndex(k)) | ||
v.SetMapIndex(k, reflect.Value{}) | ||
v.SetMapIndex(key, item) | ||
} | ||
if err != nil { | ||
return errors.Wrap(err, "key("+k.String()+")") | ||
} | ||
} | ||
} | ||
return | ||
case reflect.String: | ||
if t.value == "expression" { | ||
var expr Expression | ||
expr, err = CompileAndResolve(v.String(), m...) | ||
if err != nil { | ||
return err | ||
} | ||
vv := expr.String() | ||
if ptr.Kind() == reflect.String { | ||
v.SetString(vv) | ||
} else { | ||
ptr.Set(reflect.ValueOf(&vv)) | ||
} | ||
} else if t.value == "template" && !IsTemplateStringWithoutExpressions(v.String()) { | ||
var expr Expression | ||
expr, err = CompileAndResolveTemplate(v.String(), m...) | ||
if err != nil { | ||
return err | ||
} | ||
vv := expr.Template() | ||
if ptr.Kind() == reflect.String { | ||
v.SetString(vv) | ||
} else { | ||
ptr.Set(reflect.ValueOf(&vv)) | ||
} | ||
} | ||
return | ||
} | ||
|
||
// Fail for unrecognized values | ||
return unrecognizedErr | ||
} | ||
|
||
func SimplifyStruct(t interface{}, m ...Machine) error { | ||
v := reflect.ValueOf(t) | ||
if v.Kind() != reflect.Pointer { | ||
return errors.New("pointer needs to be passed to Resolve function") | ||
} | ||
return resolve(v, tagData{value: "include"}, m) | ||
} |
Oops, something went wrong.