File size: 4,847 Bytes
d6f631f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | package meter
import (
"encoding/json"
"errors"
"fmt"
"math"
"strconv"
"github.com/oliveagle/jsonpath"
"github.com/samber/lo"
"github.com/openmeterio/openmeter/pkg/models"
)
var _ models.GenericError = (*ErrInvalidEvent)(nil)
type ErrInvalidEvent struct {
err error
}
func (e ErrInvalidEvent) Error() string {
s := "invalid event"
if e.err != nil {
return s + ": " + e.err.Error()
}
return s
}
func (e ErrInvalidEvent) Unwrap() error {
return e.err
}
func NewErrInvalidEvent(err error) error {
return ErrInvalidEvent{err: err}
}
var _ models.GenericError = (*ErrInvalidMeter)(nil)
type ErrInvalidMeter struct {
err error
}
func (e ErrInvalidMeter) Error() string {
s := "invalid meter"
if e.err != nil {
return s + ": " + e.err.Error()
}
return s
}
func (e ErrInvalidMeter) Unwrap() error {
return e.err
}
func NewErrInvalidMeter(err error) error {
return &ErrInvalidMeter{err: err}
}
type ParsedEvent struct {
Value *float64
ValueString *string
GroupBy map[string]string
}
func ParseEventString(meter Meter, data string) (*ParsedEvent, error) {
return ParseEvent(meter, []byte(data))
}
// ParseEvent validates and parses an event against a meter.
func ParseEvent(meter Meter, data []byte) (*ParsedEvent, error) {
// Parse CloudEvents data
var (
event interface{}
err error
)
parsedEvent := &ParsedEvent{
GroupBy: map[string]string{},
}
if len(data) > 0 {
err = json.Unmarshal(data, &event)
if err != nil {
return parsedEvent, NewErrInvalidEvent(fmt.Errorf("failed to parse event data: %w", err))
}
}
// Parse group by fields
parsedEvent.GroupBy = parseGroupBy(meter, event)
// We can skip count events as they don't have value property
if meter.Aggregation == MeterAggregationCount {
parsedEvent.Value = lo.ToPtr(1.0)
return parsedEvent, nil
}
// Non count events require value property to be present
// If the event data is null, we return an error as value property is missing
if event == nil {
return parsedEvent, NewErrInvalidEvent(errors.New("null and missing value property"))
}
if meter.ValueProperty == nil {
return parsedEvent, NewErrInvalidEvent(errors.New("non count meter value property is missing"))
}
// Get value from event data by value property
var rawValue interface{}
rawValue, err = jsonpath.JsonPathLookup(event, *meter.ValueProperty)
if err != nil {
return parsedEvent, NewErrInvalidEvent(fmt.Errorf("missing value property: %q", *meter.ValueProperty))
}
if rawValue == nil {
return parsedEvent, NewErrInvalidEvent(errors.New("value cannot be null"))
}
// Aggregation specific value validation
switch meter.Aggregation {
// UNIQUE_COUNT aggregation requires string property value
case MeterAggregationUniqueCount:
// We convert the value to string
parsedEvent.ValueString = lo.ToPtr(fmt.Sprintf("%v", rawValue))
return parsedEvent, nil
// SUM, AVG, MIN, MAX, LATEST aggregations require float64 parsable value property value
case MeterAggregationSum, MeterAggregationAvg, MeterAggregationMin, MeterAggregationMax, MeterAggregationLatest:
switch v := rawValue.(type) {
case string:
parsedValue, err := strconv.ParseFloat(v, 64)
if err != nil {
// TODO: omit value or make sure it's length is not too long
return parsedEvent, NewErrInvalidEvent(fmt.Errorf("value cannot be parsed as float64: %s", v))
}
if err := validateFloat64(parsedValue); err != nil {
return parsedEvent, NewErrInvalidEvent(err)
}
parsedEvent.Value = lo.ToPtr(parsedValue)
return parsedEvent, nil
case float64:
if err := validateFloat64(v); err != nil {
return parsedEvent, NewErrInvalidEvent(err)
}
parsedEvent.Value = lo.ToPtr(v)
return parsedEvent, nil
default:
return parsedEvent, NewErrInvalidEvent(fmt.Errorf("unsupported value property type: %T", v))
}
}
return parsedEvent, NewErrInvalidMeter(fmt.Errorf("unknown meter aggregation: %s", meter.Aggregation))
}
// valiodateFloat64 validates a float64 value
func validateFloat64(v float64) error {
if math.IsNaN(v) {
return errors.New("value cannot be NaN")
}
if math.IsInf(v, 0) {
return errors.New("value cannot be infinity")
}
return nil
}
// parseGroupBy parses the group by fields from the event data
// we allow the group by fields to be missing in the event data or the data to be null
// in such cases we set the group by value to empty string
func parseGroupBy(meter Meter, data interface{}) map[string]string {
groupBy := map[string]string{}
// Group by fields
for groupByKey, groupByPath := range meter.GroupBy {
var groupByValue string
rawGroupBy, err := jsonpath.JsonPathLookup(data, groupByPath)
if err != nil {
groupByValue = ""
} else {
groupByValue = fmt.Sprintf("%v", rawGroupBy)
}
groupBy[groupByKey] = groupByValue
}
return groupBy
}
|