github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/helper/expr_string.go (about) 1 package helper 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync" 8 9 "github.com/antonmedv/expr" 10 "github.com/antonmedv/expr/vm" 11 "github.com/observiq/carbon/entry" 12 "github.com/observiq/carbon/errors" 13 ) 14 15 type ExprStringConfig string 16 17 const ( 18 exprStartToken = "EXPR(" 19 exprEndToken = ")" 20 ) 21 22 func (e ExprStringConfig) Build() (*ExprString, error) { 23 s := string(e) 24 rangeStart := 0 25 26 subStrings := make([]string, 0, 4) 27 subExprStrings := make([]string, 0, 4) 28 29 for { 30 rangeEnd := len(s) 31 32 // Find the first instance of the start token 33 indexStart := strings.Index(s[rangeStart:rangeEnd], exprStartToken) 34 if indexStart == -1 { 35 // Start token does not exist in the remainder of the string, 36 // so treat the rest as a string literal 37 subStrings = append(subStrings, s[rangeStart:]) 38 break 39 } else { 40 indexStart = rangeStart + indexStart 41 } 42 43 // Restrict our end token search range to the next instance of the start token 44 nextIndexStart := strings.Index(s[indexStart+len(exprStartToken):], exprStartToken) 45 if nextIndexStart == -1 { 46 rangeEnd = len(s) 47 } else { 48 rangeEnd = indexStart + len(exprStartToken) + nextIndexStart 49 } 50 51 // Greedily search for the last end token in the search range 52 indexEnd := strings.LastIndex(s[indexStart:rangeEnd], exprEndToken) 53 if indexEnd == -1 { 54 // End token does not exist before the next start token 55 // or end of expression string, so treat the remainder of the string 56 // as a string literal 57 subStrings = append(subStrings, s[rangeStart:]) 58 break 59 } else { 60 indexEnd = indexStart + indexEnd 61 } 62 63 // Unscope the indexes and add the partitioned strings 64 subStrings = append(subStrings, s[rangeStart:indexStart]) 65 subExprStrings = append(subExprStrings, s[indexStart+len(exprStartToken):indexEnd]) 66 67 // Reset the starting range and finish if it reaches the end of the string 68 rangeStart = indexEnd + len(exprEndToken) 69 if rangeStart > len(s) { 70 break 71 } 72 } 73 74 subExprs := make([]*vm.Program, 0, len(subExprStrings)) 75 for _, subExprString := range subExprStrings { 76 program, err := expr.Compile(subExprString, expr.AllowUndefinedVariables()) 77 if err != nil { 78 return nil, errors.Wrap(err, "compile embedded expression") 79 } 80 subExprs = append(subExprs, program) 81 } 82 83 return &ExprString{ 84 SubStrings: subStrings, 85 SubExprs: subExprs, 86 }, nil 87 } 88 89 // An ExprString is made up of a list of string literals 90 // interleaved with expressions. len(SubStrings) == len(SubExprs) + 1 91 type ExprString struct { 92 SubStrings []string 93 SubExprs []*vm.Program 94 } 95 96 func (e *ExprString) Render(env map[string]interface{}) (string, error) { 97 var b strings.Builder 98 for i := 0; i < len(e.SubExprs); i++ { 99 b.WriteString(e.SubStrings[i]) 100 out, err := vm.Run(e.SubExprs[i], env) 101 if err != nil { 102 return "", errors.Wrap(err, "render embedded expression") 103 } 104 outString, ok := out.(string) 105 if !ok { 106 return "", fmt.Errorf("embedded expression returned non-string %v", out) 107 } 108 b.WriteString(outString) 109 } 110 b.WriteString(e.SubStrings[len(e.SubStrings)-1]) 111 112 return b.String(), nil 113 } 114 115 var envPool = sync.Pool{ 116 New: func() interface{} { 117 return map[string]interface{}{ 118 "env": os.Getenv, 119 } 120 }, 121 } 122 123 func GetExprEnv(e *entry.Entry) map[string]interface{} { 124 env := envPool.Get().(map[string]interface{}) 125 env["$"] = e.Record 126 env["$record"] = e.Record 127 env["$labels"] = e.Labels 128 env["$resource"] = e.Resource 129 env["$timestamp"] = e.Timestamp 130 131 return env 132 } 133 134 func PutExprEnv(e map[string]interface{}) { 135 envPool.Put(e) 136 }