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 }