Skip to content

Commit

Permalink
feat: new wrp validator, add the ability to validate whether wrp mess…
Browse files Browse the repository at this point in the history
…ages' source/dest are not empty

- new wrp validators: none_empty_destination & none_empty_source
  - validates whether wrp messages' source/dest are not empty
  - includes metrics for the new validator

other things included:
- fix doc typos
- improve metric help docs
  • Loading branch information
denopink committed Oct 10, 2024
1 parent c5b5eb1 commit ad37074
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 21 deletions.
10 changes: 10 additions & 0 deletions wrpvalidator/metaValidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,16 @@ func (v *MetaValidator) UnmarshalJSON(b []byte) error {
val = SimpleEventType
case SpansType:
val = Spans
case NoneEmptySourceType:
val = NoneEmptySource
case NoneEmptyDestinationType:
val = NoneEmptyDestination
default:
return fmt.Errorf("validator `%s`: wrp validator selection: %w: %s", v.meta.Type, ErrValidatorUnmarshalling, errValidatorTypeInvalid)
}

// By default validators will have no metrics.
// Metrics are optional and can be added with `MetaValidator.AddMetric()`.
v.validator = NewValidatorWithoutMetric(val)

if !v.IsValid() {
Expand Down Expand Up @@ -128,6 +134,10 @@ func (v *MetaValidator) AddMetric(tf *touchstone.Factory, labelNames ...string)
val, err = NewSimpleEventTypeWithMetric(tf, labelNames...)
case SpansType:
val, err = NewSpansWithMetric(tf, labelNames...)
case NoneEmptySourceType:
val, err = NewNoneEmptySourceWithMetric(tf, labelNames...)
case NoneEmptyDestinationType:
val, err = NewNoneEmptyDestinationWithMetric(tf, labelNames...)
// no default is needed since v.IsValid() takes care of this case
}

Expand Down
18 changes: 18 additions & 0 deletions wrpvalidator/metaValidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,24 @@ func TestMetaValidatorAddMetric(t *testing.T) {
}
]`),
},
{
description: "Add metric validator NoneEmptySource",
config: []byte(`[
{
"type": "none_empty_source",
"level": "warning"
}
]`),
},
{
description: "Add metric validator NoneEmptyDestinationType",
config: []byte(`[
{
"type": "none_empty_destination",
"level": "warning"
}
]`),
},
{
description: "Add metric validator always_invalid",
config: []byte(`[
Expand Down
62 changes: 47 additions & 15 deletions wrpvalidator/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,65 @@ const (
// MetricPrefix is prepended to all metrics exposed by this package.
metricPrefix = "wrp_validator_"

// alwaysInvalidValidatorErrorTotalName is the name of the counter for all AlwaysInvalid validation.
// alwaysInvalidValidatorErrorTotalName is the name of the counter for all AlwaysInvalid validation errors.
alwaysInvalidValidatorErrorTotalName = metricPrefix + "always_invalid"

// alwaysInvalidValidatorErrorTotalHelp is the help text for the AlwaysInvalid metric.
alwaysInvalidValidatorErrorTotalHelp = "the total number of AlwaysInvalid validations"

// destinationValidatorErrorTotalName is the name of the counter for all destination validation.
// destinationValidatorErrorTotalName is the name of the counter for all destination validation errors.
destinationValidatorErrorTotalName = metricPrefix + "destination"

// destinationValidatorErrorTotalHelp is the help text for the Destination metric.
destinationValidatorErrorTotalHelp = "the total number of Destination metric"
destinationValidatorErrorTotalHelp = "the total number of Destination validation errors"

// sourceValidatorErrorTotalName is the name of the counter for all source validation.
// sourceValidatorErrorTotalName is the name of the counter for all source validation errors.
sourceValidatorErrorTotalName = metricPrefix + "source"

// sourceValidatorErrorTotalHelp is the help text for the Source metric.
sourceValidatorErrorTotalHelp = "the total number of Source metric"
sourceValidatorErrorTotalHelp = "the total number of Source validation errors"

// messageTypeValidatorErrorTotalName is the name of the counter for all MessageType validation.
// messageTypeValidatorErrorTotalName is the name of the counter for all MessageType validation errors.
messageTypeValidatorErrorTotalName = metricPrefix + "message_type"

// messageTypeValidatorErrorTotalHelp is the help text for the MessageType metric.
messageTypeValidatorErrorTotalHelp = "the total number of MessageType metric"
messageTypeValidatorErrorTotalHelp = "the total number of MessageType validation errors"

// utf8ValidatorErrorTotalName is the name of the counter for all UTF8 validation.
// utf8ValidatorErrorTotalName is the name of the counter for all UTF8 validation errors.
utf8ValidatorErrorTotalName = metricPrefix + "utf8"

// utf8ValidatorErrorTotalHelp is the help text for the UTF8 Validator metric.
utf8ValidatorErrorTotalHelp = "the total number of UTF8 Validator metric"
utf8ValidatorErrorTotalHelp = "the total number of UTF8 validation errors"

// simpleEventTypeValidatorErrorTotalName is the name of the counter for all SimpleEventType validation.
// simpleEventTypeValidatorErrorTotalName is the name of the counter for all SimpleEventType validation errors.
simpleEventTypeValidatorErrorTotalName = metricPrefix + "simple_event_type"

// simpleEventTypeValidatorErrorTotalHelp is the help text for the SimpleEventType Validator metric.
simpleEventTypeValidatorErrorTotalHelp = "the total number of SimpleEventType Validator metric"
simpleEventTypeValidatorErrorTotalHelp = "the total number of SimpleEventType validation errors"

// simpleRequestResponseMessageTypeErrorTotalName is the name of the counter for all SimpleRequestResponseMessageType validation.
// simpleRequestResponseMessageTypeErrorTotalName is the name of the counter for all SimpleRequestResponseMessageType validation errors.
simpleRequestResponseMessageTypeErrorTotalName = metricPrefix + "simple_request_response_message_type"

// simpleRequestResponseMessageTypeErrorTotalHelp is the help text for the SimpleRequestResponseMessageType Validator metric.
simpleRequestResponseMessageTypeErrorTotalHelp = "the total number of SimpleRequestResponseMessageType Validator metric"
simpleRequestResponseMessageTypeErrorTotalHelp = "the total number of SimpleRequestResponseMessageType validation errors"

// spansValidatorErrorTotalName is the name of the counter for all Spans validation.
// spansValidatorErrorTotalName is the name of the counter for all Spans validation errors.
spansValidatorErrorTotalName = metricPrefix + "spans"

// spansValidatorErrorTotalHelp is the help text for the Spans Validator metric.
spansValidatorErrorTotalHelp = "the total number of Spans Validator metric"
spansValidatorErrorTotalHelp = "the total number of Spans validation errors"

// noneEmptySourceErrorTotalName is the name of the counter for all noneEmptySource validation errors.
noneEmptySourceErrorTotalName = metricPrefix + "none_empty_source"

// noneEmptySourceErrorTotalHelp is the help text for the noneEmptySource Validator metric.
noneEmptySourceErrorTotalHelp = "the total number of None Empty Source validation errors"

// noneEmptyDestinationErrorTotalName is the name of the counter for all noneEmptyDestination validation errors.
noneEmptyDestinationErrorTotalName = metricPrefix + "none_empty_destination"

// noneEmptyDestinationErrorTotalHelp is the help text for the noneEmptyDestination Validator metric.
noneEmptyDestinationErrorTotalHelp = "the total number of None Empty Destination validation errors"
)

// Metric label names
Expand Down Expand Up @@ -147,3 +159,23 @@ func newSpansErrorTotal(tf *touchstone.Factory, labelNames ...string) (m *promet
labelNames...,
)
}

func newNoneEmptySourceErrorTotal(tf *touchstone.Factory, labelNames ...string) (m *prometheus.CounterVec, err error) {
return tf.NewCounterVec(
prometheus.CounterOpts{
Name: noneEmptySourceErrorTotalName,
Help: noneEmptySourceErrorTotalHelp,
},
labelNames...,
)
}

func newNoneEmptyDestinationErrorTotal(tf *touchstone.Factory, labelNames ...string) (m *prometheus.CounterVec, err error) {
return tf.NewCounterVec(
prometheus.CounterOpts{
Name: noneEmptyDestinationErrorTotalName,
Help: noneEmptyDestinationErrorTotalHelp,
},
labelNames...,
)
}
71 changes: 65 additions & 6 deletions wrpvalidator/specValidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ const (
)

var (
ErrorInvalidMessageEncoding = NewValidatorError(errors.New("invalid message encoding"), "", nil)
ErrorInvalidMessageType = NewValidatorError(errors.New("invalid message type"), "", []string{"Type"})
ErrorInvalidSource = NewValidatorError(errors.New("invalid Source name"), "", []string{"Source"})
ErrorInvalidDestination = NewValidatorError(errors.New("invalid Destination name"), "", []string{"Destination"})
errorInvalidUUID = errors.New("invalid UUID")
ErrorInvalidMessageEncoding = NewValidatorError(errors.New("invalid message encoding"), "", nil)
ErrorInvalidMessageType = NewValidatorError(errors.New("invalid message type"), "", []string{"Type"})
ErrorInvalidSource = NewValidatorError(errors.New("invalid Source name"), "", []string{"Source"})
ErrorInvalidDestination = NewValidatorError(errors.New("invalid Destination name"), "", []string{"Destination"})
ErrorInvalidEmptySource = NewValidatorError(errors.New("empty Source"), "", []string{"Source"})
ErrorInvalidEmptyDestination = NewValidatorError(errors.New("empty Destination"), "", []string{"Destination"})

errorInvalidUUID = errors.New("invalid UUID")
)

// SpecWithMetrics ensures messages are valid based on each spec validator in the list.
Expand Down Expand Up @@ -51,7 +54,17 @@ func SpecWithMetrics(tf *touchstone.Factory, labelNames ...string) (Validators,
errs = multierr.Append(errs, err)
}

return Validators{}.AddFunc(utf8v, mtv, sv, dv), errs
nesv, err := NewNoneEmptySourceWithMetric(tf, labelNames...)
if err != nil {
errs = multierr.Append(errs, err)
}

nedv, err := NewNoneEmptyDestinationWithMetric(tf, labelNames...)
if err != nil {
errs = multierr.Append(errs, err)
}

return Validators{}.AddFunc(utf8v, mtv, sv, dv, nesv, nedv), errs
}

// NewUTF8WithMetric returns a UTF8 validator with a metric middleware.
Expand Down Expand Up @@ -110,6 +123,34 @@ func NewDestinationWithMetric(tf *touchstone.Factory, labelNames ...string) (Val
}, err
}

// NewNoneEmptySourceWithMetric returns a NoneEmptySource validator with a metric middleware.
func NewNoneEmptySourceWithMetric(tf *touchstone.Factory, labelNames ...string) (ValidatorFunc, error) {
m, err := newNoneEmptySourceErrorTotal(tf, labelNames...)

return func(msg wrp.Message, ls prometheus.Labels) error {
err := NoneEmptySource(msg)
if err != nil {
m.With(ls).Add(1.0)
}

return err
}, err
}

// NewNoneEmptyDestinationWithMetric returns a NoneEmptyDestination validator with a metric middleware.
func NewNoneEmptyDestinationWithMetric(tf *touchstone.Factory, labelNames ...string) (ValidatorFunc, error) {
m, err := newNoneEmptyDestinationErrorTotal(tf, labelNames...)

return func(msg wrp.Message, ls prometheus.Labels) error {
err := NoneEmptyDestination(msg)
if err != nil {
m.With(ls).Add(1.0)
}

return err
}, err
}

// UTF8 takes messages and validates that it contains UTF-8 strings.
func UTF8(m wrp.Message) error {
if err := wrp.UTF8(m); err != nil {
Expand Down Expand Up @@ -150,6 +191,24 @@ func Destination(m wrp.Message) error {
return nil
}

// NoneEmptySource takes messages and validates whether their Source is not empty.
func NoneEmptySource(m wrp.Message) error {
if m.Source == "" {
return ErrorInvalidEmptySource
}

return nil
}

// NoneEmptyDestination takes messages and validates whether their Destination is not empty.
func NoneEmptyDestination(m wrp.Message) error {
if m.Destination == "" {
return ErrorInvalidEmptyDestination
}

return nil
}

// validateLocator validates a given locator's scheme and authority (ID).
// Only mac and uuid schemes' IDs are validated. IDs from serial, event and dns schemes are
// not validated.
Expand Down
92 changes: 92 additions & 0 deletions wrpvalidator/specValidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func TestSpecHelperValidators(t *testing.T) {
{"MessageType", testMessageType},
{"Source", testSource},
{"Destination", testDestination},
{"NoneEmptySource", testNoneEmptySource},
{"NoneEmptyDestination", testNoneEmptyDestination},
{"validateLocator", testValidateLocator},
}

Expand Down Expand Up @@ -225,6 +227,24 @@ func TestSpecWithDuplicateValidators(t *testing.T) {
require.NoError(err)
_, err = SpecWithMetrics(f4)
require.Error(err)

_, pr5, err := touchstone.New(cfg)
require.NoError(err)

f5 := touchstone.NewFactory(cfg, sallust.Default(), pr5)
_, err = NewNoneEmptySourceWithMetric(f5)
require.NoError(err)
_, err = SpecWithMetrics(f5)
require.Error(err)

_, pr6, err := touchstone.New(cfg)
require.NoError(err)

f6 := touchstone.NewFactory(cfg, sallust.Default(), pr6)
_, err = NewNoneEmptyDestinationWithMetric(f6)
require.NoError(err)
_, err = SpecWithMetrics(f6)
require.Error(err)
})
}
}
Expand Down Expand Up @@ -579,6 +599,78 @@ func testDestination(t *testing.T) {
}
}

func testNoneEmptySource(t *testing.T) {
tests := []struct {
description string
msg wrp.Message
expectedErr error
}{
// Success case
{
description: "Source success",
msg: wrp.Message{Source: "MAC:11:22:33:44:55:66"},
},
// Failures
{
description: "NoneEmptySource error",
msg: wrp.Message{Source: ""},
expectedErr: ErrorInvalidEmptySource,
},
}

for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
err := NoneEmptySource(tc.msg)
if expectedErr := tc.expectedErr; expectedErr != nil {
var targetErr ValidatorError

assert.ErrorAs(expectedErr, &targetErr)
assert.ErrorIs(err, targetErr.Err)
return
}

assert.NoError(err)
})
}
}

func testNoneEmptyDestination(t *testing.T) {
tests := []struct {
description string
msg wrp.Message
expectedErr error
}{
// Success case
{
description: "NoneEmptyDestination success",
msg: wrp.Message{Destination: "MAC:11:22:33:44:55:66"},
},
// Failures
{
description: "NoneEmptyDestination error",
msg: wrp.Message{Destination: ""},
expectedErr: ErrorInvalidEmptyDestination,
},
}

for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
err := NoneEmptyDestination(tc.msg)
if expectedErr := tc.expectedErr; expectedErr != nil {
var targetErr ValidatorError

assert.ErrorAs(expectedErr, &targetErr)
assert.ErrorIs(err, targetErr.Err)
return
}

assert.NoError(err)
})
}
}

func testValidateLocator(t *testing.T) {
tests := []struct {
description string
Expand Down
Loading

0 comments on commit ad37074

Please sign in to comment.