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 }