github.com/Jeffail/benthos/v3@v3.65.0/lib/util/text/function_vars.go (about) 1 package text 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "os" 7 "regexp" 8 "strconv" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/Jeffail/benthos/v3/lib/types" 14 "github.com/Jeffail/gabs/v2" 15 "github.com/gofrs/uuid" 16 ) 17 18 //------------------------------------------------------------------------------ 19 20 // Message is an interface type to be given to a function interpolator, it 21 // allows the function to resolve fields and metadata from a message. 22 type Message interface { 23 Get(p int) types.Part 24 Len() int 25 } 26 27 //------------------------------------------------------------------------------ 28 29 func jsonFieldFunction(msg Message, index int, arg string) []byte { 30 part := index 31 if argIndex := strings.LastIndex(arg, ","); argIndex > 0 && len(arg) > argIndex { 32 partB, err := strconv.ParseInt(arg[argIndex+1:], 10, 64) 33 if err == nil { 34 part = int(partB) 35 } 36 arg = arg[:argIndex] 37 } 38 jPart, err := msg.Get(part).JSON() 39 if err != nil { 40 return []byte("null") 41 } 42 gPart := gabs.Wrap(jPart) 43 if len(arg) > 0 { 44 gPart = gPart.Path(arg) 45 } 46 switch t := gPart.Data().(type) { 47 case string: 48 return []byte(t) 49 case nil: 50 return []byte(`null`) 51 } 52 return gPart.Bytes() 53 } 54 55 func metadataFunction(msg Message, index int, arg string) []byte { 56 part := index 57 if argIndex := strings.LastIndex(arg, ","); argIndex > 0 && len(arg) > argIndex { 58 partB, err := strconv.ParseInt(arg[argIndex+1:], 10, 64) 59 if err == nil { 60 part = int(partB) 61 } 62 arg = arg[:argIndex] 63 } 64 if arg == "" { 65 return []byte("") 66 } 67 meta := msg.Get(part).Metadata() 68 return []byte(meta.Get(arg)) 69 } 70 71 func metadataMapFunction(msg Message, index int, arg string) []byte { 72 part := index 73 if len(arg) > 0 { 74 partB, err := strconv.ParseInt(arg, 10, 64) 75 if err == nil { 76 part = int(partB) 77 } 78 } 79 kvs := map[string]string{} 80 msg.Get(part).Metadata().Iter(func(k, v string) error { 81 kvs[k] = v 82 return nil 83 }) 84 result, err := json.Marshal(kvs) 85 if err != nil { 86 return []byte("") 87 } 88 return result 89 } 90 91 func errorFunction(msg Message, index int, arg string) []byte { 92 part := index 93 if len(arg) > 0 { 94 partB, err := strconv.ParseInt(arg, 10, 64) 95 if err == nil { 96 part = int(partB) 97 } 98 } 99 return []byte(msg.Get(part).Metadata().Get(types.FailFlagKey)) 100 } 101 102 func contentFunction(msg Message, index int, arg string) []byte { 103 part := index 104 if len(arg) > 0 { 105 partB, err := strconv.ParseInt(arg, 10, 64) 106 if err == nil { 107 part = int(partB) 108 } 109 } 110 return msg.Get(part).Get() 111 } 112 113 //------------------------------------------------------------------------------ 114 115 var ( 116 functionRegex = regexp.MustCompile(`\${![a-z0-9_]+(:[^}]+)?}`) 117 escapedFunctionRegex = regexp.MustCompile(`\${({![a-z0-9_]+(:[^}]+)?})}`) 118 119 counters = map[string]uint64{} 120 countersMux = &sync.Mutex{} 121 122 functionVars = map[string]func(msg Message, index int, arg string) []byte{ 123 "timestamp_unix_nano": func(_ Message, _ int, arg string) []byte { 124 return []byte(strconv.FormatInt(time.Now().UnixNano(), 10)) 125 }, 126 "timestamp_unix": func(_ Message, _ int, arg string) []byte { 127 tNow := time.Now() 128 precision, _ := strconv.ParseInt(arg, 10, 64) 129 tStr := strconv.FormatInt(tNow.Unix(), 10) 130 if precision > 0 { 131 nanoStr := strconv.FormatInt(int64(tNow.Nanosecond()), 10) 132 if lNano := int64(len(nanoStr)); precision >= lNano { 133 precision = lNano - 1 134 } 135 tStr = tStr + "." + nanoStr[:precision] 136 } 137 return []byte(tStr) 138 }, 139 "timestamp": func(_ Message, _ int, arg string) []byte { 140 if arg == "" { 141 arg = "Mon Jan 2 15:04:05 -0700 MST 2006" 142 } 143 return []byte(time.Now().Format(arg)) 144 }, 145 "timestamp_utc": func(_ Message, _ int, arg string) []byte { 146 if arg == "" { 147 arg = "Mon Jan 2 15:04:05 -0700 MST 2006" 148 } 149 return []byte(time.Now().In(time.UTC).Format(arg)) 150 }, 151 "hostname": func(_ Message, _ int, arg string) []byte { 152 hn, _ := os.Hostname() 153 return []byte(hn) 154 }, 155 "echo": func(_ Message, _ int, arg string) []byte { 156 return []byte(arg) 157 }, 158 "count": func(_ Message, _ int, arg string) []byte { 159 countersMux.Lock() 160 defer countersMux.Unlock() 161 162 var count uint64 163 var exists bool 164 165 if count, exists = counters[arg]; exists { 166 count++ 167 } else { 168 count = 1 169 } 170 counters[arg] = count 171 172 return []byte(strconv.FormatUint(count, 10)) 173 }, 174 "error": errorFunction, 175 "content": contentFunction, 176 "json_field": jsonFieldFunction, 177 "metadata": metadataFunction, 178 "metadata_json_object": metadataMapFunction, 179 "batch_size": func(m Message, _ int, _ string) []byte { 180 return strconv.AppendInt(nil, int64(m.Len()), 10) 181 }, 182 "uuid_v4": func(_ Message, _ int, _ string) []byte { 183 u4, err := uuid.NewV4() 184 if err != nil { 185 panic(err) 186 } 187 return []byte(u4.String()) 188 }, 189 } 190 ) 191 192 // ContainsFunctionVariables returns true if inBytes contains function variable 193 // replace patterns. 194 func ContainsFunctionVariables(inBytes []byte) bool { 195 return functionRegex.Find(inBytes) != nil || escapedFunctionRegex.Find(inBytes) != nil 196 } 197 198 func escapeBytes(in []byte) []byte { 199 quoted := strconv.Quote(string(in)) 200 if len(quoted) < 3 { 201 return in 202 } 203 return []byte(quoted[1 : len(quoted)-1]) 204 } 205 206 // ReplaceFunctionVariables will search a blob of data for the pattern 207 // `${!foo}`, where `foo` is a function name. 208 // 209 // For each aforementioned pattern found in the blob the contents of the 210 // respective function will be run and will replace the pattern. 211 // 212 // Some functions are able to extract contents and metadata from a message, and 213 // so a message must be supplied. 214 func ReplaceFunctionVariables(msg Message, inBytes []byte) []byte { 215 return ReplaceFunctionVariablesFor(msg, 0, inBytes) 216 } 217 218 // ReplaceFunctionVariablesFor will search a blob of data for the pattern 219 // `${!foo}`, where `foo` is a function name. 220 // 221 // For each aforementioned pattern found in the blob the contents of the 222 // respective function will be run and will replace the pattern. 223 // 224 // Some functions are able to extract contents and metadata from a message, and 225 // so a message must be supplied along with the specific index of the message 226 // part that this function should be resolved for. 227 func ReplaceFunctionVariablesFor(msg Message, index int, inBytes []byte) []byte { 228 return replaceFunctionVariables(msg, index, false, inBytes) 229 } 230 231 // ReplaceFunctionVariablesEscaped will search a blob of data for the pattern 232 // `${!foo}`, where `foo` is a function name. 233 // 234 // For each aforementioned pattern found in the blob the contents of the 235 // respective function will be run and will replace the pattern. 236 // 237 // The contents of the swapped pattern is escaped such that it can be safely 238 // injected within the contents of a JSON object. 239 // 240 // Some functions are able to extract contents and metadata from a message, and 241 // so a message must be supplied. 242 func ReplaceFunctionVariablesEscaped(msg Message, inBytes []byte) []byte { 243 return ReplaceFunctionVariablesEscapedFor(msg, 0, inBytes) 244 } 245 246 // ReplaceFunctionVariablesEscapedFor will search a blob of data for the pattern 247 // `${!foo}`, where `foo` is a function name. 248 // 249 // For each aforementioned pattern found in the blob the contents of the 250 // respective function will be run and will replace the pattern. 251 // 252 // The contents of the swapped pattern is escaped such that it can be safely 253 // injected within the contents of a JSON object. 254 // 255 // Some functions are able to extract contents and metadata from a message, and 256 // so a message must be supplied along with the specific index of the message 257 // part that this function should be resolved for. 258 func ReplaceFunctionVariablesEscapedFor(msg Message, index int, inBytes []byte) []byte { 259 return replaceFunctionVariables(msg, index, true, inBytes) 260 } 261 262 func replaceFunctionVariables( 263 msg Message, index int, escape bool, inBytes []byte, 264 ) []byte { 265 replaced := functionRegex.ReplaceAllFunc(inBytes, func(content []byte) []byte { 266 if len(content) > 4 { 267 if colonIndex := bytes.IndexByte(content, ':'); colonIndex == -1 { 268 if ftor, exists := functionVars[string(content[3:len(content)-1])]; exists { 269 if escape { 270 return escapeBytes(ftor(msg, index, "")) 271 } 272 return ftor(msg, index, "") 273 } 274 } else { 275 argVal := string(content[colonIndex+1 : len(content)-1]) 276 if ftor, exists := functionVars[string(content[3:colonIndex])]; exists { 277 if escape { 278 return escapeBytes(ftor(msg, index, argVal)) 279 } 280 return ftor(msg, index, argVal) 281 } 282 } 283 } 284 return content 285 }) 286 replaced = escapedFunctionRegex.ReplaceAll(replaced, []byte(`$$$1`)) 287 return replaced 288 } 289 290 //------------------------------------------------------------------------------