diff --git a/api/eventing/v1alpha2/errors.go b/api/eventing/v1alpha2/errors.go deleted file mode 100644 index 6afbe419d..000000000 --- a/api/eventing/v1alpha2/errors.go +++ /dev/null @@ -1,41 +0,0 @@ -package v1alpha2 - -import ( - "fmt" - "strconv" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/kyma-project/eventing-manager/pkg/ems/api/events/types" -) - -//nolint:gochecknoglobals // these are required for testing -var ( - SourcePath = field.NewPath("spec").Child("source") - TypesPath = field.NewPath("spec").Child("types") - ConfigPath = field.NewPath("spec").Child("config") - SinkPath = field.NewPath("spec").Child("sink") - NSPath = field.NewPath("metadata").Child("namespace") - - EmptyErrDetail = "must not be empty" - InvalidURIErrDetail = "must be valid as per RFC 3986" - DuplicateTypesErrDetail = "must not have duplicate types" - LengthErrDetail = "must not be of length zero" - MinSegmentErrDetail = fmt.Sprintf("must have minimum %s segments", strconv.Itoa(minEventTypeSegments)) - InvalidPrefixErrDetail = fmt.Sprintf("must not have %s as type prefix", InvalidPrefix) - StringIntErrDetail = fmt.Sprintf("%s must be a stringified int value", MaxInFlightMessages) - - InvalidQosErrDetail = fmt.Sprintf("must be a valid QoS value %s or %s", - types.QosAtLeastOnce, types.QosAtMostOnce) - InvalidAuthTypeErrDetail = fmt.Sprintf("must be a valid Auth Type value %s", types.AuthTypeClientCredentials) - InvalidGrantTypeErrDetail = fmt.Sprintf("must be a valid Grant Type value %s", types.GrantTypeClientCredentials) - - MissingSchemeErrDetail = "must have URL scheme 'http' or 'https'" - SuffixMissingErrDetail = fmt.Sprintf("must have valid sink URL suffix %s", ClusterLocalURLSuffix) - SubDomainsErrDetail = fmt.Sprintf("must have sink URL with %d sub-domains: ", subdomainSegments) - NSMismatchErrDetail = "must have the same namespace as the subscriber: " -) - -func MakeInvalidFieldError(path *field.Path, subName, detail string) *field.Error { - return field.Invalid(path, subName, detail) -} diff --git a/api/eventing/v1alpha2/subscription_validation.go b/api/eventing/v1alpha2/subscription_validation.go deleted file mode 100644 index 15be68e4b..000000000 --- a/api/eventing/v1alpha2/subscription_validation.go +++ /dev/null @@ -1,148 +0,0 @@ -package v1alpha2 - -import ( - "strconv" - "strings" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/kyma-project/eventing-manager/pkg/ems/api/events/types" - "github.com/kyma-project/eventing-manager/pkg/utils" -) - -const ( - DefaultMaxInFlightMessages = "10" - minEventTypeSegments = 2 - subdomainSegments = 5 - InvalidPrefix = "sap.kyma.custom" - ClusterLocalURLSuffix = "svc.cluster.local" - ValidSource = "source" -) - -func (s *Subscription) ValidateSpec() field.ErrorList { - var allErrs field.ErrorList - if err := s.validateSubscriptionSource(); err != nil { - allErrs = append(allErrs, err) - } - if err := s.validateSubscriptionTypes(); err != nil { - allErrs = append(allErrs, err) - } - if err := s.validateSubscriptionConfig(); err != nil { - allErrs = append(allErrs, err...) - } - if err := s.validateSubscriptionSink(); err != nil { - allErrs = append(allErrs, err) - } - if len(allErrs) == 0 { - return nil - } - return allErrs -} - -func (s *Subscription) validateSubscriptionSource() *field.Error { - if s.Spec.Source == "" && s.Spec.TypeMatching != TypeMatchingExact { - return MakeInvalidFieldError(SourcePath, s.Spec.Source, EmptyErrDetail) - } - // Check only if the source is valid for the cloud event, with a valid event type. - if IsInvalidCE(s.Spec.Source, "") { - return MakeInvalidFieldError(SourcePath, s.Spec.Source, InvalidURIErrDetail) - } - return nil -} - -func (s *Subscription) validateSubscriptionTypes() *field.Error { - if s.Spec.Types == nil || len(s.Spec.Types) == 0 { - return MakeInvalidFieldError(TypesPath, "", EmptyErrDetail) - } - if duplicates := s.GetDuplicateTypes(); len(duplicates) > 0 { - return MakeInvalidFieldError(TypesPath, strings.Join(duplicates, ","), DuplicateTypesErrDetail) - } - for _, etype := range s.Spec.Types { - if len(etype) == 0 { - return MakeInvalidFieldError(TypesPath, etype, LengthErrDetail) - } - if segments := strings.Split(etype, "."); len(segments) < minEventTypeSegments { - return MakeInvalidFieldError(TypesPath, etype, MinSegmentErrDetail) - } - if s.Spec.TypeMatching != TypeMatchingExact && strings.HasPrefix(etype, InvalidPrefix) { - return MakeInvalidFieldError(TypesPath, etype, InvalidPrefixErrDetail) - } - // Check only is the event type is valid for the cloud event, with a valid source. - if IsInvalidCE(ValidSource, etype) { - return MakeInvalidFieldError(TypesPath, etype, InvalidURIErrDetail) - } - } - return nil -} - -func (s *Subscription) validateSubscriptionConfig() field.ErrorList { - var allErrs field.ErrorList - if isNotInt(s.Spec.Config[MaxInFlightMessages]) { - allErrs = append(allErrs, MakeInvalidFieldError(ConfigPath, s.Spec.Config[MaxInFlightMessages], StringIntErrDetail)) - } - if s.ifKeyExistsInConfig(ProtocolSettingsQos) && types.IsInvalidQoS(s.Spec.Config[ProtocolSettingsQos]) { - allErrs = append(allErrs, MakeInvalidFieldError(ConfigPath, s.Spec.Config[ProtocolSettingsQos], InvalidQosErrDetail)) - } - if s.ifKeyExistsInConfig(WebhookAuthType) && types.IsInvalidAuthType(s.Spec.Config[WebhookAuthType]) { - allErrs = append(allErrs, MakeInvalidFieldError(ConfigPath, s.Spec.Config[WebhookAuthType], InvalidAuthTypeErrDetail)) - } - if s.ifKeyExistsInConfig(WebhookAuthGrantType) && types.IsInvalidGrantType(s.Spec.Config[WebhookAuthGrantType]) { - allErrs = append(allErrs, MakeInvalidFieldError(ConfigPath, s.Spec.Config[WebhookAuthGrantType], InvalidGrantTypeErrDetail)) - } - return allErrs -} - -func (s *Subscription) validateSubscriptionSink() *field.Error { - if s.Spec.Sink == "" { - return MakeInvalidFieldError(SinkPath, s.Spec.Sink, EmptyErrDetail) - } - - if !utils.IsValidScheme(s.Spec.Sink) { - return MakeInvalidFieldError(SinkPath, s.Spec.Sink, MissingSchemeErrDetail) - } - - trimmedHost, subDomains, err := utils.GetSinkData(s.Spec.Sink) - if err != nil { - return MakeInvalidFieldError(SinkPath, s.Spec.Sink, err.Error()) - } - - // Validate sink URL is a cluster local URL. - if !strings.HasSuffix(trimmedHost, ClusterLocalURLSuffix) { - return MakeInvalidFieldError(SinkPath, s.Spec.Sink, SuffixMissingErrDetail) - } - - // We expected a sink in the format "service.namespace.svc.cluster.local". - if len(subDomains) != subdomainSegments { - return MakeInvalidFieldError(SinkPath, s.Spec.Sink, SubDomainsErrDetail+trimmedHost) - } - - // Assumption: Subscription CR and Subscriber should be deployed in the same namespace. - svcNs := subDomains[1] - if s.Namespace != svcNs { - return MakeInvalidFieldError(NSPath, s.Spec.Sink, NSMismatchErrDetail+svcNs) - } - - return nil -} - -func (s *Subscription) ifKeyExistsInConfig(key string) bool { - _, ok := s.Spec.Config[key] - return ok -} - -func isNotInt(value string) bool { - if _, err := strconv.Atoi(value); err != nil { - return true - } - return false -} - -func IsInvalidCE(source, eventType string) bool { - if source == "" { - return false - } - newEvent := utils.GetCloudEvent(eventType) - newEvent.SetSource(source) - err := newEvent.Validate() - return err != nil -} diff --git a/internal/controller/eventing/subscription/validator/errors.go b/internal/controller/eventing/subscription/validator/errors.go new file mode 100644 index 000000000..8f43b0e17 --- /dev/null +++ b/internal/controller/eventing/subscription/validator/errors.go @@ -0,0 +1,41 @@ +package validator + +import ( + "fmt" + "strconv" + + "k8s.io/apimachinery/pkg/util/validation/field" + + eventingv1alpha2 "github.com/kyma-project/eventing-manager/api/eventing/v1alpha2" + "github.com/kyma-project/eventing-manager/pkg/ems/api/events/types" +) + +//nolint:gochecknoglobals // these are required for testing +var ( + sourcePath = field.NewPath("spec").Child("source") + typesPath = field.NewPath("spec").Child("types") + configPath = field.NewPath("spec").Child("config") + sinkPath = field.NewPath("spec").Child("sink") + namespacePath = field.NewPath("metadata").Child("namespace") + + emptyErrDetail = "must not be empty" + invalidURIErrDetail = "must be valid as per RFC 3986" + duplicateTypesErrDetail = "must not have duplicate types" + lengthErrDetail = "must not be of length zero" + minSegmentErrDetail = fmt.Sprintf("must have minimum %s segments", strconv.Itoa(minEventTypeSegments)) + invalidPrefixErrDetail = fmt.Sprintf("must not have %s as type prefix", validPrefix) + stringIntErrDetail = fmt.Sprintf("%s must be a stringified int value", eventingv1alpha2.MaxInFlightMessages) + + invalidQosErrDetail = fmt.Sprintf("must be a valid QoS value %s or %s", types.QosAtLeastOnce, types.QosAtMostOnce) + invalidAuthTypeErrDetail = fmt.Sprintf("must be a valid Auth Type value %s", types.AuthTypeClientCredentials) + invalidGrantTypeErrDetail = fmt.Sprintf("must be a valid Grant Type value %s", types.GrantTypeClientCredentials) + + missingSchemeErrDetail = "must have URL scheme 'http' or 'https'" + suffixMissingErrDetail = fmt.Sprintf("must have valid sink URL suffix %s", clusterLocalURLSuffix) + subDomainsErrDetail = fmt.Sprintf("must have sink URL with %d sub-domains: ", subdomainSegments) + namespaceMismatchErrDetail = "must have the same namespace as the subscriber: " +) + +func makeInvalidFieldError(path *field.Path, subName, detail string) *field.Error { + return field.Invalid(path, subName, detail) +} diff --git a/internal/controller/eventing/subscription/validator/spec.go b/internal/controller/eventing/subscription/validator/spec.go new file mode 100644 index 000000000..ab2680448 --- /dev/null +++ b/internal/controller/eventing/subscription/validator/spec.go @@ -0,0 +1,148 @@ +package validator + +import ( + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" + + eventingv1alpha2 "github.com/kyma-project/eventing-manager/api/eventing/v1alpha2" + "github.com/kyma-project/eventing-manager/pkg/ems/api/events/types" + "github.com/kyma-project/eventing-manager/pkg/utils" +) + +const ( + minEventTypeSegments = 2 + subdomainSegments = 5 + validPrefix = "sap.kyma.custom" + clusterLocalURLSuffix = "svc.cluster.local" +) + +func validateSpec(subscription eventingv1alpha2.Subscription) field.ErrorList { + var allErrs field.ErrorList + if err := validateSource(subscription); err != nil { + allErrs = append(allErrs, err) + } + if err := validateTypes(subscription); err != nil { + allErrs = append(allErrs, err) + } + if err := validateConfig(subscription); err != nil { + allErrs = append(allErrs, err...) + } + if err := validateSink(subscription); err != nil { + allErrs = append(allErrs, err) + } + if len(allErrs) == 0 { + return nil + } + return allErrs +} + +func validateSource(subscription eventingv1alpha2.Subscription) *field.Error { + if subscription.Spec.Source == "" && subscription.Spec.TypeMatching != eventingv1alpha2.TypeMatchingExact { + return makeInvalidFieldError(sourcePath, subscription.Spec.Source, emptyErrDetail) + } + // Check only if the source is valid for the cloud event, with a valid event type. + if IsInvalidCE(subscription.Spec.Source, "") { + return makeInvalidFieldError(sourcePath, subscription.Spec.Source, invalidURIErrDetail) + } + return nil +} + +func validateTypes(subscription eventingv1alpha2.Subscription) *field.Error { + if subscription.Spec.Types == nil || len(subscription.Spec.Types) == 0 { + return makeInvalidFieldError(typesPath, "", emptyErrDetail) + } + if duplicates := subscription.GetDuplicateTypes(); len(duplicates) > 0 { + return makeInvalidFieldError(typesPath, strings.Join(duplicates, ","), duplicateTypesErrDetail) + } + for _, eventType := range subscription.Spec.Types { + if len(eventType) == 0 { + return makeInvalidFieldError(typesPath, eventType, lengthErrDetail) + } + if segments := strings.Split(eventType, "."); len(segments) < minEventTypeSegments { + return makeInvalidFieldError(typesPath, eventType, minSegmentErrDetail) + } + if subscription.Spec.TypeMatching != eventingv1alpha2.TypeMatchingExact && strings.HasPrefix(eventType, validPrefix) { + return makeInvalidFieldError(typesPath, eventType, invalidPrefixErrDetail) + } + // Check only is the event type is valid for the cloud event, with a valid source. + const validSource = "source" + if IsInvalidCE(validSource, eventType) { + return makeInvalidFieldError(typesPath, eventType, invalidURIErrDetail) + } + } + return nil +} + +func validateConfig(subscription eventingv1alpha2.Subscription) field.ErrorList { + var allErrs field.ErrorList + if isNotInt(subscription.Spec.Config[eventingv1alpha2.MaxInFlightMessages]) { + allErrs = append(allErrs, makeInvalidFieldError(configPath, subscription.Spec.Config[eventingv1alpha2.MaxInFlightMessages], stringIntErrDetail)) + } + if ifKeyExistsInConfig(subscription, eventingv1alpha2.ProtocolSettingsQos) && types.IsInvalidQoS(subscription.Spec.Config[eventingv1alpha2.ProtocolSettingsQos]) { + allErrs = append(allErrs, makeInvalidFieldError(configPath, subscription.Spec.Config[eventingv1alpha2.ProtocolSettingsQos], invalidQosErrDetail)) + } + if ifKeyExistsInConfig(subscription, eventingv1alpha2.WebhookAuthType) && types.IsInvalidAuthType(subscription.Spec.Config[eventingv1alpha2.WebhookAuthType]) { + allErrs = append(allErrs, makeInvalidFieldError(configPath, subscription.Spec.Config[eventingv1alpha2.WebhookAuthType], invalidAuthTypeErrDetail)) + } + if ifKeyExistsInConfig(subscription, eventingv1alpha2.WebhookAuthGrantType) && types.IsInvalidGrantType(subscription.Spec.Config[eventingv1alpha2.WebhookAuthGrantType]) { + allErrs = append(allErrs, makeInvalidFieldError(configPath, subscription.Spec.Config[eventingv1alpha2.WebhookAuthGrantType], invalidGrantTypeErrDetail)) + } + return allErrs +} + +func validateSink(subscription eventingv1alpha2.Subscription) *field.Error { + if subscription.Spec.Sink == "" { + return makeInvalidFieldError(sinkPath, subscription.Spec.Sink, emptyErrDetail) + } + + if !utils.IsValidScheme(subscription.Spec.Sink) { + return makeInvalidFieldError(sinkPath, subscription.Spec.Sink, missingSchemeErrDetail) + } + + trimmedHost, subDomains, err := utils.GetSinkData(subscription.Spec.Sink) + if err != nil { + return makeInvalidFieldError(sinkPath, subscription.Spec.Sink, err.Error()) + } + + // Validate sink URL is a cluster local URL. + if !strings.HasSuffix(trimmedHost, clusterLocalURLSuffix) { + return makeInvalidFieldError(sinkPath, subscription.Spec.Sink, suffixMissingErrDetail) + } + + // We expected a sink in the format "service.namespace.svc.cluster.local". + if len(subDomains) != subdomainSegments { + return makeInvalidFieldError(sinkPath, subscription.Spec.Sink, subDomainsErrDetail+trimmedHost) + } + + // Assumption: Subscription CR and Subscriber should be deployed in the same namespace. + svcNs := subDomains[1] + if subscription.Namespace != svcNs { + return makeInvalidFieldError(namespacePath, subscription.Spec.Sink, namespaceMismatchErrDetail+svcNs) + } + + return nil +} + +func ifKeyExistsInConfig(subscription eventingv1alpha2.Subscription, key string) bool { + _, ok := subscription.Spec.Config[key] + return ok +} + +func isNotInt(value string) bool { + if _, err := strconv.Atoi(value); err != nil { + return true + } + return false +} + +func IsInvalidCE(source, eventType string) bool { + if source == "" { + return false + } + newEvent := utils.GetCloudEvent(eventType) + newEvent.SetSource(source) + err := newEvent.Validate() + return err != nil +} diff --git a/api/eventing/v1alpha2/subscription_validation_test.go b/internal/controller/eventing/subscription/validator/spec_test.go similarity index 67% rename from api/eventing/v1alpha2/subscription_validation_test.go rename to internal/controller/eventing/subscription/validator/spec_test.go index 533fcfb2a..f46b16e4f 100644 --- a/api/eventing/v1alpha2/subscription_validation_test.go +++ b/internal/controller/eventing/subscription/validator/spec_test.go @@ -1,4 +1,4 @@ -package v1alpha2_test +package validator import ( "testing" @@ -6,21 +6,23 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/util/validation/field" - "github.com/kyma-project/eventing-manager/api/eventing/v1alpha2" + eventingv1alpha2 "github.com/kyma-project/eventing-manager/api/eventing/v1alpha2" eventingtesting "github.com/kyma-project/eventing-manager/testing" ) -const ( - subName = "sub" - subNamespace = "test" - sink = "https://eventing-nats.test.svc.cluster.local:8080" -) - func Test_validateSubscription(t *testing.T) { t.Parallel() + + const ( + subName = "sub" + subNamespace = "test" + maxInFlightMessages = "10" + sink = "https://eventing-nats.test.svc.cluster.local:8080" + ) + type TestCase struct { name string - givenSub *v1alpha2.Subscription + givenSub *eventingv1alpha2.Subscription wantErr field.ErrorList } @@ -31,7 +33,7 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), eventingtesting.WithWebhookAuthForEventMesh(), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), wantErr: nil, @@ -41,11 +43,11 @@ func Test_validateSubscription(t *testing.T) { givenSub: eventingtesting.NewSubscription(subName, subNamespace, eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SourcePath, - "", v1alpha2.EmptyErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sourcePath, + "", emptyErrDetail)}, }, { name: "valid source and TypeMatching Standard should not return error", @@ -53,7 +55,7 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), wantErr: nil, @@ -63,7 +65,7 @@ func Test_validateSubscription(t *testing.T) { givenSub: eventingtesting.NewSubscription(subName, subNamespace, eventingtesting.WithTypeMatchingExact(), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), wantErr: nil, @@ -74,22 +76,22 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource("s%ourc%e"), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SourcePath, - "s%ourc%e", v1alpha2.InvalidURIErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sourcePath, + "s%ourc%e", invalidURIErrDetail)}, }, { name: "nil types field should return error", givenSub: eventingtesting.NewSubscription(subName, subNamespace, eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "", v1alpha2.EmptyErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(typesPath, + "", emptyErrDetail)}, }, { name: "empty types field should return error", @@ -97,11 +99,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithTypes([]string{}), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "", v1alpha2.EmptyErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(typesPath, + "", emptyErrDetail)}, }, { name: "duplicate types should return error", @@ -112,11 +114,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.OrderCreatedV1Event, eventingtesting.OrderCreatedV1Event, }), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "order.created.v1", v1alpha2.DuplicateTypesErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(typesPath, + "order.created.v1", duplicateTypesErrDetail)}, }, { name: "empty event type should return error", @@ -124,11 +126,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithTypes([]string{eventingtesting.OrderCreatedV1Event, ""}), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "", v1alpha2.LengthErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(typesPath, + "", lengthErrDetail)}, }, { name: "lower than min segments should return error", @@ -136,31 +138,32 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithTypes([]string{"order"}), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "order", v1alpha2.MinSegmentErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(typesPath, + "order", minSegmentErrDetail)}, }, { name: "invalid prefix should return error", givenSub: eventingtesting.NewSubscription(subName, subNamespace, eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), - eventingtesting.WithTypes([]string{v1alpha2.InvalidPrefix}), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithTypes([]string{validPrefix}), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.TypesPath, - "sap.kyma.custom", v1alpha2.InvalidPrefixErrDetail)}, + wantErr: field.ErrorList{ + makeInvalidFieldError(typesPath, "sap.kyma.custom", invalidPrefixErrDetail), + }, }, { name: "invalid prefix with exact should not return error", givenSub: eventingtesting.NewSubscription(subName, subNamespace, eventingtesting.WithTypeMatchingExact(), eventingtesting.WithSource(eventingtesting.EventSourceClean), - eventingtesting.WithTypes([]string{v1alpha2.InvalidPrefix}), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithTypes([]string{validPrefix}), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(sink), ), wantErr: nil, @@ -174,8 +177,8 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithMaxInFlightMessages("invalid"), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.ConfigPath, - "invalid", v1alpha2.StringIntErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(configPath, + "invalid", stringIntErrDetail)}, }, { name: "invalid QoS value should return error", @@ -183,12 +186,12 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithInvalidProtocolSettingsQos(), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.ConfigPath, - "AT_INVALID_ONCE", v1alpha2.InvalidQosErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(configPath, + "AT_INVALID_ONCE", invalidQosErrDetail)}, }, { name: "invalid webhook auth type value should return error", @@ -196,12 +199,12 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithInvalidWebhookAuthType(), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.ConfigPath, - "abcd", v1alpha2.InvalidAuthTypeErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(configPath, + "abcd", invalidAuthTypeErrDetail)}, }, { name: "invalid webhook grant type value should return error", @@ -209,12 +212,12 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithInvalidWebhookAuthGrantType(), eventingtesting.WithSink(sink), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.ConfigPath, - "invalid", v1alpha2.InvalidGrantTypeErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(configPath, + "invalid", invalidGrantTypeErrDetail)}, }, { name: "missing sink should return error", @@ -222,10 +225,10 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, - "", v1alpha2.EmptyErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, + "", emptyErrDetail)}, }, { name: "sink with invalid scheme should return error", @@ -233,11 +236,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink(subNamespace), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, - "test", v1alpha2.MissingSchemeErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, + "test", missingSchemeErrDetail)}, }, { name: "sink with invalid URL should return error", @@ -245,10 +248,10 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink("http://invalid Sink"), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, "http://invalid Sink", "failed to parse subscription sink URL: "+ "parse \"http://invalid Sink\": invalid character \" \" in host name")}, }, @@ -258,11 +261,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink("https://svc2.test.local"), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, - "https://svc2.test.local", v1alpha2.SuffixMissingErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, + "https://svc2.test.local", suffixMissingErrDetail)}, }, { name: "sink with invalid suffix and port should return error", @@ -270,11 +273,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink("https://svc2.test.local:8080"), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, - "https://svc2.test.local:8080", v1alpha2.SuffixMissingErrDetail)}, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, + "https://svc2.test.local:8080", suffixMissingErrDetail)}, }, { name: "sink with invalid number of subdomains should return error", @@ -282,11 +285,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink("https://svc.cluster.local:8080"), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.SinkPath, - "https://svc.cluster.local:8080", v1alpha2.SubDomainsErrDetail+"svc.cluster.local")}, + wantErr: field.ErrorList{makeInvalidFieldError(sinkPath, + "https://svc.cluster.local:8080", subDomainsErrDetail+"svc.cluster.local")}, }, { name: "sink with different namespace should return error", @@ -294,11 +297,11 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithTypeMatchingStandard(), eventingtesting.WithSource(eventingtesting.EventSourceClean), eventingtesting.WithEventType(eventingtesting.OrderCreatedV1Event), - eventingtesting.WithMaxInFlightMessages(v1alpha2.DefaultMaxInFlightMessages), + eventingtesting.WithMaxInFlightMessages(maxInFlightMessages), eventingtesting.WithSink("https://eventing-nats.kyma-system.svc.cluster.local"), ), - wantErr: field.ErrorList{v1alpha2.MakeInvalidFieldError(v1alpha2.NSPath, - "https://eventing-nats.kyma-system.svc.cluster.local", v1alpha2.NSMismatchErrDetail+"kyma-system")}, + wantErr: field.ErrorList{makeInvalidFieldError(namespacePath, + "https://eventing-nats.kyma-system.svc.cluster.local", namespaceMismatchErrDetail+"kyma-system")}, }, { name: "multiple errors should be reported if exists", @@ -309,10 +312,10 @@ func Test_validateSubscription(t *testing.T) { eventingtesting.WithSink(sink), ), wantErr: field.ErrorList{ - v1alpha2.MakeInvalidFieldError(v1alpha2.SourcePath, - "", v1alpha2.EmptyErrDetail), - v1alpha2.MakeInvalidFieldError(v1alpha2.ConfigPath, - "invalid", v1alpha2.StringIntErrDetail), + makeInvalidFieldError(sourcePath, + "", emptyErrDetail), + makeInvalidFieldError(configPath, + "invalid", stringIntErrDetail), }, }, } @@ -321,7 +324,7 @@ func Test_validateSubscription(t *testing.T) { tc := testCase t.Run(tc.name, func(t *testing.T) { t.Parallel() - err := tc.givenSub.ValidateSpec() + err := validateSpec(*tc.givenSub) require.Equal(t, tc.wantErr, err) }) } @@ -367,7 +370,7 @@ func Test_IsInvalidCESource(t *testing.T) { tc := testCase t.Run(tc.name, func(t *testing.T) { t.Parallel() - gotIsInvalid := v1alpha2.IsInvalidCE(tc.givenSource, tc.givenType) + gotIsInvalid := IsInvalidCE(tc.givenSource, tc.givenType) require.Equal(t, tc.wantIsInvalid, gotIsInvalid) }) } diff --git a/internal/controller/eventing/subscription/validator/subscription.go b/internal/controller/eventing/subscription/validator/subscription.go index 070803108..a6e7f7c40 100644 --- a/internal/controller/eventing/subscription/validator/subscription.go +++ b/internal/controller/eventing/subscription/validator/subscription.go @@ -27,7 +27,7 @@ func NewSubscriptionValidator(sinkValidator SinkValidator) SubscriptionValidator } func (sv *subscriptionValidator) Validate(ctx context.Context, subscription eventingv1alpha2.Subscription) error { - if errs := subscription.ValidateSpec(); len(errs) > 0 { + if errs := validateSpec(subscription); len(errs) > 0 { return fmt.Errorf("%w: %w", ErrSubscriptionValidationFailed, errs.ToAggregate()) } if err := sv.sinkValidator.Validate(ctx, subscription.Spec.Sink); err != nil {