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

     1  package processor
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/Jeffail/benthos/v3/internal/bloblang/field"
    10  	"github.com/Jeffail/benthos/v3/internal/docs"
    11  	"github.com/Jeffail/benthos/v3/internal/interop"
    12  	"github.com/Jeffail/benthos/v3/internal/tracing"
    13  	"github.com/Jeffail/benthos/v3/lib/log"
    14  	"github.com/Jeffail/benthos/v3/lib/metrics"
    15  	"github.com/Jeffail/benthos/v3/lib/types"
    16  )
    17  
    18  //------------------------------------------------------------------------------
    19  
    20  func init() {
    21  	Constructors[TypeNumber] = TypeSpec{
    22  		constructor: NewNumber,
    23  		Status:      docs.StatusDeprecated,
    24  		Footnotes: `
    25  ## Alternatives
    26  
    27  All functionality of this processor has been superseded by the
    28  [bloblang](/docs/components/processors/bloblang) processor.`,
    29  		FieldSpecs: docs.FieldSpecs{
    30  			docs.FieldCommon("operator", "The [operator](#operators) to apply."),
    31  			docs.FieldCommon("value", "A value used by the operator.").IsInterpolated(),
    32  			PartsFieldSpec,
    33  		},
    34  	}
    35  }
    36  
    37  //------------------------------------------------------------------------------
    38  
    39  // NumberConfig contains configuration fields for the Number processor.
    40  type NumberConfig struct {
    41  	Parts    []int       `json:"parts" yaml:"parts"`
    42  	Operator string      `json:"operator" yaml:"operator"`
    43  	Value    interface{} `json:"value" yaml:"value"`
    44  }
    45  
    46  // NewNumberConfig returns a NumberConfig with default values.
    47  func NewNumberConfig() NumberConfig {
    48  	return NumberConfig{
    49  		Parts:    []int{},
    50  		Operator: "add",
    51  		Value:    0,
    52  	}
    53  }
    54  
    55  //------------------------------------------------------------------------------
    56  
    57  type numberOperator func(content, value float64) float64
    58  
    59  func newNumberAddOperator() numberOperator {
    60  	return func(content, value float64) float64 {
    61  		return content + value
    62  	}
    63  }
    64  
    65  func newNumberSubtractOperator() numberOperator {
    66  	return func(content, value float64) float64 {
    67  		return content - value
    68  	}
    69  }
    70  
    71  func getNumberOperator(opStr string) (numberOperator, error) {
    72  	switch opStr {
    73  	case "add":
    74  		return newNumberAddOperator(), nil
    75  	case "subtract":
    76  		return newNumberSubtractOperator(), nil
    77  	}
    78  	return nil, fmt.Errorf("operator not recognised: %v", opStr)
    79  }
    80  
    81  //------------------------------------------------------------------------------
    82  
    83  // Number is a processor that performs number based operations on payloads.
    84  type Number struct {
    85  	parts []int
    86  
    87  	interpolatedValue *field.Expression
    88  	value             float64
    89  	operator          numberOperator
    90  
    91  	conf  Config
    92  	log   log.Modular
    93  	stats metrics.Type
    94  
    95  	mCount     metrics.StatCounter
    96  	mErr       metrics.StatCounter
    97  	mSent      metrics.StatCounter
    98  	mBatchSent metrics.StatCounter
    99  }
   100  
   101  // NewNumber returns a Number processor.
   102  func NewNumber(
   103  	conf Config, mgr types.Manager, log log.Modular, stats metrics.Type,
   104  ) (Type, error) {
   105  	n := &Number{
   106  		parts: conf.Number.Parts,
   107  		conf:  conf,
   108  		log:   log,
   109  		stats: stats,
   110  
   111  		mCount:     stats.GetCounter("count"),
   112  		mErr:       stats.GetCounter("error"),
   113  		mSent:      stats.GetCounter("sent"),
   114  		mBatchSent: stats.GetCounter("batch.sent"),
   115  	}
   116  
   117  	var err error
   118  	switch t := conf.Number.Value.(type) {
   119  	case string:
   120  		n.interpolatedValue, err = interop.NewBloblangField(mgr, t)
   121  	case float64:
   122  		n.value = t
   123  	case int:
   124  		n.value = float64(t)
   125  	case json.Number:
   126  		n.value, err = t.Float64()
   127  	default:
   128  		err = fmt.Errorf("value type '%T' not allowed", t)
   129  	}
   130  	if err != nil {
   131  		return nil, fmt.Errorf("failed to parse value: %v", err)
   132  	}
   133  
   134  	if n.operator, err = getNumberOperator(conf.Number.Operator); err != nil {
   135  		return nil, err
   136  	}
   137  	return n, nil
   138  }
   139  
   140  //------------------------------------------------------------------------------
   141  
   142  // ProcessMessage applies the processor to a message, either creating >0
   143  // resulting messages or a response to be sent back to the message source.
   144  func (n *Number) ProcessMessage(msg types.Message) ([]types.Message, types.Response) {
   145  	n.mCount.Incr(1)
   146  	newMsg := msg.Copy()
   147  
   148  	proc := func(index int, span *tracing.Span, part types.Part) error {
   149  		value := n.value
   150  		if n.interpolatedValue != nil {
   151  			interpStr := n.interpolatedValue.String(index, msg)
   152  			var err error
   153  			if value, err = strconv.ParseFloat(interpStr, 64); err != nil {
   154  				n.mErr.Incr(1)
   155  				n.log.Debugf("Failed to parse interpolated value into float: %v\n", err)
   156  				return fmt.Errorf("failed to parse interpolated value '%v' into float: %v", interpStr, err)
   157  			}
   158  		}
   159  
   160  		data, err := strconv.ParseFloat(string(part.Get()), 64)
   161  		if err != nil {
   162  			n.mErr.Incr(1)
   163  			n.log.Debugf("Failed to parse content into float: %v\n", err)
   164  			return err
   165  		}
   166  		data = n.operator(data, value)
   167  		part.Set([]byte(strconv.FormatFloat(data, 'f', -1, 64)))
   168  		return nil
   169  	}
   170  
   171  	IteratePartsWithSpanV2(TypeNumber, n.parts, newMsg, proc)
   172  
   173  	n.mBatchSent.Incr(1)
   174  	n.mSent.Incr(int64(newMsg.Len()))
   175  	return []types.Message{newMsg}, nil
   176  }
   177  
   178  // CloseAsync shuts down the processor and stops processing requests.
   179  func (n *Number) CloseAsync() {
   180  }
   181  
   182  // WaitForClose blocks until the processor has closed down.
   183  func (n *Number) WaitForClose(timeout time.Duration) error {
   184  	return nil
   185  }
   186  
   187  //------------------------------------------------------------------------------