Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new wrp validator, add the ability to validate whether wrp messages' source/dest are not empty #208

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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