github.com/Jeffail/benthos/v3@v3.65.0/lib/message/mapper/type.go (about) 1 package mapper 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 8 "github.com/Jeffail/benthos/v3/lib/log" 9 "github.com/Jeffail/benthos/v3/lib/message" 10 "github.com/Jeffail/benthos/v3/lib/metrics" 11 "github.com/Jeffail/benthos/v3/lib/types" 12 "github.com/Jeffail/gabs/v2" 13 ) 14 15 //------------------------------------------------------------------------------ 16 17 // Type contains conditions and maps for transforming a batch of messages into 18 // a subset of request messages, and mapping results from those requests back 19 // into the original message batch. 20 type Type struct { 21 log log.Modular 22 stats metrics.Type 23 24 reqTargets []string 25 reqMap map[string]string 26 reqOptTargets []string 27 reqOptMap map[string]string 28 29 resTargets []string 30 resMap map[string]string 31 resOptTargets []string 32 resOptMap map[string]string 33 34 conditions []types.Condition 35 36 // Metrics 37 mErrParts metrics.StatCounter 38 39 mCondPass metrics.StatCounter 40 mCondFail metrics.StatCounter 41 42 mReqErr metrics.StatCounter 43 mReqErrJSON metrics.StatCounter 44 mReqErrMap metrics.StatCounter 45 46 mResErr metrics.StatCounter 47 mResErrJSON metrics.StatCounter 48 mResErrParts metrics.StatCounter 49 mResErrMap metrics.StatCounter 50 } 51 52 // New creates a new mapper Type. 53 func New(opts ...func(*Type)) (*Type, error) { 54 t := &Type{ 55 reqMap: map[string]string{}, 56 reqOptMap: map[string]string{}, 57 resMap: map[string]string{}, 58 resOptMap: map[string]string{}, 59 conditions: nil, 60 log: log.Noop(), 61 stats: metrics.Noop(), 62 } 63 64 for _, opt := range opts { 65 opt(t) 66 } 67 68 var err error 69 if t.reqTargets, err = validateMap(t.reqMap); err != nil { 70 return nil, fmt.Errorf("bad request mandatory map: %v", err) 71 } 72 if t.reqOptTargets, err = validateMap(t.reqOptMap); err != nil { 73 return nil, fmt.Errorf("bad request optional map: %v", err) 74 } 75 if t.resTargets, err = validateMap(t.resMap); err != nil { 76 return nil, fmt.Errorf("bad response mandatory map: %v", err) 77 } 78 if t.resOptTargets, err = validateMap(t.resOptMap); err != nil { 79 return nil, fmt.Errorf("bad response optional map: %v", err) 80 } 81 82 t.mErrParts = t.stats.GetCounter("error.parts_diverged") 83 84 t.mCondPass = t.stats.GetCounter("condition.pass") 85 t.mCondFail = t.stats.GetCounter("condition.fail") 86 87 t.mReqErr = t.stats.GetCounter("request.error") 88 t.mReqErrJSON = t.stats.GetCounter("request.error.json") 89 t.mReqErrMap = t.stats.GetCounter("request.error.map") 90 91 t.mResErr = t.stats.GetCounter("response.error") 92 t.mResErrParts = t.stats.GetCounter("response.error.parts_diverged") 93 t.mResErrMap = t.stats.GetCounter("response.error.map") 94 t.mResErrJSON = t.stats.GetCounter("response.error.json") 95 96 return t, nil 97 } 98 99 //------------------------------------------------------------------------------ 100 101 // OptSetReqMap sets the mandatory request map used by this type. 102 func OptSetReqMap(m map[string]string) func(*Type) { 103 return func(t *Type) { 104 t.reqMap = m 105 } 106 } 107 108 // OptSetOptReqMap sets the optional request map used by this type. 109 func OptSetOptReqMap(m map[string]string) func(*Type) { 110 return func(t *Type) { 111 t.reqOptMap = m 112 } 113 } 114 115 // OptSetResMap sets the mandatory response map used by this type. 116 func OptSetResMap(m map[string]string) func(*Type) { 117 return func(t *Type) { 118 t.resMap = m 119 } 120 } 121 122 // OptSetOptResMap sets the optional response map used by this type. 123 func OptSetOptResMap(m map[string]string) func(*Type) { 124 return func(t *Type) { 125 t.resOptMap = m 126 } 127 } 128 129 // OptSetConditions sets the conditions used by this type. 130 func OptSetConditions(conditions []types.Condition) func(*Type) { 131 return func(t *Type) { 132 t.conditions = conditions 133 } 134 } 135 136 // OptSetLogger sets the logger used by this type. 137 func OptSetLogger(l log.Modular) func(*Type) { 138 return func(t *Type) { 139 t.log = l 140 } 141 } 142 143 // OptSetStats sets the metrics aggregator used by this type. 144 func OptSetStats(s metrics.Type) func(*Type) { 145 return func(t *Type) { 146 t.stats = s 147 } 148 } 149 150 //------------------------------------------------------------------------------ 151 152 func validateMap(m map[string]string) ([]string, error) { 153 targets := make([]string, 0, len(m)) 154 for k, v := range m { 155 if k == "." { 156 if _, exists := m[""]; exists { 157 return nil, errors.New("dot path '.' and empty path '' both set root") 158 } 159 m[""] = v 160 delete(m, ".") 161 k = "" 162 } 163 if v == "." { 164 m[k] = "" 165 } 166 targets = append(targets, k) 167 } 168 sort.Slice(targets, func(i, j int) bool { return len(targets[i]) < len(targets[j]) }) 169 return targets, nil 170 } 171 172 //------------------------------------------------------------------------------ 173 174 // TargetsUsed returns a list of dot paths that this mapper depends on. 175 func (t *Type) TargetsUsed() []string { 176 depsMap := map[string]struct{}{} 177 for _, v := range t.reqMap { 178 depsMap[v] = struct{}{} 179 } 180 for _, v := range t.reqOptMap { 181 depsMap[v] = struct{}{} 182 } 183 deps := []string{} 184 for k := range depsMap { 185 deps = append(deps, k) 186 } 187 sort.Strings(deps) 188 return deps 189 } 190 191 // TargetsProvided returns a list of dot paths that this mapper provides. 192 func (t *Type) TargetsProvided() []string { 193 targetsMap := map[string]struct{}{} 194 for k := range t.resMap { 195 targetsMap[k] = struct{}{} 196 } 197 for k := range t.resOptMap { 198 targetsMap[k] = struct{}{} 199 } 200 targets := []string{} 201 for k := range targetsMap { 202 targets = append(targets, k) 203 } 204 sort.Strings(targets) 205 return targets 206 } 207 208 //------------------------------------------------------------------------------ 209 210 // test a message against the conditions of a mapper. 211 func (t *Type) test(msg types.Message) bool { 212 for _, c := range t.conditions { 213 if !c.Check(msg) { 214 t.mCondFail.Incr(1) 215 return false 216 } 217 } 218 t.mCondPass.Incr(1) 219 return true 220 } 221 222 func getGabs(msg types.Message, index int) (*gabs.Container, error) { 223 payloadObj, err := msg.Get(index).JSON() 224 if err != nil { 225 return nil, err 226 } 227 container := gabs.Wrap(payloadObj) 228 return container, nil 229 } 230 231 // MapRequests takes a single payload (of potentially multiple parts, where 232 // parts can potentially be nil) and maps the parts according to the request 233 // mapping. 234 // 235 // Two arrays are also returned, the first containing all message part indexes 236 // that were skipped due to either failed conditions or for being empty. The 237 // second contains only message part indexes that failed their map stage. 238 func (t *Type) MapRequests(msg types.Message) (skipped, failed []int) { 239 var mappedParts []types.Part 240 241 partLoop: 242 for i := 0; i < msg.Len(); i++ { 243 // Skip if message part is empty. 244 if msg.Get(i).IsEmpty() { 245 skipped = append(skipped, i) 246 continue partLoop 247 } 248 249 // Skip if message part fails condition. 250 if !t.test(message.Lock(msg, i)) { 251 skipped = append(skipped, i) 252 continue partLoop 253 } 254 255 if len(t.reqMap) == 0 && len(t.reqOptMap) == 0 { 256 mappedParts = append(mappedParts, msg.Get(i)) 257 continue partLoop 258 } 259 260 sourceObj, err := getGabs(msg, i) 261 if err != nil { 262 t.mReqErr.Incr(1) 263 t.mReqErrJSON.Incr(1) 264 t.log.Debugf("Failed to parse message part '%v': %v. Failed part: %q\n", i, err, msg.Get(i).Get()) 265 266 // Skip if message part fails JSON parse. 267 failed = append(failed, i) 268 continue partLoop 269 } 270 271 destObj := gabs.New() 272 for _, k := range t.reqTargets { 273 v := t.reqMap[k] 274 src := sourceObj 275 if len(v) > 0 { 276 src = sourceObj.Path(v) 277 if src.Data() == nil { 278 t.mReqErr.Incr(1) 279 t.mReqErrMap.Incr(1) 280 t.log.Debugf("Failed to find request map target '%v' in message part '%v'.\n", v, i) 281 282 // Skip if message part fails mapping. 283 failed = append(failed, i) 284 continue partLoop 285 } 286 } 287 srcData, _ := message.CopyJSON(src.Data()) 288 if len(k) > 0 { 289 destObj.SetP(srcData, k) 290 } else { 291 destObj = gabs.Wrap(srcData) 292 } 293 } 294 for _, k := range t.reqOptTargets { 295 v := t.reqOptMap[k] 296 src := sourceObj 297 if len(v) > 0 { 298 src = sourceObj.Path(v) 299 if src.Data() == nil { 300 continue 301 } 302 } 303 srcData, _ := message.CopyJSON(src.Data()) 304 if len(k) > 0 { 305 destObj.SetP(srcData, k) 306 } else { 307 destObj = gabs.Wrap(srcData) 308 } 309 } 310 311 if err = msg.Get(i).SetJSON(destObj.Data()); err != nil { 312 t.mReqErr.Incr(1) 313 t.mReqErrJSON.Incr(1) 314 t.log.Errorf("Failed to marshal request map result in message part '%v'. Map contents: '%v'\n", i, destObj.String()) 315 failed = append(failed, i) 316 } else { 317 mappedParts = append(mappedParts, msg.Get(i)) 318 } 319 } 320 321 msg.SetAll(mappedParts) 322 return 323 } 324 325 // AlignResult takes the original length of a mapped payload, a slice of skipped 326 // message part indexes, a slice of failed message part indexes, and a 327 // post-mapped, post-processed slice of resulting messages, and attempts to 328 // create a new payload where the results are realigned and ready to map back 329 // into the original. 330 func (t *Type) AlignResult(length int, skipped, failed []int, result []types.Message) (types.Message, error) { 331 resMsgParts := []types.Part{} 332 for _, m := range result { 333 m.Iter(func(i int, p types.Part) error { 334 resMsgParts = append(resMsgParts, p) 335 return nil 336 }) 337 } 338 339 skippedOrFailed := make([]int, len(skipped)+len(failed)) 340 i := copy(skippedOrFailed, skipped) 341 copy(skippedOrFailed[i:], failed) 342 343 sort.Ints(skippedOrFailed) 344 345 // Check that size of response is aligned with payload. 346 if rLen, pLen := len(resMsgParts)+len(skippedOrFailed), length; rLen != pLen { 347 return nil, fmt.Errorf("parts returned from enrichment do not match payload: %v != %v", rLen, pLen) 348 } 349 350 var responseParts []types.Part 351 if len(skippedOrFailed) == 0 { 352 responseParts = resMsgParts 353 } else { 354 // Remember to insert nil for each skipped part at the correct index. 355 responseParts = make([]types.Part, length) 356 sIndex := 0 357 for i = 0; i < len(resMsgParts); i++ { 358 for sIndex < len(skippedOrFailed) && skippedOrFailed[sIndex] == (i+sIndex) { 359 sIndex++ 360 } 361 responseParts[i+sIndex] = resMsgParts[i] 362 } 363 } 364 365 newMsg := message.New(nil) 366 newMsg.SetAll(responseParts) 367 return newMsg, nil 368 } 369 370 // MapResponses attempts to merge a batch of responses with original payloads as 371 // per the response map. 372 // 373 // The count of parts within the response message must match the original 374 // payload. If parts were removed from the enrichment request the original 375 // contents must be interlaced back within the response object before calling 376 // the overlay. 377 // 378 // Returns an array of message indexes that failed their map stage, or an error. 379 func (t *Type) MapResponses(payload, response types.Message) ([]int, error) { 380 if exp, act := payload.Len(), response.Len(); exp != act { 381 t.mResErr.Incr(1) 382 t.mResErrParts.Incr(1) 383 return nil, fmt.Errorf("payload message counts have diverged from the request and response: %v != %v", act, exp) 384 } 385 386 var failed []int 387 388 parts := make([]types.Part, payload.Len()) 389 payload.Iter(func(i int, p types.Part) error { 390 parts[i] = p 391 return nil 392 }) 393 394 partLoop: 395 for i := 0; i < response.Len(); i++ { 396 if response.Get(i).IsEmpty() { 397 // Parts that are nil are skipped. 398 continue partLoop 399 } 400 401 if len(t.resMap) == 0 && len(t.resOptMap) == 0 { 402 newPart := message.MetaPartCopy(parts[i]) 403 newPart.Set(response.Get(i).Get()) 404 405 // Overwrite payload parts with new parts metadata. 406 metadata := newPart.Metadata() 407 response.Get(i).Metadata().Iter(func(k, v string) error { 408 metadata.Set(k, v) 409 return nil 410 }) 411 412 parts[i] = newPart 413 continue partLoop 414 } 415 416 sourceObj, err := getGabs(response, i) 417 if err != nil { 418 t.mResErr.Incr(1) 419 t.mResErrJSON.Incr(1) 420 t.log.Debugf("Failed to parse response part '%v': %v. Failed part: '%s'\n", i, err, response.Get(i).Get()) 421 422 // Skip parts that fail JSON parse. 423 failed = append(failed, i) 424 continue partLoop 425 } 426 427 // Check all mandatory map targets before proceeding. 428 for _, k := range t.resTargets { 429 v := t.resMap[k] 430 if v == "" || sourceObj.Path(v).Data() != nil { 431 continue 432 } 433 434 t.mResErr.Incr(1) 435 t.mResErrMap.Incr(1) 436 t.log.Debugf("Failed to find map target '%v' in response part '%v'.\n", v, i) 437 438 // Skip parts that fail mapping. 439 failed = append(failed, i) 440 continue partLoop 441 } 442 443 var destObj *gabs.Container 444 if destObj, err = getGabs(payload, i); err != nil { 445 t.mResErr.Incr(1) 446 t.mResErrJSON.Incr(1) 447 t.log.Debugf("Failed to parse payload part '%v': %v. Failed part: '%s'\n", i, err, response.Get(i).Get()) 448 449 // Skip parts that fail JSON parse. 450 failed = append(failed, i) 451 continue partLoop 452 } 453 454 for _, k := range t.resTargets { 455 v := t.resMap[k] 456 src := sourceObj 457 if len(v) > 0 { 458 src = sourceObj.Path(v) 459 } 460 if len(k) > 0 { 461 destObj.SetP(src.Data(), k) 462 } else { 463 destObj = src 464 } 465 } 466 for _, k := range t.resOptTargets { 467 v := t.resOptMap[k] 468 src := sourceObj 469 if len(v) > 0 { 470 src = sourceObj.Path(v) 471 if src.Data() == nil { 472 continue 473 } 474 } 475 if len(k) > 0 { 476 destObj.SetP(src.Data(), k) 477 } else { 478 destObj = src 479 } 480 } 481 482 if err = parts[i].SetJSON(destObj.Data()); err != nil { 483 t.mResErr.Incr(1) 484 t.mResErrJSON.Incr(1) 485 t.log.Debugf("Failed to marshal response map result in message part '%v'. Map contents: '%v'\n", i, destObj.String()) 486 487 // Skip parts that fail mapping. 488 failed = append(failed, i) 489 continue partLoop 490 } 491 492 metadata := parts[i].Metadata() 493 response.Get(i).Metadata().Iter(func(k, v string) error { 494 metadata.Set(k, v) 495 return nil 496 }) 497 } 498 499 payload.SetAll(parts) 500 return failed, nil 501 } 502 503 //------------------------------------------------------------------------------