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  //------------------------------------------------------------------------------