github.com/Jeffail/benthos/v3@v3.65.0/public/service/message.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/Jeffail/benthos/v3/internal/bloblang/mapping"
     8  	"github.com/Jeffail/benthos/v3/lib/message"
     9  	"github.com/Jeffail/benthos/v3/lib/processor"
    10  	"github.com/Jeffail/benthos/v3/lib/types"
    11  	"github.com/Jeffail/benthos/v3/public/bloblang"
    12  )
    13  
    14  // MessageHandlerFunc is a function signature defining a component that consumes
    15  // Benthos messages. An error must be returned if the context is cancelled, or
    16  // if the message could not be delivered or processed.
    17  type MessageHandlerFunc func(context.Context, *Message) error
    18  
    19  // MessageBatchHandlerFunc is a function signature defining a component that
    20  // consumes Benthos message batches. An error must be returned if the context is
    21  // cancelled, or if the messages could not be delivered or processed.
    22  type MessageBatchHandlerFunc func(context.Context, MessageBatch) error
    23  
    24  // Message represents a single discrete message passing through a Benthos
    25  // pipeline. It is safe to mutate the message via Set methods, but the
    26  // underlying byte data should not be edited directly.
    27  type Message struct {
    28  	part       types.Part
    29  	partCopied bool
    30  }
    31  
    32  // MessageBatch describes a collection of one or more messages.
    33  type MessageBatch []*Message
    34  
    35  // Copy creates a new slice of the same messages, which can be modified without
    36  // changing the contents of the original batch.
    37  func (b MessageBatch) Copy() MessageBatch {
    38  	bCopy := make(MessageBatch, len(b))
    39  	for i, m := range b {
    40  		bCopy[i] = m.Copy()
    41  	}
    42  	return bCopy
    43  }
    44  
    45  // NewMessage creates a new message with an initial raw bytes content. The
    46  // initial content can be nil, which is recommended if you intend to set it with
    47  // structured contents.
    48  func NewMessage(content []byte) *Message {
    49  	return &Message{
    50  		part:       message.NewPart(content),
    51  		partCopied: true,
    52  	}
    53  }
    54  
    55  func newMessageFromPart(part types.Part) *Message {
    56  	return &Message{part, false}
    57  }
    58  
    59  // Copy creates a shallow copy of a message that is safe to mutate with Set
    60  // methods without mutating the original. Both messages will share a context,
    61  // and therefore a tracing ID, if one has been associated with them.
    62  //
    63  // Note that this does not perform a deep copy of the byte or structured
    64  // contents of the message, and therefore it is not safe to perform inline
    65  // mutations on those values without copying them.
    66  func (m *Message) Copy() *Message {
    67  	return &Message{
    68  		part:       m.part.Copy(),
    69  		partCopied: true,
    70  	}
    71  }
    72  
    73  func (m *Message) ensureCopied() {
    74  	if !m.partCopied {
    75  		m.part = m.part.Copy()
    76  		m.partCopied = true
    77  	}
    78  }
    79  
    80  // Context returns a context associated with the message, or a background
    81  // context in the absence of one.
    82  func (m *Message) Context() context.Context {
    83  	return message.GetContext(m.part)
    84  }
    85  
    86  // WithContext returns a new message with a provided context associated with it.
    87  func (m *Message) WithContext(ctx context.Context) *Message {
    88  	return &Message{
    89  		part:       message.WithContext(ctx, m.part),
    90  		partCopied: m.partCopied,
    91  	}
    92  }
    93  
    94  // AsBytes returns the underlying byte array contents of a message or, if the
    95  // contents are a structured type, attempts to marshal the contents as a JSON
    96  // document and returns either the byte array result or an error.
    97  //
    98  // It is NOT safe to mutate the contents of the returned slice.
    99  func (m *Message) AsBytes() ([]byte, error) {
   100  	// TODO: Escalate errors in marshalling once we're able.
   101  	return m.part.Get(), nil
   102  }
   103  
   104  // AsStructured returns the underlying structured contents of a message or, if
   105  // the contents are a byte array, attempts to parse the bytes contents as a JSON
   106  // document and returns either the structured result or an error.
   107  //
   108  // It is NOT safe to mutate the contents of the returned value if it is a
   109  // reference type (slice or map). In order to safely mutate the structured
   110  // contents of a message use AsStructuredMut.
   111  func (m *Message) AsStructured() (interface{}, error) {
   112  	return m.part.JSON()
   113  }
   114  
   115  // AsStructuredMut returns the underlying structured contents of a message or,
   116  // if the contents are a byte array, attempts to parse the bytes contents as a
   117  // JSON document and returns either the structured result or an error.
   118  //
   119  // It is safe to mutate the contents of the returned value even if it is a
   120  // reference type (slice or map), as the structured contents will be lazily deep
   121  // cloned if it is still owned by an upstream component.
   122  func (m *Message) AsStructuredMut() (interface{}, error) {
   123  	// TODO: Use refactored APIs to determine if the contents are owned.
   124  	v, err := m.part.JSON()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return message.CopyJSON(v)
   129  }
   130  
   131  // SetBytes sets the underlying contents of the message as a byte slice.
   132  func (m *Message) SetBytes(b []byte) {
   133  	m.ensureCopied()
   134  	m.part.Set(b)
   135  }
   136  
   137  // SetStructured sets the underlying contents of the message as a structured
   138  // type. This structured value should be a scalar Go type, or either a
   139  // map[string]interface{} or []interface{} containing the same types all the way
   140  // through the hierarchy, this ensures that other processors are able to work
   141  // with the contents and that they can be JSON marshalled when coerced into a
   142  // byte array.
   143  func (m *Message) SetStructured(i interface{}) {
   144  	m.ensureCopied()
   145  	m.part.SetJSON(i)
   146  }
   147  
   148  // SetError marks the message as having failed a processing step and adds the
   149  // error to it as context. Messages marked with errors can be handled using a
   150  // range of methods outlined in https://www.benthos.dev/docs/configuration/error_handling.
   151  func (m *Message) SetError(err error) {
   152  	m.ensureCopied()
   153  	processor.FlagErr(m.part, err)
   154  }
   155  
   156  // GetError returns an error associated with a message, or nil if there isn't
   157  // one. Messages marked with errors can be handled using a range of methods
   158  // outlined in https://www.benthos.dev/docs/configuration/error_handling.
   159  func (m *Message) GetError() error {
   160  	failStr := processor.GetFail(m.part)
   161  	if failStr == "" {
   162  		return nil
   163  	}
   164  	return errors.New(failStr)
   165  }
   166  
   167  // MetaGet attempts to find a metadata key from the message and returns a string
   168  // result and a boolean indicating whether it was found.
   169  func (m *Message) MetaGet(key string) (string, bool) {
   170  	v := m.part.Metadata().Get(key)
   171  	return v, len(v) > 0
   172  }
   173  
   174  // MetaSet sets the value of a metadata key. If the value is an empty string the
   175  // metadata key is deleted.
   176  func (m *Message) MetaSet(key, value string) {
   177  	m.ensureCopied()
   178  	if value == "" {
   179  		m.part.Metadata().Delete(key)
   180  	} else {
   181  		m.part.Metadata().Set(key, value)
   182  	}
   183  }
   184  
   185  // MetaDelete removes a key from the message metadata.
   186  func (m *Message) MetaDelete(key string) {
   187  	m.ensureCopied()
   188  	m.part.Metadata().Delete(key)
   189  }
   190  
   191  // MetaWalk iterates each metadata key/value pair and executes a provided
   192  // closure on each iteration. To stop iterating, return an error from the
   193  // closure. An error returned by the closure will be returned by this function.
   194  func (m *Message) MetaWalk(fn func(string, string) error) error {
   195  	return m.part.Metadata().Iter(fn)
   196  }
   197  
   198  //------------------------------------------------------------------------------
   199  
   200  // BloblangQuery executes a parsed Bloblang mapping on a message and returns a
   201  // message back or an error if the mapping fails. If the mapping results in the
   202  // root being deleted the returned message will be nil, which indicates it has
   203  // been filtered.
   204  func (m *Message) BloblangQuery(blobl *bloblang.Executor) (*Message, error) {
   205  	uw := blobl.XUnwrapper().(interface {
   206  		Unwrap() *mapping.Executor
   207  	}).Unwrap()
   208  
   209  	msg := message.New(nil)
   210  	msg.Append(m.part)
   211  
   212  	res, err := uw.MapPart(0, msg)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	if res != nil {
   217  		return newMessageFromPart(res), nil
   218  	}
   219  	return nil, nil
   220  }
   221  
   222  // BloblangQuery executes a parsed Bloblang mapping on a message batch, from the
   223  // perspective of a particular message index, and returns a message back or an
   224  // error if the mapping fails. If the mapping results in the root being deleted
   225  // the returned message will be nil, which indicates it has been filtered.
   226  //
   227  // This method allows mappings to perform windowed aggregations across message
   228  // batches.
   229  func (b MessageBatch) BloblangQuery(index int, blobl *bloblang.Executor) (*Message, error) {
   230  	uw := blobl.XUnwrapper().(interface {
   231  		Unwrap() *mapping.Executor
   232  	}).Unwrap()
   233  
   234  	msg := message.New(nil)
   235  	for _, m := range b {
   236  		msg.Append(m.part)
   237  	}
   238  
   239  	res, err := uw.MapPart(index, msg)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	if res != nil {
   244  		return newMessageFromPart(res), nil
   245  	}
   246  	return nil, nil
   247  }
   248  
   249  // InterpolatedString resolves an interpolated string expression on a message
   250  // batch, from the perspective of a particular message index.
   251  //
   252  // This method allows interpolation functions to perform windowed aggregations
   253  // across message batches, and is a more powerful way to interpolate strings
   254  // than the standard .String method.
   255  func (b MessageBatch) InterpolatedString(index int, i *InterpolatedString) string {
   256  	msg := message.New(nil)
   257  	for _, m := range b {
   258  		msg.Append(m.part)
   259  	}
   260  	return i.expr.String(index, msg)
   261  }
   262  
   263  // InterpolatedBytes resolves an interpolated string expression on a message
   264  // batch, from the perspective of a particular message index.
   265  //
   266  // This method allows interpolation functions to perform windowed aggregations
   267  // across message batches, and is a more powerful way to interpolate strings
   268  // than the standard .String method.
   269  func (b MessageBatch) InterpolatedBytes(index int, i *InterpolatedString) []byte {
   270  	msg := message.New(nil)
   271  	for _, m := range b {
   272  		msg.Append(m.part)
   273  	}
   274  	return i.expr.Bytes(index, msg)
   275  }