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  }