github.com/kubeshop/testkube@v1.17.23/pkg/tcl/expressionstcl/stdlib.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  	"context"
    13  	"encoding/json"
    14  	"fmt"
    15  	math2 "math"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/itchyny/gojq"
    20  	"github.com/kballard/go-shellquote"
    21  	"github.com/pkg/errors"
    22  	"gopkg.in/yaml.v3"
    23  )
    24  
    25  type StdFunction struct {
    26  	ReturnType Type
    27  	Handler    func(...StaticValue) (Expression, error)
    28  }
    29  
    30  type stdMachine struct{}
    31  
    32  var StdLibMachine = &stdMachine{}
    33  
    34  var stdFunctions = map[string]StdFunction{
    35  	"string": {
    36  		ReturnType: TypeString,
    37  		Handler: func(value ...StaticValue) (Expression, error) {
    38  			str := ""
    39  			for i := range value {
    40  				next, _ := value[i].StringValue()
    41  				str += next
    42  			}
    43  			return NewValue(str), nil
    44  		},
    45  	},
    46  	"list": {
    47  		Handler: func(value ...StaticValue) (Expression, error) {
    48  			v := make([]interface{}, len(value))
    49  			for i := range value {
    50  				v[i] = value[i].Value()
    51  			}
    52  			return NewValue(v), nil
    53  		},
    54  	},
    55  	"join": {
    56  		ReturnType: TypeString,
    57  		Handler: func(value ...StaticValue) (Expression, error) {
    58  			if len(value) == 0 || len(value) > 2 {
    59  				return nil, fmt.Errorf(`"join" function expects 1-2 arguments, %d provided`, len(value))
    60  			}
    61  			if value[0].IsNone() {
    62  				return value[0], nil
    63  			}
    64  			if !value[0].IsSlice() {
    65  				return nil, fmt.Errorf(`"join" function expects a slice as 1st argument: %v provided`, value[0].Value())
    66  			}
    67  			slice, err := value[0].SliceValue()
    68  			if err != nil {
    69  				return nil, fmt.Errorf(`"join" function error: reading slice: %s`, err.Error())
    70  			}
    71  			v := make([]string, len(slice))
    72  			for i := range slice {
    73  				v[i], _ = toString(slice[i])
    74  			}
    75  			separator := ","
    76  			if len(value) == 2 {
    77  				separator, _ = value[1].StringValue()
    78  			}
    79  			return NewValue(strings.Join(v, separator)), nil
    80  		},
    81  	},
    82  	"split": {
    83  		Handler: func(value ...StaticValue) (Expression, error) {
    84  			if len(value) == 0 || len(value) > 2 {
    85  				return nil, fmt.Errorf(`"split" function expects 1-2 arguments, %d provided`, len(value))
    86  			}
    87  			str, _ := value[0].StringValue()
    88  			separator := ","
    89  			if len(value) == 2 {
    90  				separator, _ = value[1].StringValue()
    91  			}
    92  			return NewValue(strings.Split(str, separator)), nil
    93  		},
    94  	},
    95  	"int": {
    96  		ReturnType: TypeInt64,
    97  		Handler: func(value ...StaticValue) (Expression, error) {
    98  			if len(value) != 1 {
    99  				return nil, fmt.Errorf(`"int" function expects 1 argument, %d provided`, len(value))
   100  			}
   101  			v, err := value[0].IntValue()
   102  			if err != nil {
   103  				return nil, err
   104  			}
   105  			return NewValue(v), nil
   106  		},
   107  	},
   108  	"bool": {
   109  		ReturnType: TypeBool,
   110  		Handler: func(value ...StaticValue) (Expression, error) {
   111  			if len(value) != 1 {
   112  				return nil, fmt.Errorf(`"bool" function expects 1 argument, %d provided`, len(value))
   113  			}
   114  			v, err := value[0].BoolValue()
   115  			if err != nil {
   116  				return nil, err
   117  			}
   118  			return NewValue(v), nil
   119  		},
   120  	},
   121  	"float": {
   122  		ReturnType: TypeFloat64,
   123  		Handler: func(value ...StaticValue) (Expression, error) {
   124  			if len(value) != 1 {
   125  				return nil, fmt.Errorf(`"float" function expects 1 argument, %d provided`, len(value))
   126  			}
   127  			v, err := value[0].FloatValue()
   128  			if err != nil {
   129  				return nil, err
   130  			}
   131  			return NewValue(v), nil
   132  		},
   133  	},
   134  	"tojson": {
   135  		ReturnType: TypeString,
   136  		Handler: func(value ...StaticValue) (Expression, error) {
   137  			if len(value) != 1 {
   138  				return nil, fmt.Errorf(`"tojson" function expects 1 argument, %d provided`, len(value))
   139  			}
   140  			b, err := json.Marshal(value[0].Value())
   141  			if err != nil {
   142  				return nil, fmt.Errorf(`"tojson" function had problem marshalling: %s`, err.Error())
   143  			}
   144  			return NewValue(string(b)), nil
   145  		},
   146  	},
   147  	"json": {
   148  		Handler: func(value ...StaticValue) (Expression, error) {
   149  			if len(value) != 1 {
   150  				return nil, fmt.Errorf(`"json" function expects 1 argument, %d provided`, len(value))
   151  			}
   152  			if !value[0].IsString() {
   153  				return nil, fmt.Errorf(`"json" function argument should be a string`)
   154  			}
   155  			var v interface{}
   156  			err := json.Unmarshal([]byte(value[0].Value().(string)), &v)
   157  			if err != nil {
   158  				return nil, fmt.Errorf(`"json" function had problem unmarshalling: %s`, err.Error())
   159  			}
   160  			return NewValue(v), nil
   161  		},
   162  	},
   163  	"toyaml": {
   164  		ReturnType: TypeString,
   165  		Handler: func(value ...StaticValue) (Expression, error) {
   166  			if len(value) != 1 {
   167  				return nil, fmt.Errorf(`"toyaml" function expects 1 argument, %d provided`, len(value))
   168  			}
   169  			b, err := yaml.Marshal(value[0].Value())
   170  			if err != nil {
   171  				return nil, fmt.Errorf(`"toyaml" function had problem marshalling: %s`, err.Error())
   172  			}
   173  			return NewValue(string(b)), nil
   174  		},
   175  	},
   176  	"yaml": {
   177  		Handler: func(value ...StaticValue) (Expression, error) {
   178  			if len(value) != 1 {
   179  				return nil, fmt.Errorf(`"yaml" function expects 1 argument, %d provided`, len(value))
   180  			}
   181  			if !value[0].IsString() {
   182  				return nil, fmt.Errorf(`"yaml" function argument should be a string`)
   183  			}
   184  			var v interface{}
   185  			err := yaml.Unmarshal([]byte(value[0].Value().(string)), &v)
   186  			if err != nil {
   187  				return nil, fmt.Errorf(`"yaml" function had problem unmarshalling: %s`, err.Error())
   188  			}
   189  			return NewValue(v), nil
   190  		},
   191  	},
   192  	"shellquote": {
   193  		ReturnType: TypeString,
   194  		Handler: func(value ...StaticValue) (Expression, error) {
   195  			args := make([]string, len(value))
   196  			for i := range value {
   197  				args[i], _ = value[i].StringValue()
   198  			}
   199  			return NewValue(shellquote.Join(args...)), nil
   200  		},
   201  	},
   202  	"shellparse": {
   203  		Handler: func(value ...StaticValue) (Expression, error) {
   204  			if len(value) != 1 {
   205  				return nil, fmt.Errorf(`"shellparse" function expects 1 arguments, %d provided`, len(value))
   206  			}
   207  			v, _ := value[0].StringValue()
   208  			words, err := shellquote.Split(v)
   209  			return NewValue(words), err
   210  		},
   211  	},
   212  	"trim": {
   213  		ReturnType: TypeString,
   214  		Handler: func(value ...StaticValue) (Expression, error) {
   215  			if len(value) != 1 {
   216  				return nil, fmt.Errorf(`"trim" function expects 1 argument, %d provided`, len(value))
   217  			}
   218  			if !value[0].IsString() {
   219  				return nil, fmt.Errorf(`"trim" function argument should be a string`)
   220  			}
   221  			str, _ := value[0].StringValue()
   222  			return NewValue(strings.TrimSpace(str)), nil
   223  		},
   224  	},
   225  	"len": {
   226  		ReturnType: TypeInt64,
   227  		Handler: func(value ...StaticValue) (Expression, error) {
   228  			if len(value) != 1 {
   229  				return nil, fmt.Errorf(`"len" function expects 1 argument, %d provided`, len(value))
   230  			}
   231  			if value[0].IsSlice() {
   232  				v, err := value[0].SliceValue()
   233  				return NewValue(int64(len(v))), err
   234  			}
   235  			if value[0].IsString() {
   236  				v, err := value[0].StringValue()
   237  				return NewValue(int64(len(v))), err
   238  			}
   239  			if value[0].IsMap() {
   240  				v, err := value[0].MapValue()
   241  				return NewValue(int64(len(v))), err
   242  			}
   243  			return nil, fmt.Errorf(`"len" function expects string, slice or map, %v provided`, value[0])
   244  		},
   245  	},
   246  	"floor": {
   247  		ReturnType: TypeInt64,
   248  		Handler: func(value ...StaticValue) (Expression, error) {
   249  			if len(value) != 1 {
   250  				return nil, fmt.Errorf(`"floor" function expects 1 argument, %d provided`, len(value))
   251  			}
   252  			f, err := value[0].FloatValue()
   253  			if err != nil {
   254  				return nil, fmt.Errorf(`"floor" function expects a number, %s provided: %v`, value[0], err)
   255  			}
   256  			return NewValue(int64(math2.Floor(f))), nil
   257  		},
   258  	},
   259  	"ceil": {
   260  		ReturnType: TypeInt64,
   261  		Handler: func(value ...StaticValue) (Expression, error) {
   262  			if len(value) != 1 {
   263  				return nil, fmt.Errorf(`"ceil" function expects 1 argument, %d provided`, len(value))
   264  			}
   265  			f, err := value[0].FloatValue()
   266  			if err != nil {
   267  				return nil, fmt.Errorf(`"ceil" function expects a number, %s provided: %v`, value[0], err)
   268  			}
   269  			return NewValue(int64(math2.Ceil(f))), nil
   270  		},
   271  	},
   272  	"round": {
   273  		ReturnType: TypeInt64,
   274  		Handler: func(value ...StaticValue) (Expression, error) {
   275  			if len(value) != 1 {
   276  				return nil, fmt.Errorf(`"round" function expects 1 argument, %d provided`, len(value))
   277  			}
   278  			f, err := value[0].FloatValue()
   279  			if err != nil {
   280  				return nil, fmt.Errorf(`"round" function expects a number, %s provided: %v`, value[0], err)
   281  			}
   282  			return NewValue(int64(math2.Round(f))), nil
   283  		},
   284  	},
   285  	"chunk": {
   286  		Handler: func(value ...StaticValue) (Expression, error) {
   287  			if len(value) != 2 {
   288  				return nil, fmt.Errorf(`"chunk" function expects 2 arguments, %d provided`, len(value))
   289  			}
   290  			list, err := value[0].SliceValue()
   291  			if err != nil {
   292  				return nil, fmt.Errorf(`"chunk" function expects 1st argument to be a list, %s provided: %v`, value[0], err)
   293  			}
   294  			size, err := value[1].IntValue()
   295  			if err != nil {
   296  				return nil, fmt.Errorf(`"chunk" function expects 2nd argument to be integer, %s provided: %v`, value[1], err)
   297  			}
   298  			if size <= 0 {
   299  				return nil, fmt.Errorf(`"chunk" function expects 2nd argument to be >= 1, %s provided: %v`, value[1], err)
   300  			}
   301  			chunks := make([][]interface{}, 0)
   302  			l := int64(len(list))
   303  			for i := int64(0); i < l; i += size {
   304  				end := i + size
   305  				if end > l {
   306  					end = l
   307  				}
   308  				chunks = append(chunks, list[i:end])
   309  			}
   310  			return NewValue(chunks), nil
   311  		},
   312  	},
   313  	"at": {
   314  		Handler: func(value ...StaticValue) (Expression, error) {
   315  			if len(value) != 2 {
   316  				return nil, fmt.Errorf(`"at" function expects 2 arguments, %d provided`, len(value))
   317  			}
   318  			if value[0].IsSlice() {
   319  				v, _ := value[0].SliceValue()
   320  				k, err := value[1].IntValue()
   321  				if err != nil {
   322  					return nil, fmt.Errorf(`"at" function expects 2nd argument to be number for list, %s provided`, value[1])
   323  				}
   324  				if k >= 0 && k < int64(len(v)) {
   325  					return NewValue(v[int(k)]), nil
   326  				}
   327  				return nil, fmt.Errorf(`"at" function: error: out of bounds (length=%d, index=%d)`, len(v), k)
   328  			}
   329  			if value[0].IsMap() {
   330  				v, _ := value[0].MapValue()
   331  				k, _ := value[1].StringValue()
   332  				item, ok := v[k]
   333  				if ok {
   334  					return NewValue(item), nil
   335  				}
   336  				return None, nil
   337  			}
   338  			if value[0].IsString() {
   339  				v, _ := value[0].StringValue()
   340  				k, err := value[1].IntValue()
   341  				if err != nil {
   342  					return nil, fmt.Errorf(`"at" function expects 2nd argument to be number for string, %s provided`, value[1])
   343  				}
   344  				if k >= 0 && k < int64(len(v)) {
   345  					return NewValue(v[int(k)]), nil
   346  				}
   347  				return nil, fmt.Errorf(`"at" function: error: out of bounds (length=%d, index=%d)`, len(v), k)
   348  			}
   349  			return nil, fmt.Errorf(`"at" function can be performed only on lists, maps and strings: %s provided`, value[0])
   350  		},
   351  	},
   352  	"map": {
   353  		Handler: func(value ...StaticValue) (Expression, error) {
   354  			if len(value) != 2 {
   355  				return nil, fmt.Errorf(`"map" function expects 2 arguments, %d provided`, len(value))
   356  			}
   357  			list, err := value[0].SliceValue()
   358  			if err != nil {
   359  				return nil, fmt.Errorf(`"map" function expects 1st argument to be a list, %s provided: %v`, value[0], err)
   360  			}
   361  			exprStr, _ := value[1].StringValue()
   362  			expr, err := Compile(exprStr)
   363  			if err != nil {
   364  				return nil, fmt.Errorf(`"map" function expects 2nd argument to be valid expression, '%s' provided: %v`, value[1], err)
   365  			}
   366  			result := make([]string, len(list))
   367  			for i := 0; i < len(list); i++ {
   368  				ex, _ := Compile(expr.String())
   369  				v, err := ex.Resolve(NewMachine().Register("_.value", list[i]).Register("_.index", i).Register("_.key", i))
   370  				if err != nil {
   371  					return nil, fmt.Errorf(`"map" function: error while mapping %d index (%v): %v`, i, list[i], err)
   372  				}
   373  				result[i] = v.String()
   374  			}
   375  			return Compile(fmt.Sprintf("list(%s)", strings.Join(result, ",")))
   376  		},
   377  	},
   378  	"filter": {
   379  		Handler: func(value ...StaticValue) (Expression, error) {
   380  			if len(value) != 2 {
   381  				return nil, fmt.Errorf(`"filter" function expects 2 arguments, %d provided`, len(value))
   382  			}
   383  			list, err := value[0].SliceValue()
   384  			if err != nil {
   385  				return nil, fmt.Errorf(`"filter" function expects 1st argument to be a list, %s provided: %v`, value[0], err)
   386  			}
   387  			exprStr, _ := value[1].StringValue()
   388  			expr, err := Compile(exprStr)
   389  			if err != nil {
   390  				return nil, fmt.Errorf(`"filter" function expects 2nd argument to be valid expression, '%s' provided: %v`, value[1], err)
   391  			}
   392  			result := make([]interface{}, 0)
   393  			for i := 0; i < len(list); i++ {
   394  				ex, _ := Compile(expr.String())
   395  				v, err := ex.Resolve(NewMachine().Register("_.value", list[i]).Register("_.index", i).Register("_.key", i))
   396  				if err != nil {
   397  					return nil, fmt.Errorf(`"filter" function: error while filtering %d index (%v): %v`, i, list[i], err)
   398  				}
   399  				if v.Static() == nil {
   400  					// TODO: It shouldn't fail then
   401  					return nil, fmt.Errorf(`"filter" function: could not resolve filter for %d index (%v): %s`, i, list[i], v)
   402  				}
   403  				b, err := v.Static().BoolValue()
   404  				if err != nil {
   405  					return nil, fmt.Errorf(`"filter" function: could not resolve filter for %d index (%v) as boolean: %s`, i, list[i], err)
   406  				}
   407  				if b {
   408  					result = append(result, list[i])
   409  				}
   410  			}
   411  			return NewValue(result), nil
   412  		},
   413  	},
   414  	"eval": {
   415  		Handler: func(value ...StaticValue) (Expression, error) {
   416  			if len(value) != 1 {
   417  				return nil, fmt.Errorf(`"eval" function expects 1 argument, %d provided`, len(value))
   418  			}
   419  			exprStr, _ := value[0].StringValue()
   420  			expr, err := Compile(exprStr)
   421  			if err != nil {
   422  				return nil, fmt.Errorf(`"eval" function: %s: error: %v`, value[0], err)
   423  			}
   424  			return expr, nil
   425  		},
   426  	},
   427  	"jq": {
   428  		Handler: func(value ...StaticValue) (Expression, error) {
   429  			if len(value) != 2 {
   430  				return nil, fmt.Errorf(`"jq" function expects 2 arguments, %d provided`, len(value))
   431  			}
   432  			queryStr, _ := value[1].StringValue()
   433  			query, err := gojq.Parse(queryStr)
   434  			if err != nil {
   435  				return nil, fmt.Errorf(`"jq" error: could not parse the query: %s: %v`, queryStr, err)
   436  			}
   437  
   438  			// Marshal data to basic types
   439  			bytes, err := json.Marshal(value[0].Value())
   440  			if err != nil {
   441  				return nil, fmt.Errorf(`"jq" error: could not marshal the value: %v: %v`, value[0].Value(), err)
   442  			}
   443  			var v interface{}
   444  			_ = json.Unmarshal(bytes, &v)
   445  
   446  			// Run query against the value
   447  			ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second)
   448  			defer ctxCancel()
   449  			iter := query.RunWithContext(ctx, v)
   450  			result := make([]interface{}, 0)
   451  			for {
   452  				v, ok := iter.Next()
   453  				if !ok {
   454  					break
   455  				}
   456  				if err, ok := v.(error); ok {
   457  					return nil, errors.Wrap(err, `"jq" error: executing: %v`)
   458  				}
   459  				result = append(result, v)
   460  			}
   461  			return NewValue(result), nil
   462  		},
   463  	},
   464  }
   465  
   466  const (
   467  	stringCastStdFn = "string"
   468  	boolCastStdFn   = "bool"
   469  	intCastStdFn    = "int"
   470  	floatCastStdFn  = "float"
   471  )
   472  
   473  func CastToString(v Expression) Expression {
   474  	if v.Static() != nil {
   475  		return NewStringValue(v.Static().Value())
   476  	} else if v.Type() == TypeString {
   477  		return v
   478  	}
   479  	return newCall(stringCastStdFn, []callArgument{{expr: v}})
   480  }
   481  
   482  func CastToBool(v Expression) Expression {
   483  	if v.Type() == TypeBool {
   484  		return v
   485  	}
   486  	return newCall(boolCastStdFn, []callArgument{{expr: v}})
   487  }
   488  
   489  func CastToInt(v Expression) Expression {
   490  	if v.Type() == TypeInt64 {
   491  		return v
   492  	}
   493  	return newCall(intCastStdFn, []callArgument{{expr: v}})
   494  }
   495  
   496  func CastToFloat(v Expression) Expression {
   497  	if v.Type() == TypeFloat64 {
   498  		return v
   499  	}
   500  	return newCall(intCastStdFn, []callArgument{{expr: v}})
   501  }
   502  
   503  func IsStdFunction(name string) bool {
   504  	_, ok := stdFunctions[name]
   505  	return ok
   506  }
   507  
   508  func GetStdFunctionReturnType(name string) Type {
   509  	return stdFunctions[name].ReturnType
   510  }
   511  
   512  func CallStdFunction(name string, value ...interface{}) (Expression, error) {
   513  	fn, ok := stdFunctions[name]
   514  	if !ok {
   515  		return nil, fmt.Errorf("function '%s' doesn't exists in standard library", name)
   516  	}
   517  	r := make([]StaticValue, 0, len(value))
   518  	for i := 0; i < len(value); i++ {
   519  		if v, ok := value[i].(StaticValue); ok {
   520  			r = append(r, v)
   521  		} else if v, ok := value[i].(Expression); ok {
   522  			return nil, fmt.Errorf("expression functions can be called only with static values: %s provided", v)
   523  		} else {
   524  			r = append(r, NewValue(value[i]))
   525  		}
   526  	}
   527  	return fn.Handler(r...)
   528  }
   529  
   530  func (*stdMachine) Get(name string) (Expression, bool, error) {
   531  	return nil, false, nil
   532  }
   533  
   534  func (*stdMachine) Call(name string, args ...StaticValue) (Expression, bool, error) {
   535  	fn, ok := stdFunctions[name]
   536  	if ok {
   537  		exp, err := fn.Handler(args...)
   538  		return exp, true, err
   539  	}
   540  	return nil, false, nil
   541  }