github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/insert_part.go (about)

     1  package processor
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/bloblang/field"
     8  	"github.com/Jeffail/benthos/v3/internal/docs"
     9  	"github.com/Jeffail/benthos/v3/internal/interop"
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/message"
    12  	"github.com/Jeffail/benthos/v3/lib/metrics"
    13  	"github.com/Jeffail/benthos/v3/lib/types"
    14  )
    15  
    16  //------------------------------------------------------------------------------
    17  
    18  func init() {
    19  	Constructors[TypeInsertPart] = TypeSpec{
    20  		constructor: NewInsertPart,
    21  		Categories: []Category{
    22  			CategoryComposition,
    23  		},
    24  		Summary: `
    25  Insert a new message into a batch at an index. If the specified index is greater
    26  than the length of the existing batch it will be appended to the end.`,
    27  		Description: `
    28  The index can be negative, and if so the message will be inserted from the end
    29  counting backwards starting from -1. E.g. if index = -1 then the new message
    30  will become the last of the batch, if index = -2 then the new message will be
    31  inserted before the last message, and so on. If the negative index is greater
    32  than the length of the existing batch it will be inserted at the beginning.
    33  
    34  The new message will have metadata copied from the first pre-existing message of
    35  the batch.
    36  
    37  This processor will interpolate functions within the 'content' field, you can
    38  find a list of functions [here](/docs/configuration/interpolation#bloblang-queries).`,
    39  		FieldSpecs: docs.FieldSpecs{
    40  			docs.FieldCommon("index", "The index within the batch to insert the message at."),
    41  			docs.FieldCommon("content", "The content of the message being inserted.").IsInterpolated(),
    42  		},
    43  	}
    44  }
    45  
    46  //------------------------------------------------------------------------------
    47  
    48  // InsertPartConfig contains configuration fields for the InsertPart processor.
    49  type InsertPartConfig struct {
    50  	Index   int    `json:"index" yaml:"index"`
    51  	Content string `json:"content" yaml:"content"`
    52  }
    53  
    54  // NewInsertPartConfig returns a InsertPartConfig with default values.
    55  func NewInsertPartConfig() InsertPartConfig {
    56  	return InsertPartConfig{
    57  		Index:   -1,
    58  		Content: "",
    59  	}
    60  }
    61  
    62  //------------------------------------------------------------------------------
    63  
    64  // InsertPart is a processor that inserts a new message part at a specific
    65  // index.
    66  type InsertPart struct {
    67  	part *field.Expression
    68  
    69  	conf  Config
    70  	log   log.Modular
    71  	stats metrics.Type
    72  
    73  	mCount     metrics.StatCounter
    74  	mSent      metrics.StatCounter
    75  	mBatchSent metrics.StatCounter
    76  }
    77  
    78  // NewInsertPart returns a InsertPart processor.
    79  func NewInsertPart(
    80  	conf Config, mgr types.Manager, log log.Modular, stats metrics.Type,
    81  ) (Type, error) {
    82  	part, err := interop.NewBloblangField(mgr, conf.InsertPart.Content)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("failed to parse content expression: %v", err)
    85  	}
    86  	return &InsertPart{
    87  		part: part,
    88  
    89  		conf:  conf,
    90  		log:   log,
    91  		stats: stats,
    92  
    93  		mCount:     stats.GetCounter("count"),
    94  		mSent:      stats.GetCounter("sent"),
    95  		mBatchSent: stats.GetCounter("batch.sent"),
    96  	}, nil
    97  }
    98  
    99  //------------------------------------------------------------------------------
   100  
   101  // ProcessMessage applies the processor to a message, either creating >0
   102  // resulting messages or a response to be sent back to the message source.
   103  func (p *InsertPart) ProcessMessage(msg types.Message) ([]types.Message, types.Response) {
   104  	p.mCount.Incr(1)
   105  
   106  	newPartBytes := p.part.Bytes(0, msg)
   107  	index := p.conf.InsertPart.Index
   108  	msgLen := msg.Len()
   109  	if index < 0 {
   110  		index = msgLen + index + 1
   111  		if index < 0 {
   112  			index = 0
   113  		}
   114  	} else if index > msgLen {
   115  		index = msgLen
   116  	}
   117  
   118  	newMsg := message.New(nil)
   119  	newPart := msg.Get(0).Copy()
   120  	newPart.Set(newPartBytes)
   121  	msg.Iter(func(i int, p types.Part) error {
   122  		if i == index {
   123  			newMsg.Append(newPart)
   124  		}
   125  		newMsg.Append(p.Copy())
   126  		return nil
   127  	})
   128  	if index == msg.Len() {
   129  		newMsg.Append(newPart)
   130  	}
   131  
   132  	p.mBatchSent.Incr(1)
   133  	p.mSent.Incr(int64(newMsg.Len()))
   134  	msgs := [1]types.Message{newMsg}
   135  	return msgs[:], nil
   136  }
   137  
   138  // CloseAsync shuts down the processor and stops processing requests.
   139  func (p *InsertPart) CloseAsync() {
   140  }
   141  
   142  // WaitForClose blocks until the processor has closed down.
   143  func (p *InsertPart) WaitForClose(timeout time.Duration) error {
   144  	return nil
   145  }
   146  
   147  //------------------------------------------------------------------------------