diff --git a/apis/duck/ABOUT.md b/apis/duck/ABOUT.md index b223412a0..989769220 100644 --- a/apis/duck/ABOUT.md +++ b/apis/duck/ABOUT.md @@ -302,7 +302,11 @@ is used with duck types:                 ...         } logger := c.Logger.Named("Revisions") -        impl := controller.NewImpl(c, logger, "Revisions") +        impl := controller.NewContext( + ctx, + c, + controller.ControllerOptions{WorkQueueName: "Revisions", Logger: logger}, + ) // Calls to Track create a 30 minute lease before they must be renewed. // Coordinate this value with controller resync periods. diff --git a/apis/duck/v1/source_types.go b/apis/duck/v1/source_types.go index 2f1afe246..00e699156 100644 --- a/apis/duck/v1/source_types.go +++ b/apis/duck/v1/source_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + "context" "time" corev1 "k8s.io/api/core/v1" @@ -170,3 +171,45 @@ type SourceList struct { Items []Source `json:"items"` } + +func (s *Source) Validate(ctx context.Context) *apis.FieldError { + if s == nil { + return nil + } + return s.Spec.Validate(ctx).ViaField("spec") +} + +func (s *SourceSpec) Validate(ctx context.Context) *apis.FieldError { + return s.CloudEventOverrides.Validate(ctx).ViaField("ceOverrides") +} + +func (ceOverrides *CloudEventOverrides) Validate(ctx context.Context) *apis.FieldError { + for key := range ceOverrides.Extensions { + if err := validateExtensionName(key); err != nil { + return err.ViaField("extensions") + } + } + return nil +} + +func validateExtensionName(key string) *apis.FieldError { + if key == "" { + return apis.ErrInvalidKeyName( + key, + "", + "keys MUST NOT be empty", + ) + } + + for _, c := range key { + if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { + return apis.ErrInvalidKeyName( + key, + "", + "keys are expected to be alphanumeric", + ) + } + } + + return nil +} diff --git a/apis/duck/v1/source_types_test.go b/apis/duck/v1/source_types_test.go new file mode 100644 index 000000000..37dfb718c --- /dev/null +++ b/apis/duck/v1/source_types_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2021 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "context" + "testing" + + "knative.dev/pkg/apis" +) + +func TestSourceValidate(t *testing.T) { + for _, tt := range []struct { + name string + src *Source + want *apis.FieldError + }{{ + name: "empty source validation", + src: nil, + want: nil, + }, { + name: "empty source ceOverrides extensions validation", + src: &Source{Spec: SourceSpec{ + CloudEventOverrides: &CloudEventOverrides{Extensions: map[string]string{}}, + }}, + want: nil, + }, { + name: "empty extension name error", + src: &Source{Spec: SourceSpec{ + CloudEventOverrides: &CloudEventOverrides{Extensions: map[string]string{"": "test"}}, + }}, + want: apis.ErrInvalidKeyName( + "", + "spec.ceOverrides.extensions", + "keys MUST NOT be empty", + ), + }, { + name: "long extension key name is valid", + src: &Source{Spec: SourceSpec{ + CloudEventOverrides: &CloudEventOverrides{ + Extensions: map[string]string{"nameLongerThan20Characters": "test"}, + }, + }}, + want: nil, + }, { + name: "invalid extension name", + src: &Source{Spec: SourceSpec{ + CloudEventOverrides: &CloudEventOverrides{Extensions: map[string]string{"invalid_name": "test"}}, + }}, + want: apis.ErrInvalidKeyName( + "invalid_name", + "spec.ceOverrides.extensions", + "keys are expected to be alphanumeric", + ), + }, { + name: "valid extension name", + src: &Source{Spec: SourceSpec{ + CloudEventOverrides: &CloudEventOverrides{ + Extensions: map[string]string{"validName": "test"}, + }, + }}, + want: nil, + }} { + t.Run(tt.name, func(t *testing.T) { + tt := tt + t.Parallel() + got := tt.src.Validate(context.TODO()) + if tt.want != nil && got.Error() != tt.want.Error() { + t.Errorf("Unexpected error want:\n%+s\ngot:\n%+s", tt.want, got) + } + + if tt.want == nil && got != nil { + t.Errorf("Unexpected error want:\nnil\ngot:\n%+s", got) + } + }) + } +}