| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888 |
- package workflow
- import (
- "encoding/json"
- "fmt"
- "reflect"
- "strconv"
- "strings"
- )
- // ExpressionEvaluator evaluates workflow expressions.
- type ExpressionEvaluator struct {
- context ContextAccessor
- }
- // NewExpressionEvaluator creates a new expression evaluator
- func NewExpressionEvaluator(ctx ContextAccessor) *ExpressionEvaluator {
- return &ExpressionEvaluator{
- context: ctx,
- }
- }
- // EvaluateValue evaluates an arbitrary JSON value using the = prefix convention:
- // - Non-string types (bool, number, etc.): returned as-is (literal value)
- // - String not starting with "=": returned as literal string
- // - String starting with "=": remainder evaluated as expression (e.g. "=$name")
- // - String starting with "==": leading "=" removed, rest returned as literal string (e.g. "==foo" → "=foo")
- func (e *ExpressionEvaluator) EvaluateValue(value interface{}) (interface{}, error) {
- str, ok := value.(string)
- if !ok {
- // Non-string types are literal values
- return value, nil
- }
- if !strings.HasPrefix(str, "=") {
- // No = prefix: literal string
- return str, nil
- }
- if strings.HasPrefix(str, "==") {
- // == prefix: escaped literal, strip one leading =
- return str[1:], nil
- }
- // Single = prefix: evaluate the rest as expression
- return e.Evaluate(str[1:])
- }
- // Evaluate evaluates an expression string and returns the result
- func (e *ExpressionEvaluator) Evaluate(expr string) (interface{}, error) {
- if expr == "" {
- return nil, nil
- }
- expr = strings.TrimSpace(expr)
- // Handle string literals (must be a simple quoted string, not a complex expression)
- if isSimpleStringLiteral(expr) {
- return expr[1 : len(expr)-1], nil
- }
- // Handle boolean literals
- if expr == "true" {
- return true, nil
- }
- if expr == "false" {
- return false, nil
- }
- // Handle null/nil
- if expr == "null" || expr == "nil" {
- return nil, nil
- }
- // Handle numeric literals
- if num, err := strconv.ParseInt(expr, 10, 64); err == nil {
- return num, nil
- }
- if num, err := strconv.ParseFloat(expr, 64); err == nil {
- return num, nil
- }
- // Handle JSON literal arrays and objects
- if strings.HasPrefix(expr, "[") || strings.HasPrefix(expr, "{") {
- var parsed interface{}
- if err := json.Unmarshal([]byte(expr), &parsed); err == nil {
- return parsed, nil
- }
- }
- // Handle logical operators (check before comparison operators)
- if strings.Contains(expr, "&&") {
- return e.evaluateLogicalAnd(expr)
- }
- if strings.Contains(expr, "||") {
- return e.evaluateLogicalOr(expr)
- }
- // Handle comparison operators (check before variable references)
- if strings.Contains(expr, "==") {
- return e.evaluateBinaryOp(expr, "==")
- }
- if strings.Contains(expr, "!=") {
- return e.evaluateBinaryOp(expr, "!=")
- }
- if strings.Contains(expr, ">=") {
- return e.evaluateBinaryOp(expr, ">=")
- }
- if strings.Contains(expr, "<=") {
- return e.evaluateBinaryOp(expr, "<=")
- }
- if strings.Contains(expr, ">") {
- return e.evaluateBinaryOp(expr, ">")
- }
- if strings.Contains(expr, "<") {
- return e.evaluateBinaryOp(expr, "<")
- }
- // Handle negation
- if strings.HasPrefix(expr, "!") {
- val, err := e.Evaluate(strings.TrimSpace(expr[1:]))
- if err != nil {
- return nil, err
- }
- return !toBool(val), nil
- }
- // Handle arithmetic operators
- if e.findOperatorIndex(expr, "+") != -1 {
- return e.evaluateArithmetic(expr, "+")
- }
- if e.findOperatorIndex(expr, "-") != -1 && !e.isSimpleNegativeNumber(expr) {
- return e.evaluateArithmetic(expr, "-")
- }
- if strings.Contains(expr, "*") {
- return e.evaluateArithmetic(expr, "*")
- }
- if strings.Contains(expr, "/") {
- return e.evaluateArithmetic(expr, "/")
- }
- // Handle variable references
- if strings.HasPrefix(expr, "$") || strings.HasPrefix(expr, "_") || strings.HasPrefix(expr, "SYSVAR.") {
- return e.evaluateVariableReference(expr)
- }
- // Try to resolve as a path (could be param reference or variable reference)
- return e.evaluateVariableReference(expr)
- }
- // isSimpleStringLiteral checks if expr is a simple quoted string (not a complex expression)
- func isSimpleStringLiteral(expr string) bool {
- if len(expr) < 2 {
- return false
- }
- quote := expr[0]
- if quote != '"' && quote != '\'' {
- return false
- }
- if expr[len(expr)-1] != quote {
- return false
- }
- // Check that there are no unescaped quotes in the middle
- for i := 1; i < len(expr)-1; i++ {
- if expr[i] == quote && (i == 0 || expr[i-1] != '\\') {
- return false
- }
- }
- return true
- }
- // isSimpleNegativeNumber checks if expr is just a negative number like "-5"
- func (e *ExpressionEvaluator) isSimpleNegativeNumber(expr string) bool {
- if !strings.HasPrefix(expr, "-") {
- return false
- }
- rest := strings.TrimSpace(expr[1:])
- if _, err := strconv.ParseInt(rest, 10, 64); err == nil {
- return true
- }
- if _, err := strconv.ParseFloat(rest, 64); err == nil {
- return true
- }
- return false
- }
- // evaluateVariableReference resolves variable references and paths
- func (e *ExpressionEvaluator) evaluateVariableReference(path string) (interface{}, error) {
- parts := e.parsePath(path)
- if len(parts) == 0 {
- return nil, fmt.Errorf("empty variable path")
- }
- // Get the root variable
- var root interface{}
- var err error
- startIndex := 1
- rootName := parts[0]
- if strings.HasPrefix(rootName, "$") {
- // Global variable
- root, err = e.getGlobalVariable(rootName)
- } else if rootName == "SYSVAR" {
- // System variable - combine SYSVAR with next part for lookup
- if len(parts) < 2 {
- return nil, fmt.Errorf("incomplete SYSVAR reference")
- }
- sysvarKey := "SYSVAR." + parts[1]
- root, err = e.getSystemVariable(sysvarKey)
- startIndex = 2
- } else if strings.HasPrefix(rootName, "_") {
- // Local variable
- root, err = e.getLocalVariable(rootName)
- } else {
- // Try as parameter reference
- root, err = e.getParameter(rootName)
- if err != nil {
- return nil, err
- }
- }
- if err != nil {
- return nil, err
- }
- // Navigate the path
- current := root
- for i := startIndex; i < len(parts); i++ {
- part := parts[i]
- current, err = e.navigatePath(current, part)
- if err != nil {
- return nil, err
- }
- }
- return current, nil
- }
- // parsePath parses a variable path into segments
- // Handles: $var.field, $var[index], $var["literal"], SYSVAR.xxx
- func (e *ExpressionEvaluator) parsePath(path string) []string {
- var parts []string
- current := ""
- inBracket := false
- bracketContent := ""
- for i := 0; i < len(path); i++ {
- ch := path[i]
- switch ch {
- case '.':
- if inBracket {
- bracketContent += string(ch)
- } else {
- if current != "" {
- parts = append(parts, current)
- current = ""
- }
- }
- case '[':
- if current != "" {
- parts = append(parts, current)
- current = ""
- }
- inBracket = true
- bracketContent = ""
- case ']':
- if inBracket {
- // Evaluate bracket content as expression
- val, err := e.Evaluate(bracketContent)
- if err == nil {
- parts = append(parts, fmt.Sprintf("[%v]", val))
- }
- inBracket = false
- bracketContent = ""
- }
- default:
- if inBracket {
- bracketContent += string(ch)
- } else {
- current += string(ch)
- }
- }
- }
- if current != "" {
- parts = append(parts, current)
- }
- return parts
- }
- // navigatePath navigates one level in a nested structure
- func (e *ExpressionEvaluator) navigatePath(obj interface{}, key string) (interface{}, error) {
- if obj == nil {
- return nil, fmt.Errorf("cannot navigate path on nil object")
- }
- // Handle .length property on arrays, slices, and strings
- if key == "length" {
- val := reflect.ValueOf(obj)
- switch val.Kind() {
- case reflect.Slice, reflect.Array:
- return int64(val.Len()), nil
- case reflect.String:
- // Return the number of Unicode code points (runes), not bytes
- return int64(len([]rune(val.String()))), nil
- }
- // Fall through to map lookup so user-defined "length" keys still work
- }
- // Handle array/slice index: [index]
- if strings.HasPrefix(key, "[") && strings.HasSuffix(key, "]") {
- indexStr := key[1 : len(key)-1]
- index, err := strconv.Atoi(indexStr)
- if err != nil {
- return nil, fmt.Errorf("invalid array index: %s", indexStr)
- }
- val := reflect.ValueOf(obj)
- if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
- if index < 0 || index >= val.Len() {
- return nil, fmt.Errorf("index out of range: %d", index)
- }
- return val.Index(index).Interface(), nil
- }
- return nil, fmt.Errorf("cannot index non-array type")
- }
- // Handle map/struct field access
- val := reflect.ValueOf(obj)
- if val.Kind() == reflect.Map {
- mapVal := val.MapIndex(reflect.ValueOf(key))
- if mapVal.IsValid() {
- return mapVal.Interface(), nil
- }
- return nil, nil
- }
- if val.Kind() == reflect.Struct {
- field := val.FieldByName(key)
- if field.IsValid() {
- return field.Interface(), nil
- }
- return nil, fmt.Errorf("field not found: %s", key)
- }
- return nil, fmt.Errorf("cannot access field on type: %T", obj)
- }
- // getParameter retrieves a parameter
- func (e *ExpressionEvaluator) getParameter(name string) (interface{}, error) {
- if val, ok := e.context.GetParam(name); ok {
- return val, nil
- }
- return nil, fmt.Errorf("parameter not found: %s", name)
- }
- // getGlobalVariable retrieves a global variable
- func (e *ExpressionEvaluator) getGlobalVariable(name string) (interface{}, error) {
- if val, ok := e.context.GetVariable(name); ok {
- return val, nil
- }
- return nil, nil // Undefined variables return nil
- }
- // getSystemVariable retrieves a system variable
- func (e *ExpressionEvaluator) getSystemVariable(name string) (interface{}, error) {
- if val, ok := e.context.GetSystemVar(name); ok {
- return val, nil
- }
- return nil, fmt.Errorf("system variable not found: %s", name)
- }
- // getLocalVariable retrieves a local variable
- func (e *ExpressionEvaluator) getLocalVariable(name string) (interface{}, error) {
- if val, ok := e.context.GetLocalVar(name); ok {
- return val, nil
- }
- return nil, fmt.Errorf("local variable not found: %s", name)
- }
- // evaluateBinaryOp evaluates binary comparison operations
- func (e *ExpressionEvaluator) evaluateBinaryOp(expr string, op string) (interface{}, error) {
- parts := strings.SplitN(expr, op, 2)
- if len(parts) != 2 {
- return nil, fmt.Errorf("invalid binary operation: %s", expr)
- }
- left, err := e.Evaluate(strings.TrimSpace(parts[0]))
- if err != nil {
- return nil, err
- }
- right, err := e.Evaluate(strings.TrimSpace(parts[1]))
- if err != nil {
- return nil, err
- }
- return e.compare(left, right, op)
- }
- // compare compares two values based on the operator
- func (e *ExpressionEvaluator) compare(left, right interface{}, op string) (bool, error) {
- switch op {
- case "==":
- return e.equals(left, right), nil
- case "!=":
- return !e.equals(left, right), nil
- case ">", ">=", "<", "<=":
- return e.compareNumeric(left, right, op)
- default:
- return false, fmt.Errorf("unknown operator: %s", op)
- }
- }
- // equals compares two values for equality, normalizing numeric types
- func (e *ExpressionEvaluator) equals(left, right interface{}) bool {
- // Handle nil cases
- if left == nil && right == nil {
- return true
- }
- if left == nil || right == nil {
- return false
- }
- // Normalize numeric types for comparison
- if isNumeric(left) && isNumeric(right) {
- return toFloat64(left) == toFloat64(right)
- }
- return reflect.DeepEqual(left, right)
- }
- func isNumeric(val interface{}) bool {
- switch val.(type) {
- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
- return true
- default:
- return false
- }
- }
- // compareNumeric compares numeric values
- func (e *ExpressionEvaluator) compareNumeric(left, right interface{}, op string) (bool, error) {
- l := toFloat64(left)
- r := toFloat64(right)
- switch op {
- case ">":
- return l > r, nil
- case ">=":
- return l >= r, nil
- case "<":
- return l < r, nil
- case "<=":
- return l <= r, nil
- default:
- return false, fmt.Errorf("unknown numeric operator: %s", op)
- }
- }
- // evaluateLogicalAnd evaluates logical AND
- func (e *ExpressionEvaluator) evaluateLogicalAnd(expr string) (interface{}, error) {
- parts := strings.Split(expr, "&&")
- for _, part := range parts {
- val, err := e.Evaluate(strings.TrimSpace(part))
- if err != nil {
- return nil, err
- }
- if !toBool(val) {
- return false, nil
- }
- }
- return true, nil
- }
- // evaluateLogicalOr evaluates logical OR
- func (e *ExpressionEvaluator) evaluateLogicalOr(expr string) (interface{}, error) {
- parts := strings.Split(expr, "||")
- for _, part := range parts {
- val, err := e.Evaluate(strings.TrimSpace(part))
- if err != nil {
- return nil, err
- }
- if toBool(val) {
- return true, nil
- }
- }
- return false, nil
- }
- // evaluateArithmetic evaluates arithmetic operations
- func (e *ExpressionEvaluator) evaluateArithmetic(expr string, op string) (interface{}, error) {
- // Find the operator (avoiding string literals)
- opIndex := e.findOperatorIndex(expr, op)
- if opIndex == -1 {
- return nil, fmt.Errorf("operator not found: %s", op)
- }
- leftExpr := strings.TrimSpace(expr[:opIndex])
- rightExpr := strings.TrimSpace(expr[opIndex+len(op):])
- left, err := e.Evaluate(leftExpr)
- if err != nil {
- return nil, err
- }
- right, err := e.Evaluate(rightExpr)
- if err != nil {
- return nil, err
- }
- // Handle string concatenation with +
- if op == "+" {
- if isString(left) || isString(right) {
- return toString(left) + toString(right), nil
- }
- }
- // Numeric operations
- l := toFloat64(left)
- r := toFloat64(right)
- switch op {
- case "+":
- return l + r, nil
- case "-":
- return l - r, nil
- case "*":
- return l * r, nil
- case "/":
- if r == 0 {
- return nil, fmt.Errorf("division by zero")
- }
- return l / r, nil
- default:
- return nil, fmt.Errorf("unknown arithmetic operator: %s", op)
- }
- }
- // findOperatorIndex finds the index of an operator, avoiding string literals
- func (e *ExpressionEvaluator) findOperatorIndex(expr string, op string) int {
- inString := false
- stringChar := rune(0)
- for i := 0; i < len(expr)-len(op)+1; i++ {
- ch := rune(expr[i])
- if ch == '"' || ch == '\'' {
- if !inString {
- inString = true
- stringChar = ch
- } else if ch == stringChar {
- inString = false
- }
- }
- if !inString && strings.HasPrefix(expr[i:], op) {
- return i
- }
- }
- return -1
- }
- // Helper functions
- func toBool(val interface{}) bool {
- if val == nil {
- return false
- }
- switch v := val.(type) {
- case bool:
- return v
- case int, int64, float64:
- return toFloat64(v) != 0
- case string:
- return v != ""
- default:
- return true
- }
- }
- func toFloat64(val interface{}) float64 {
- if val == nil {
- return 0
- }
- switch v := val.(type) {
- case int:
- return float64(v)
- case int64:
- return float64(v)
- case float64:
- return v
- case float32:
- return float64(v)
- case string:
- f, _ := strconv.ParseFloat(v, 64)
- return f
- default:
- return 0
- }
- }
- func toString(val interface{}) string {
- if val == nil {
- return ""
- }
- return fmt.Sprintf("%v", val)
- }
- func isString(val interface{}) bool {
- _, ok := val.(string)
- return ok
- }
- // SetVariable sets a variable value using a path expression
- func (e *ExpressionEvaluator) SetVariable(path string, value interface{}) error {
- parts := e.parsePath(path)
- if len(parts) == 0 {
- return fmt.Errorf("empty variable path")
- }
- rootName := parts[0]
- // Check if trying to set a parameter (read-only)
- if !strings.HasPrefix(rootName, "$") && !strings.HasPrefix(rootName, "_") {
- // This is a parameter reference - check if it exists
- if _, ok := e.context.GetParam(rootName); ok {
- return fmt.Errorf("cannot modify parameter: %s (parameters are read-only)", rootName)
- }
- }
- // Simple case: direct assignment
- if len(parts) == 1 {
- if strings.HasPrefix(rootName, "$") {
- // Apply type conversion if needed
- convertedValue, err := e.applyTypeConversion(rootName, value)
- if err != nil {
- return err
- }
- e.context.SetVariable(rootName, convertedValue)
- return nil
- }
- return fmt.Errorf("cannot set non-global variable: %s", rootName)
- }
- // Special case: $var[index] - direct slice index assignment with auto-grow
- if len(parts) == 2 && strings.HasPrefix(rootName, "$") {
- lastKey := parts[1]
- if strings.HasPrefix(lastKey, "[") && strings.HasSuffix(lastKey, "]") {
- indexStr := lastKey[1 : len(lastKey)-1]
- index, err := strconv.Atoi(indexStr)
- if err == nil {
- // Use atomic SetArrayIndex to avoid race conditions
- e.context.SetArrayIndex(rootName, index, value)
- return nil
- }
- }
- }
- // Deep set with auto-creation of intermediates
- if !strings.HasPrefix(rootName, "$") {
- return fmt.Errorf("cannot set non-global variable: %s", rootName)
- }
- root, _ := e.context.GetVariable(rootName)
- if root == nil {
- // Auto-create root: slice if next part is [N], map otherwise
- if isArrayIndex(parts[1]) {
- root = make([]interface{}, 0)
- } else {
- root = make(map[string]interface{})
- }
- e.context.SetVariable(rootName, root)
- }
- // Navigate to parent of the final field, auto-creating intermediates.
- // We track (parent, key) so we can propagate slice growth back up.
- current := root
- for i := 1; i < len(parts)-1; i++ {
- key := parts[i]
- // Determine what type the next level should be
- nextIsArray := i+1 < len(parts) && isArrayIndex(parts[i+1])
- if isArrayIndex(key) {
- idx, _ := strconv.Atoi(key[1 : len(key)-1])
- slice, ok := current.([]interface{})
- if !ok {
- return fmt.Errorf("cannot index non-array type %T at path segment %s", current, key)
- }
- // Auto-grow slice
- if idx >= len(slice) {
- grown := make([]interface{}, idx+1)
- copy(grown, slice)
- slice = grown
- // Propagate grown slice back: if root level, update variable
- if i == 1 {
- e.context.SetVariable(rootName, slice)
- }
- }
- // Auto-create element if nil
- if slice[idx] == nil {
- if nextIsArray {
- slice[idx] = make([]interface{}, 0)
- } else {
- slice[idx] = make(map[string]interface{})
- }
- }
- current = slice[idx]
- } else {
- m, ok := current.(map[string]interface{})
- if !ok {
- return fmt.Errorf("cannot access field %s on type %T", key, current)
- }
- if m[key] == nil {
- if nextIsArray {
- m[key] = make([]interface{}, 0)
- } else {
- m[key] = make(map[string]interface{})
- }
- }
- current = m[key]
- }
- }
- // Set the final field
- lastKey := parts[len(parts)-1]
- return e.setField(current, lastKey, value)
- }
- // setField sets a field on an object
- func (e *ExpressionEvaluator) setField(obj interface{}, key string, value interface{}) error {
- if obj == nil {
- return fmt.Errorf("cannot set field on nil object")
- }
- // Handle map
- if m, ok := obj.(map[string]interface{}); ok {
- m[key] = value
- return nil
- }
- // Handle slice index assignment (key is "[N]" format)
- if strings.HasPrefix(key, "[") && strings.HasSuffix(key, "]") {
- indexStr := key[1 : len(key)-1]
- index, err := strconv.Atoi(indexStr)
- if err != nil {
- return fmt.Errorf("invalid array index: %s", indexStr)
- }
- rv := reflect.ValueOf(obj)
- if rv.Kind() == reflect.Slice {
- // Grow slice if necessary
- if index >= rv.Len() {
- // We need to get the pointer to the slice in the parent to grow it
- // For now, return an error if index is out of bounds
- return fmt.Errorf("array index %d out of bounds (length %d)", index, rv.Len())
- }
- rv.Index(index).Set(reflect.ValueOf(value))
- return nil
- }
- }
- return fmt.Errorf("cannot set field on type: %T", obj)
- }
- // isArrayIndex returns true if the path segment is an array index like "[0]", "[2]"
- func isArrayIndex(segment string) bool {
- return strings.HasPrefix(segment, "[") && strings.HasSuffix(segment, "]")
- }
- // EvaluateDeep recursively evaluates expressions in nested structures (maps, arrays)
- // It walks through the entire structure and evaluates any string values that look like expressions
- func (e *ExpressionEvaluator) EvaluateDeep(value interface{}) (interface{}, error) {
- switch v := value.(type) {
- case string:
- // Evaluate string values using EvaluateValue (handles = prefix convention)
- return e.EvaluateValue(v)
- case map[string]interface{}:
- // Recursively evaluate all values in the map
- result := make(map[string]interface{})
- for key, val := range v {
- evaluated, err := e.EvaluateDeep(val)
- if err != nil {
- return nil, fmt.Errorf("failed to evaluate map key %s: %w", key, err)
- }
- result[key] = evaluated
- }
- return result, nil
- case []interface{}:
- // Recursively evaluate all elements in the array
- result := make([]interface{}, len(v))
- for i, val := range v {
- evaluated, err := e.EvaluateDeep(val)
- if err != nil {
- return nil, fmt.Errorf("failed to evaluate array index %d: %w", i, err)
- }
- result[i] = evaluated
- }
- return result, nil
- default:
- // For other types (int, bool, float, etc.), return as-is
- return value, nil
- }
- }
- // applyTypeConversion applies type conversion based on variable type declaration
- // If the variable is declared as OBJECT and the value is a string, it parses the string as JSON
- // If the variable is declared as an array type (e.g., [OBJECT], [STRING]) and the value is a string, it parses as JSON array
- // If the variable is declared as STRING and the value is not a string, it marshals the value to JSON
- func (e *ExpressionEvaluator) applyTypeConversion(varName string, value interface{}) (interface{}, error) {
- // Get base context to access VarTypes
- baseCtx := e.context.GetBaseContext()
- if baseCtx.VarTypes == nil {
- return value, nil
- }
- // Check if variable has a type declaration
- varType, ok := baseCtx.VarTypes[varName]
- if !ok {
- return value, nil
- }
- // Check if type is an array type like [OBJECT], [STRING], etc.
- if strings.HasPrefix(varType, "[") && strings.HasSuffix(varType, "]") {
- // Array type
- if str, ok := value.(string); ok {
- var result []interface{}
- if err := json.Unmarshal([]byte(str), &result); err != nil {
- return nil, fmt.Errorf("failed to convert string to %s for variable %s (original string: %q): %w", varType, varName, str, err)
- }
- return result, nil
- }
- } else if varType == "OBJECT" {
- // If type is OBJECT and value is string, parse as JSON
- if str, ok := value.(string); ok {
- var result map[string]interface{}
- if err := json.Unmarshal([]byte(str), &result); err != nil {
- return nil, fmt.Errorf("failed to convert string to OBJECT for variable %s (original string: %q): %w", varName, str, err)
- }
- return result, nil
- }
- } else if varType == "INT" {
- // If type is INT and value is a string, parse as integer
- if str, ok := value.(string); ok {
- if n, err := strconv.ParseInt(str, 10, 64); err == nil {
- return n, nil
- }
- // Also try parsing as float then truncating
- if f, err := strconv.ParseFloat(str, 64); err == nil {
- return int64(f), nil
- }
- return nil, fmt.Errorf("failed to convert string %q to INT for variable %s", str, varName)
- }
- // If value is float64, convert to int64
- if f, ok := value.(float64); ok {
- return int64(f), nil
- }
- } else if varType == "FLOAT" {
- // If type is FLOAT and value is a string, parse as float
- if str, ok := value.(string); ok {
- if f, err := strconv.ParseFloat(str, 64); err == nil {
- return f, nil
- }
- return nil, fmt.Errorf("failed to convert string %q to FLOAT for variable %s", str, varName)
- }
- // If value is int64, convert to float64
- if n, ok := value.(int64); ok {
- return float64(n), nil
- }
- } else if varType == "STRING" {
- // If type is STRING and value is not a string, marshal to JSON
- if _, ok := value.(string); !ok {
- // For non-string values (objects, arrays, etc.), marshal to JSON string
- jsonBytes, err := json.Marshal(value)
- if err != nil {
- return nil, fmt.Errorf("failed to convert %T to STRING for variable %s: %w", value, varName, err)
- }
- return string(jsonBytes), nil
- }
- }
- return value, nil
- }
|