diff --git a/api/handlers/build_test.go b/api/handlers/build_test.go index 04808e723..c4dfd19ee 100644 --- a/api/handlers/build_test.go +++ b/api/handlers/build_test.go @@ -64,8 +64,7 @@ var _ = Describe("Build", func() { req, err = http.NewRequestWithContext(ctx, "GET", "/v3/builds/"+buildGUID, nil) Expect(err).NotTo(HaveOccurred()) - decoderValidator, err := NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) + decoderValidator := NewDefaultDecoderValidator() apiHandler := NewBuild( *serverURL, @@ -315,18 +314,16 @@ var _ = Describe("Build", func() { Describe("the PATCH /v3/builds endpoint", func() { BeforeEach(func() { - decoderValidator, err := NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) - apiHandler := NewBuild( *serverURL, new(fake.CFBuildRepository), new(fake.CFPackageRepository), new(fake.CFAppRepository), - decoderValidator, + NewDefaultDecoderValidator(), ) routerBuilder.LoadRoutes(apiHandler) + var err error req, err = http.NewRequestWithContext(context.Background(), "PATCH", "/v3/builds/build-guid", strings.NewReader(`{}`)) Expect(err).NotTo(HaveOccurred()) }) diff --git a/api/handlers/deployment_test.go b/api/handlers/deployment_test.go index 3b3d2b991..06e548341 100644 --- a/api/handlers/deployment_test.go +++ b/api/handlers/deployment_test.go @@ -30,8 +30,7 @@ var _ = Describe("Deployment", func() { runnerInfoRepo = new(fake.RunnerInfoRepository) runnerName = "statefulset-runner" - decoderValidator, err := handlers.NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) + decoderValidator := handlers.NewDefaultDecoderValidator() apiHandler := handlers.NewDeployment(*serverURL, decoderValidator, deploymentsRepo, runnerInfoRepo, runnerName) routerBuilder.LoadRoutes(apiHandler) diff --git a/api/handlers/service_instance_test.go b/api/handlers/service_instance_test.go index 8abf21231..ae4fb02e5 100644 --- a/api/handlers/service_instance_test.go +++ b/api/handlers/service_instance_test.go @@ -66,8 +66,8 @@ var _ = Describe("ServiceInstance", func() { Name: "service-instance-name", Type: "user-provided", Tags: []string{"foo", "bar"}, - Relationships: payloads.ServiceInstanceRelationships{ - Space: payloads.Relationship{ + Relationships: &payloads.ServiceInstanceRelationships{ + Space: &payloads.Relationship{ Data: &payloads.RelationshipData{ GUID: "space-guid", }, diff --git a/api/handlers/space.go b/api/handlers/space.go index e659e3f74..70aec6d59 100644 --- a/api/handlers/space.go +++ b/api/handlers/space.go @@ -34,10 +34,10 @@ type SpaceRepository interface { type Space struct { spaceRepo SpaceRepository apiBaseURL url.URL - decoderValidator *DecoderValidator + decoderValidator DecoderValidator } -func NewSpace(apiBaseURL url.URL, spaceRepo SpaceRepository, decoderValidator *DecoderValidator) *Space { +func NewSpace(apiBaseURL url.URL, spaceRepo SpaceRepository, decoderValidator DecoderValidator) *Space { return &Space{ apiBaseURL: apiBaseURL, spaceRepo: spaceRepo, diff --git a/api/handlers/space_manifest.go b/api/handlers/space_manifest.go index 1cba4177b..802613e43 100644 --- a/api/handlers/space_manifest.go +++ b/api/handlers/space_manifest.go @@ -33,7 +33,7 @@ type SpaceManifest struct { serverURL url.URL manifestApplier ManifestApplier spaceRepo CFSpaceRepository - decoderValidator *DecoderValidator + decoderValidator DecoderValidator } //counterfeiter:generate -o fake -fake-name ManifestApplier . ManifestApplier @@ -45,7 +45,7 @@ func NewSpaceManifest( serverURL url.URL, manifestApplier ManifestApplier, spaceRepo CFSpaceRepository, - decoderValidator *DecoderValidator, + decoderValidator DecoderValidator, ) *SpaceManifest { return &SpaceManifest{ serverURL: serverURL, diff --git a/api/handlers/space_manifest_test.go b/api/handlers/space_manifest_test.go index 23009e03b..7498ee576 100644 --- a/api/handlers/space_manifest_test.go +++ b/api/handlers/space_manifest_test.go @@ -33,8 +33,7 @@ var _ = Describe("SpaceManifest", func() { manifestApplier = new(fake.ManifestApplier) spaceRepo = new(fake.CFSpaceRepository) - decoderValidator, err := NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) + decoderValidator := NewDefaultDecoderValidator() apiHandler := NewSpaceManifest( *serverURL, diff --git a/api/handlers/space_test.go b/api/handlers/space_test.go index 7e14e8e18..632c067ce 100644 --- a/api/handlers/space_test.go +++ b/api/handlers/space_test.go @@ -36,8 +36,7 @@ var _ = Describe("Space", func() { OrganizationGUID: "the-org-guid", }, nil) - decoderValidator, err := handlers.NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) + decoderValidator := handlers.NewDefaultDecoderValidator() apiHandler = handlers.NewSpace( *serverURL, @@ -149,7 +148,7 @@ var _ = Describe("Space", func() { }) It("returns a bad request error", func() { - expectUnprocessableEntityError("Data is a required field") + expectUnprocessableEntityError("organization is required") }) }) }) @@ -362,7 +361,7 @@ var _ = Describe("Space", func() { }) It("returns an unprocessable entity error", func() { - expectUnprocessableEntityError(`Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`) + expectUnprocessableEntityError("cannot use the cloudfoundry.org domain") }) }) @@ -378,7 +377,7 @@ var _ = Describe("Space", func() { }) It("returns an unprocessable entity error", func() { - expectUnprocessableEntityError(`Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`) + expectUnprocessableEntityError("cannot use the cloudfoundry.org domain") }) }) }) @@ -396,7 +395,7 @@ var _ = Describe("Space", func() { }) It("returns an unprocessable entity error", func() { - expectUnprocessableEntityError(`Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`) + expectUnprocessableEntityError("cannot use the cloudfoundry.org domain") }) }) @@ -412,7 +411,7 @@ var _ = Describe("Space", func() { }) It("returns an unprocessable entity error", func() { - expectUnprocessableEntityError(`Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`) + expectUnprocessableEntityError("cannot use the cloudfoundry.org domain") }) }) }) diff --git a/api/handlers/validator.go b/api/handlers/validator.go index 26819bfc2..81c4a3707 100644 --- a/api/handlers/validator.go +++ b/api/handlers/validator.go @@ -12,12 +12,7 @@ import ( apierrors "code.cloudfoundry.org/korifi/api/errors" - "github.com/go-playground/locales/en" - ut "github.com/go-playground/universal-translator" - "github.com/go-playground/validator/v10" - en_translations "github.com/go-playground/validator/v10/translations/en" "github.com/jellydator/validation" - "golang.org/x/exp/maps" "golang.org/x/text/cases" "golang.org/x/text/language" "gopkg.in/yaml.v3" @@ -35,24 +30,13 @@ type KeyedPayload interface { DecodeFromURLValues(url.Values) error } -type DecoderValidator struct { - validator *validator.Validate - translator ut.Translator -} - -func NewDefaultDecoderValidator() (*DecoderValidator, error) { - validator, translator, err := wireValidator() - if err != nil { - return nil, err - } +type DecoderValidator struct{} - return &DecoderValidator{ - validator: validator, - translator: translator, - }, nil +func NewDefaultDecoderValidator() DecoderValidator { + return DecoderValidator{} } -func (dv *DecoderValidator) DecodeAndValidateJSONPayload(r *http.Request, object interface{}) error { +func (dv DecoderValidator) DecodeAndValidateJSONPayload(r *http.Request, object interface{}) error { decoder := json.NewDecoder(r.Body) defer r.Body.Close() decoder.DisallowUnknownFields() @@ -74,7 +58,7 @@ func (dv *DecoderValidator) DecodeAndValidateJSONPayload(r *http.Request, object return dv.validatePayload(object) } -func (dv *DecoderValidator) DecodeAndValidateYAMLPayload(r *http.Request, object interface{}) error { +func (dv DecoderValidator) DecodeAndValidateYAMLPayload(r *http.Request, object interface{}) error { decoder := yaml.NewDecoder(r.Body) defer r.Body.Close() decoder.KnownFields(false) // TODO: change this to true once we've added all manifest fields to payloads.Manifest @@ -86,7 +70,7 @@ func (dv *DecoderValidator) DecodeAndValidateYAMLPayload(r *http.Request, object return dv.validatePayload(object) } -func (dv *DecoderValidator) DecodeAndValidateURLValues(r *http.Request, object KeyedPayload) error { +func (dv DecoderValidator) DecodeAndValidateURLValues(r *http.Request, object KeyedPayload) error { if err := r.ParseForm(); err != nil { return err } @@ -114,34 +98,13 @@ func checkKeysAreSupported(payloadObject KeyedPayload, values url.Values) error } func (dv *DecoderValidator) validatePayload(object interface{}) error { - // New validation library for which we have implemented manifest payload validation t, ok := object.(validation.Validatable) - if ok { - err := t.Validate() - if err != nil { - return apierrors.NewUnprocessableEntityError(err, strings.Join(errorMessages(err), ", ")) - } + if !ok { return nil } - // Existing validation library for payloads that have not yet implemented validation.Validatable - err := dv.validator.Struct(object) - if err != nil { - errorMessage := err.Error() - - var typedErr validator.ValidationErrors - if errors.As(err, &typedErr) { - errorMap := typedErr.Translate(dv.translator) - var errorMessages []string - for _, msg := range errorMap { - errorMessages = append(errorMessages, msg) - } - - if len(errorMessages) > 0 { - errorMessage = strings.Join(errorMessages, ",") - } - } - return apierrors.NewUnprocessableEntityError(err, errorMessage) + if err := t.Validate(); err != nil { + return apierrors.NewUnprocessableEntityError(err, strings.Join(errorMessages(err), ", ")) } return nil @@ -184,113 +147,3 @@ func prefixedErrorMessages(field string, err error) []string { return messages } - -func wireValidator() (*validator.Validate, ut.Translator, error) { - v := validator.New() - - trans, err := registerDefaultTranslator(v) - if err != nil { - return nil, nil, err - } - // Register custom validators - err = v.RegisterValidation("serviceinstancetaglength", serviceInstanceTagLength) - if err != nil { - return nil, nil, err - } - - err = v.RegisterValidation("metadatavalidator", metadataValidator) - if err != nil { - return nil, nil, err - } - err = v.RegisterTranslation("metadatavalidator", trans, func(ut ut.Translator) error { - return ut.Add("metadatavalidator", `Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`, false) - }, func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("metadatavalidator", fe.Field()) - return t - }) - if err != nil { - return nil, nil, err - } - - err = v.RegisterValidation("buildmetadatavalidator", buildMetadataValidator) - if err != nil { - return nil, nil, err - } - err = v.RegisterTranslation("buildmetadatavalidator", trans, func(ut ut.Translator) error { - return ut.Add("buildmetadatavalidator", `Labels and annotations are not supported for builds`, false) - }, func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("buildmetadatavalidator", fe.Field()) - return t - }) - if err != nil { - return nil, nil, err - } - - return v, trans, nil -} - -func registerDefaultTranslator(v *validator.Validate) (ut.Translator, error) { - en := en.New() - uni := ut.New(en, en) - trans, _ := uni.GetTranslator("en") - - err := en_translations.RegisterDefaultTranslations(v, trans) - if err != nil { - return nil, err - } - - return trans, nil -} - -func serviceInstanceTagLength(fl validator.FieldLevel) bool { - tags, ok := fl.Field().Interface().([]string) - if !ok { - return true // the value is optional, and is set to nil - } - - tagLen := 0 - for _, tag := range tags { - tagLen += len(tag) - } - - return tagLen < 2048 -} - -func metadataValidator(fl validator.FieldLevel) bool { - metadata, isMeta := fl.Field().Interface().(map[string]string) - if isMeta { - return validateMetadataKeys(maps.Keys(metadata)) - } - - metadataPatch, isMetaPatch := fl.Field().Interface().(map[string]*string) - if isMetaPatch { - return validateMetadataKeys(maps.Keys(metadataPatch)) - } - - return true -} - -func buildMetadataValidator(fl validator.FieldLevel) bool { - metadata, isMeta := fl.Field().Interface().(map[string]string) - if isMeta { - if len(metadata) > 0 { - return false - } - } - return true -} - -func validateMetadataKeys(metaKeys []string) bool { - for _, key := range metaKeys { - u, err := url.ParseRequestURI("https://" + key) // without the scheme, the hostname will be parsed as a path - if err != nil { - continue - } - - if strings.HasSuffix(u.Hostname(), "cloudfoundry.org") { - return false - } - } - - return true -} diff --git a/api/handlers/validator_test.go b/api/handlers/validator_test.go index aaba495ff..bf4d1acdc 100644 --- a/api/handlers/validator_test.go +++ b/api/handlers/validator_test.go @@ -15,19 +15,15 @@ import ( var _ = Describe("Validator", func() { Describe("DecodeAndValidateURLValues", func() { var ( - requestValidator *handlers.DecoderValidator + requestValidator handlers.DecoderValidator decoded DecodeTestPayload decodeErr error requestUrl string ) BeforeEach(func() { - var err error - requestValidator, err = handlers.NewDefaultDecoderValidator() - Expect(err).NotTo(HaveOccurred()) - + requestValidator = handlers.NewDefaultDecoderValidator() requestUrl = "http://foo.com?key=3" - decoded = DecodeTestPayload{} }) diff --git a/api/main.go b/api/main.go index 97e4fa766..e032bee0e 100644 --- a/api/main.go +++ b/api/main.go @@ -236,10 +236,7 @@ func main() { ) appLogs := actions.NewAppLogs(appRepo, buildRepo, podRepo) - decoderValidator, err := handlers.NewDefaultDecoderValidator() - if err != nil { - panic(fmt.Sprintf("could not wire validator: %v", err)) - } + decoderValidator := handlers.NewDefaultDecoderValidator() routerBuilder := routing.NewRouterBuilder() routerBuilder.UseMiddleware( diff --git a/api/payloads/app.go b/api/payloads/app.go index 7253b8fa1..85dfe9ec3 100644 --- a/api/payloads/app.go +++ b/api/payloads/app.go @@ -151,6 +151,12 @@ type AppPatch struct { Metadata MetadataPatch `json:"metadata"` } +func (p AppPatch) Validate() error { + return validation.ValidateStruct(&p, + validation.Field(&p.Metadata), + ) +} + func (a *AppPatch) ToMessage(appGUID, spaceGUID string) repositories.PatchAppMetadataMessage { return repositories.PatchAppMetadataMessage{ AppGUID: appGUID, diff --git a/api/payloads/app_test.go b/api/payloads/app_test.go index e446231e1..e9b08b33f 100644 --- a/api/payloads/app_test.go +++ b/api/payloads/app_test.go @@ -156,7 +156,7 @@ var _ = Describe("App payload validation", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, `Labels and annotations cannot begin with "cloudfoundry.org" or its subdomains`) + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") }) }) }) diff --git a/api/payloads/deployment.go b/api/payloads/deployment.go index 1aaa57c09..bf9495d37 100644 --- a/api/payloads/deployment.go +++ b/api/payloads/deployment.go @@ -2,6 +2,7 @@ package payloads import ( "code.cloudfoundry.org/korifi/api/repositories" + "github.com/jellydator/validation" ) type DropletGUID struct { @@ -10,11 +11,12 @@ type DropletGUID struct { type DeploymentCreate struct { Droplet DropletGUID `json:"droplet"` - Relationships *DeploymentRelationships `json:"relationships" validate:"required"` + Relationships *DeploymentRelationships `json:"relationships"` } -type DeploymentRelationships struct { - App *Relationship `json:"app" validate:"required"` +func (c DeploymentCreate) Validate() error { + return validation.ValidateStruct(&c, + validation.Field(&c.Relationships, validation.NotNil)) } func (c *DeploymentCreate) ToMessage() repositories.CreateDeploymentMessage { @@ -23,3 +25,12 @@ func (c *DeploymentCreate) ToMessage() repositories.CreateDeploymentMessage { DropletGUID: c.Droplet.Guid, } } + +type DeploymentRelationships struct { + App *Relationship `json:"app"` +} + +func (r DeploymentRelationships) Validate() error { + return validation.ValidateStruct(&r, + validation.Field(&r.App, validation.NotNil)) +} diff --git a/api/payloads/deployment_test.go b/api/payloads/deployment_test.go index 300cd2146..633720166 100644 --- a/api/payloads/deployment_test.go +++ b/api/payloads/deployment_test.go @@ -63,7 +63,7 @@ var _ = Describe("DeploymentCreate", func() { }) It("says relationships is required", func() { - expectUnprocessableEntityError(validatorErr, "Relationships is a required field") + expectUnprocessableEntityError(validatorErr, "relationships is required") }) }) @@ -73,7 +73,7 @@ var _ = Describe("DeploymentCreate", func() { }) It("says app is required", func() { - expectUnprocessableEntityError(validatorErr, "App is a required field") + expectUnprocessableEntityError(validatorErr, "app is required") }) }) @@ -83,7 +83,7 @@ var _ = Describe("DeploymentCreate", func() { }) It("says app guid is required", func() { - expectUnprocessableEntityError(validatorErr, "GUID is a required field") + expectUnprocessableEntityError(validatorErr, "guid cannot be blank") }) }) }) diff --git a/api/payloads/lifecycle_test.go b/api/payloads/lifecycle_test.go index bd967aa44..4e7666a74 100644 --- a/api/payloads/lifecycle_test.go +++ b/api/payloads/lifecycle_test.go @@ -10,15 +10,14 @@ import ( var _ = Describe("Lifecycle", func() { var ( - decoderValidator *handlers.DecoderValidator + decoderValidator handlers.DecoderValidator payload payloads.Lifecycle decodedPayload *payloads.Lifecycle validatorErr error ) BeforeEach(func() { - decoderValidator, validatorErr = handlers.NewDefaultDecoderValidator() - Expect(validatorErr).NotTo(HaveOccurred()) + decoderValidator = handlers.NewDefaultDecoderValidator() payload = payloads.Lifecycle{ Type: "buildpack", diff --git a/api/payloads/metadata.go b/api/payloads/metadata.go index da7345f96..e5151ec9c 100644 --- a/api/payloads/metadata.go +++ b/api/payloads/metadata.go @@ -22,8 +22,8 @@ func (m BuildMetadata) Validate() error { } type Metadata struct { - Annotations map[string]string `json:"annotations" yaml:"annotations" validate:"metadatavalidator"` - Labels map[string]string `json:"labels" yaml:"labels" validate:"metadatavalidator"` + Annotations map[string]string `json:"annotations" yaml:"annotations"` + Labels map[string]string `json:"labels" yaml:"labels"` } func (m Metadata) Validate() error { @@ -34,8 +34,8 @@ func (m Metadata) Validate() error { } type MetadataPatch struct { - Annotations map[string]*string `json:"annotations" validate:"metadatavalidator"` - Labels map[string]*string `json:"labels" validate:"metadatavalidator"` + Annotations map[string]*string `json:"annotations"` + Labels map[string]*string `json:"labels"` } func (p MetadataPatch) Validate() error { diff --git a/api/payloads/package.go b/api/payloads/package.go index e4eaf353f..180036218 100644 --- a/api/payloads/package.go +++ b/api/payloads/package.go @@ -10,31 +10,50 @@ import ( ) type PackageCreate struct { - Type string `json:"type" validate:"required,oneof='bits'"` - Relationships *PackageRelationships `json:"relationships" validate:"required"` + Type string `json:"type"` + Relationships *PackageRelationships `json:"relationships"` Metadata Metadata `json:"metadata"` } -type PackageRelationships struct { - App *Relationship `json:"app" validate:"required"` +func (c PackageCreate) Validate() error { + return jellidation.ValidateStruct(&c, + jellidation.Field(&c.Type, validation.OneOf("bits"), jellidation.Required), + jellidation.Field(&c.Relationships, jellidation.NotNil), + jellidation.Field(&c.Metadata), + ) } -func (m PackageCreate) ToMessage(record repositories.AppRecord) repositories.CreatePackageMessage { +func (c PackageCreate) ToMessage(record repositories.AppRecord) repositories.CreatePackageMessage { return repositories.CreatePackageMessage{ - Type: m.Type, + Type: c.Type, AppGUID: record.GUID, SpaceGUID: record.SpaceGUID, Metadata: repositories.Metadata{ - Annotations: m.Metadata.Annotations, - Labels: m.Metadata.Labels, + Annotations: c.Metadata.Annotations, + Labels: c.Metadata.Labels, }, } } +type PackageRelationships struct { + App *Relationship `json:"app"` +} + +func (r PackageRelationships) Validate() error { + return jellidation.ValidateStruct(&r, + jellidation.Field(&r.App, jellidation.NotNil)) +} + type PackageUpdate struct { Metadata MetadataPatch `json:"metadata"` } +func (p PackageUpdate) Validate() error { + return jellidation.ValidateStruct(&p, + jellidation.Field(&p.Metadata), + ) +} + func (u *PackageUpdate) ToMessage(packageGUID string) repositories.UpdatePackageMessage { return repositories.UpdatePackageMessage{ GUID: packageGUID, diff --git a/api/payloads/package_test.go b/api/payloads/package_test.go index 4610f1b79..57247002a 100644 --- a/api/payloads/package_test.go +++ b/api/payloads/package_test.go @@ -52,7 +52,7 @@ var _ = Describe("PackageCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Type is a required field") + expectUnprocessableEntityError(validatorErr, "type cannot be blank") }) }) @@ -62,7 +62,7 @@ var _ = Describe("PackageCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Type must be one of ['bits']") + expectUnprocessableEntityError(validatorErr, "type value must be one of: bits") }) }) @@ -72,7 +72,7 @@ var _ = Describe("PackageCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Relationships is a required field") + expectUnprocessableEntityError(validatorErr, "relationships is required") }) }) @@ -82,31 +82,21 @@ var _ = Describe("PackageCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "App is a required field") + expectUnprocessableEntityError(validatorErr, "app is required") }) }) - When("relationships.app.data is not set", func() { - BeforeEach(func() { - createPayload.Relationships.App.Data = nil - }) - - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Data is a required field") - }) - }) - - When("relationships.app.data.guid is not set", func() { + When("relationships.app is invalid", func() { BeforeEach(func() { createPayload.Relationships.App.Data.GUID = "" }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "GUID is a required field") + expectUnprocessableEntityError(validatorErr, "guid cannot be blank") }) }) - When("metadata.labels contains an invalid key", func() { + When("metadata is invalid", func() { BeforeEach(func() { createPayload.Metadata = payloads.Metadata{ Labels: map[string]string{ @@ -116,35 +106,16 @@ var _ = Describe("PackageCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") - }) - }) - - When("metadata.annotations contains an invalid key", func() { - BeforeEach(func() { - createPayload.Metadata = payloads.Metadata{ - Annotations: map[string]string{ - "foo.cloudfoundry.org/bar": "jim", - }, - } - }) - - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") }) }) }) var _ = Describe("PackageUpdate", func() { - var ( - updatePayload payloads.PackageUpdate - packageUpdate *payloads.PackageUpdate - validatorErr error - ) + var payload payloads.PackageUpdate BeforeEach(func() { - packageUpdate = new(payloads.PackageUpdate) - updatePayload = payloads.PackageUpdate{ + payload = payloads.PackageUpdate{ Metadata: payloads.MetadataPatch{ Labels: map[string]*string{ "foo": tools.PtrTo("bar"), @@ -157,46 +128,40 @@ var _ = Describe("PackageUpdate", func() { } }) - JustBeforeEach(func() { - validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(updatePayload), packageUpdate) - }) - - It("succeeds", func() { - Expect(validatorErr).NotTo(HaveOccurred()) - Expect(packageUpdate).To(gstruct.PointTo(Equal(updatePayload))) - }) + Describe("Validation", func() { + var ( + decodedPayload *payloads.PackageUpdate + validatorErr error + ) - When("metadata.labels contains an invalid key", func() { - BeforeEach(func() { - updatePayload.Metadata = payloads.MetadataPatch{ - Labels: map[string]*string{ - "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), - }, - } + JustBeforeEach(func() { + decodedPayload = new(payloads.PackageUpdate) + validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(payload), decodedPayload) }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + It("succeeds", func() { + Expect(validatorErr).NotTo(HaveOccurred()) + Expect(decodedPayload).To(gstruct.PointTo(Equal(payload))) }) - }) - When("metadata.annotations contains an invalid key", func() { - BeforeEach(func() { - updatePayload.Metadata = payloads.MetadataPatch{ - Annotations: map[string]*string{ - "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), - }, - } - }) + When("metadata is invalid", func() { + BeforeEach(func() { + payload.Metadata = payloads.MetadataPatch{ + Labels: map[string]*string{ + "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), + }, + } + }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + It("returns an appropriate error", func() { + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") + }) }) }) - Context("toMessage", func() { + Describe("ToMessage", func() { It("converts to repo message correctly", func() { - msg := packageUpdate.ToMessage("foo") + msg := payload.ToMessage("foo") Expect(msg.MetadataPatch.Labels).To(Equal(map[string]*string{ "foo": tools.PtrTo("bar"), "bar": nil, diff --git a/api/payloads/payloads_suite_test.go b/api/payloads/payloads_suite_test.go index 468eb964c..5775a3113 100644 --- a/api/payloads/payloads_suite_test.go +++ b/api/payloads/payloads_suite_test.go @@ -13,11 +13,11 @@ import ( . "github.com/onsi/gomega" ) -var validator *handlers.DecoderValidator +var validator handlers.DecoderValidator var _ = BeforeEach(func() { var err error - validator, err = handlers.NewDefaultDecoderValidator() + validator = handlers.NewDefaultDecoderValidator() Expect(err).NotTo(HaveOccurred()) }) diff --git a/api/payloads/process_test.go b/api/payloads/process_test.go index ea4e54250..0a4b0fba3 100644 --- a/api/payloads/process_test.go +++ b/api/payloads/process_test.go @@ -29,13 +29,13 @@ var _ = Describe("ProcessList", func() { var _ = Describe("Process payload validation", func() { var ( - decoderValidator *handlers.DecoderValidator + decoderValidator handlers.DecoderValidator validatorErr error ) BeforeEach(func() { var err error - decoderValidator, err = handlers.NewDefaultDecoderValidator() + decoderValidator = handlers.NewDefaultDecoderValidator() Expect(err).NotTo(HaveOccurred()) }) diff --git a/api/payloads/relationship.go b/api/payloads/relationship.go index d1ce811b3..32041911f 100644 --- a/api/payloads/relationship.go +++ b/api/payloads/relationship.go @@ -5,7 +5,7 @@ import ( ) type Relationship struct { - Data *RelationshipData `json:"data" validate:"required"` + Data *RelationshipData `json:"data"` } func (r Relationship) Validate() error { @@ -15,7 +15,7 @@ func (r Relationship) Validate() error { } type RelationshipData struct { - GUID string `json:"guid" validate:"required"` + GUID string `json:"guid"` } func (r RelationshipData) Validate() error { diff --git a/api/payloads/role.go b/api/payloads/role.go index 35553bfca..91b7b53fc 100644 --- a/api/payloads/role.go +++ b/api/payloads/role.go @@ -92,7 +92,7 @@ func (r RoleRelationships) ValidateWithContext(ctx context.Context) error { jellidation.Field(&r.User, validation.StrictlyRequired), jellidation.Field(&r.Space, - jellidation.When(r.Organization != nil && r.Organization.Data != nil && r.Organization.Data.GUID != "", + jellidation.When(r.Organization != nil, jellidation.Nil.Error("cannot pass both 'organization' and 'space' in a create role request"))), jellidation.Field(&r.Space, @@ -100,7 +100,6 @@ func (r RoleRelationships) ValidateWithContext(ctx context.Context) error { roleType == RoleSpaceAuditor || roleType == RoleSpaceDeveloper || roleType == RoleSpaceManager || roleType == RoleSpaceSupporter, jellidation.NotNil, - validation.StrictlyRequired, )), jellidation.Field(&r.Organization, @@ -108,7 +107,6 @@ func (r RoleRelationships) ValidateWithContext(ctx context.Context) error { roleType == RoleOrganizationAuditor || roleType == RoleOrganizationBillingManager || roleType == RoleOrganizationManager || roleType == RoleOrganizationUser, jellidation.NotNil, - validation.StrictlyRequired, )), ) } diff --git a/api/payloads/service_binding.go b/api/payloads/service_binding.go index 9a4356941..8e59cb0f2 100644 --- a/api/payloads/service_binding.go +++ b/api/payloads/service_binding.go @@ -10,8 +10,8 @@ import ( ) type ServiceBindingCreate struct { - Relationships *ServiceBindingRelationships `json:"relationships" validate:"required"` - Type string `json:"type" validate:"oneof=app"` + Relationships *ServiceBindingRelationships `json:"relationships"` + Type string `json:"type"` Name *string `json:"name"` } diff --git a/api/payloads/service_instance.go b/api/payloads/service_instance.go index 0001ef12b..ac189c167 100644 --- a/api/payloads/service_instance.go +++ b/api/payloads/service_instance.go @@ -2,23 +2,52 @@ package payloads import ( "encoding/json" + "errors" + "fmt" "net/url" "code.cloudfoundry.org/korifi/api/payloads/parse" + "code.cloudfoundry.org/korifi/api/payloads/validation" "code.cloudfoundry.org/korifi/api/repositories" + jellidation "github.com/jellydator/validation" ) type ServiceInstanceCreate struct { - Name string `json:"name" validate:"required"` - Type string `json:"type" validate:"required,oneof=user-provided"` - Tags []string `json:"tags" validate:"serviceinstancetaglength"` - Credentials map[string]string `json:"credentials"` - Relationships ServiceInstanceRelationships `json:"relationships" validate:"required"` - Metadata Metadata `json:"metadata"` + Name string `json:"name"` + Type string `json:"type"` + Tags []string `json:"tags"` + Credentials map[string]string `json:"credentials"` + Relationships *ServiceInstanceRelationships `json:"relationships"` + Metadata Metadata `json:"metadata"` } -type ServiceInstanceRelationships struct { - Space Relationship `json:"space" validate:"required"` +const maxTagsLength = 2048 + +func validateTagLength(tags any) error { + tagSlice, ok := tags.([]string) + if !ok { + return errors.New("wrong input") + } + + l := 0 + for _, t := range tagSlice { + l += len(t) + if l >= maxTagsLength { + return fmt.Errorf("combined length of tags cannot exceed %d", maxTagsLength) + } + } + + return nil +} + +func (c ServiceInstanceCreate) Validate() error { + return jellidation.ValidateStruct(&c, + jellidation.Field(&c.Name, jellidation.Required), + jellidation.Field(&c.Type, jellidation.Required, validation.OneOf("user-provided")), + jellidation.Field(&c.Tags, jellidation.By(validateTagLength)), + jellidation.Field(&c.Relationships, jellidation.NotNil), + jellidation.Field(&c.Metadata), + ) } func (p ServiceInstanceCreate) ToServiceInstanceCreateMessage() repositories.CreateServiceInstanceMessage { @@ -33,6 +62,16 @@ func (p ServiceInstanceCreate) ToServiceInstanceCreateMessage() repositories.Cre } } +type ServiceInstanceRelationships struct { + Space *Relationship `json:"space"` +} + +func (r ServiceInstanceRelationships) Validate() error { + return jellidation.ValidateStruct(&r, + jellidation.Field(&r.Space, jellidation.NotNil), + ) +} + type ServiceInstancePatch struct { Name *string `json:"name,omitempty"` Tags *[]string `json:"tags,omitempty"` @@ -40,6 +79,12 @@ type ServiceInstancePatch struct { Metadata MetadataPatch `json:"metadata"` } +func (p ServiceInstancePatch) Validate() error { + return jellidation.ValidateStruct(&p, + jellidation.Field(&p.Metadata), + ) +} + func (p ServiceInstancePatch) ToServiceInstancePatchMessage(spaceGUID, appGUID string) repositories.PatchServiceInstanceMessage { return repositories.PatchServiceInstanceMessage{ SpaceGUID: spaceGUID, diff --git a/api/payloads/service_instance_test.go b/api/payloads/service_instance_test.go index 6181cfa7c..80ba25b0b 100644 --- a/api/payloads/service_instance_test.go +++ b/api/payloads/service_instance_test.go @@ -45,8 +45,8 @@ var _ = Describe("ServiceInstanceCreate", func() { "username": "bob", "password": "float", }, - Relationships: payloads.ServiceInstanceRelationships{ - Space: payloads.Relationship{ + Relationships: &payloads.ServiceInstanceRelationships{ + Space: &payloads.Relationship{ Data: &payloads.RelationshipData{ GUID: "space-guid", }, @@ -74,7 +74,7 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Name is a required field") + expectUnprocessableEntityError(validatorErr, "name cannot be blank") }) }) @@ -84,7 +84,7 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Type is a required field") + expectUnprocessableEntityError(validatorErr, "type cannot be blank") }) }) @@ -94,7 +94,7 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Type must be one of [user-provided]") + expectUnprocessableEntityError(validatorErr, "type value must be one of: user-provided") }) }) @@ -104,7 +104,7 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Data is a required field") + expectUnprocessableEntityError(validatorErr, "data is required") }) }) @@ -115,11 +115,11 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Key: 'ServiceInstanceCreate.Tags' Error:Field validation for 'Tags' failed on the 'serviceinstancetaglength' tag") + expectUnprocessableEntityError(validatorErr, "combined length of tags cannot exceed") }) }) - When("metadata.labels contains an invalid key", func() { + When("metadata is invalid", func() { BeforeEach(func() { createPayload.Metadata = payloads.Metadata{ Labels: map[string]string{ @@ -129,21 +129,7 @@ var _ = Describe("ServiceInstanceCreate", func() { }) It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") - }) - }) - - When("metadata.annotations contains an invalid key", func() { - BeforeEach(func() { - createPayload.Metadata = payloads.Metadata{ - Annotations: map[string]string{ - "foo.cloudfoundry.org/bar": "jim", - }, - } - }) - - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") }) }) @@ -262,6 +248,16 @@ var _ = Describe("ServiceInstancePatch", func() { }) }) + When("metadata is invalid", func() { + BeforeEach(func() { + patchPayload.Metadata.Labels["foo.cloudfoundry.org/bar"] = tools.PtrTo("baz") + }) + + It("returns an appropriate error", func() { + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") + }) + }) + Context("ToServiceInstancePatchMessage", func() { It("converts to repo message correctly", func() { msg := serviceInstancePatch.ToServiceInstancePatchMessage("space-guid", "app-guid") diff --git a/api/payloads/space.go b/api/payloads/space.go index 2d420c5d3..06cc29a41 100644 --- a/api/payloads/space.go +++ b/api/payloads/space.go @@ -2,16 +2,21 @@ package payloads import ( "code.cloudfoundry.org/korifi/api/repositories" + "github.com/jellydator/validation" ) type SpaceCreate struct { - Name string `json:"name" validate:"required"` - Relationships SpaceRelationships `json:"relationships" validate:"required"` - Metadata Metadata `json:"metadata"` + Name string `json:"name"` + Relationships *SpaceRelationships `json:"relationships"` + Metadata Metadata `json:"metadata"` } -type SpaceRelationships struct { - Org Relationship `json:"organization" validate:"required"` +func (c SpaceCreate) Validate() error { + return validation.ValidateStruct(&c, + validation.Field(&c.Name, validation.Required), + validation.Field(&c.Relationships, validation.NotNil), + validation.Field(&c.Metadata), + ) } func (p SpaceCreate) ToMessage() repositories.CreateSpaceMessage { @@ -21,10 +26,26 @@ func (p SpaceCreate) ToMessage() repositories.CreateSpaceMessage { } } +type SpaceRelationships struct { + Org *Relationship `json:"organization"` +} + +func (r SpaceRelationships) Validate() error { + return validation.ValidateStruct(&r, + validation.Field(&r.Org, validation.NotNil), + ) +} + type SpacePatch struct { Metadata MetadataPatch `json:"metadata"` } +func (p SpacePatch) Validate() error { + return validation.ValidateStruct(&p, + validation.Field(&p.Metadata), + ) +} + func (p SpacePatch) ToMessage(spaceGUID, orgGUID string) repositories.PatchSpaceMetadataMessage { return repositories.PatchSpaceMetadataMessage{ GUID: spaceGUID, diff --git a/api/payloads/space_test.go b/api/payloads/space_test.go new file mode 100644 index 000000000..e4b9b3fe0 --- /dev/null +++ b/api/payloads/space_test.go @@ -0,0 +1,136 @@ +package payloads_test + +import ( + "code.cloudfoundry.org/korifi/api/payloads" + "code.cloudfoundry.org/korifi/tools" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gstruct" +) + +var _ = Describe("Space", func() { + Describe("SpaceCreate", func() { + var ( + payload payloads.SpaceCreate + decodedPayload *payloads.SpaceCreate + validatorErr error + ) + + BeforeEach(func() { + payload = payloads.SpaceCreate{ + Name: "my-space", + Relationships: &payloads.SpaceRelationships{ + Org: &payloads.Relationship{ + Data: &payloads.RelationshipData{ + GUID: "org-guid", + }, + }, + }, + Metadata: payloads.Metadata{ + Annotations: map[string]string{ + "foo": "bar", + }, + Labels: map[string]string{ + "bob": "alice", + }, + }, + } + + decodedPayload = new(payloads.SpaceCreate) + }) + + JustBeforeEach(func() { + validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(payload), decodedPayload) + }) + + It("succeeds", func() { + Expect(validatorErr).NotTo(HaveOccurred()) + Expect(decodedPayload).To(gstruct.PointTo(Equal(payload))) + }) + + When("the space name is missing", func() { + BeforeEach(func() { + payload.Name = "" + }) + + It("returns a status 422 with appropriate error message json", func() { + expectUnprocessableEntityError(validatorErr, "name cannot be blank") + }) + }) + + When("relationships is not set", func() { + BeforeEach(func() { + payload.Relationships = nil + }) + + It("returns a status 422 with appropriate error message json", func() { + expectUnprocessableEntityError(validatorErr, "relationships is required") + }) + }) + + When("relationships.organization is not set", func() { + BeforeEach(func() { + payload.Relationships.Org = nil + }) + + It("returns a status 422 with appropriate error message json", func() { + expectUnprocessableEntityError(validatorErr, "relationships.organization is required") + }) + }) + + When("metadata is invalid", func() { + BeforeEach(func() { + payload.Metadata.Labels["foo.cloudfoundry.org/bar"] = "baz" + }) + + It("returns a status 422 with appropriate error message json", func() { + expectUnprocessableEntityError(validatorErr, "metadata.labels.foo.cloudfoundry.org/bar label/annotation key cannot use the cloudfoundry.org domain") + }) + }) + }) + + Describe("SpacePatch", func() { + var ( + payload payloads.SpacePatch + decodedPayload *payloads.SpacePatch + validatorErr error + ) + + BeforeEach(func() { + payload = payloads.SpacePatch{ + Metadata: payloads.MetadataPatch{ + Annotations: map[string]*string{ + "foo": tools.PtrTo("bar"), + }, + Labels: map[string]*string{ + "bob": tools.PtrTo("alice"), + }, + }, + } + + decodedPayload = new(payloads.SpacePatch) + }) + + JustBeforeEach(func() { + validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(payload), decodedPayload) + }) + + It("succeeds", func() { + Expect(validatorErr).NotTo(HaveOccurred()) + Expect(decodedPayload).To(gstruct.PointTo(Equal(payload))) + }) + + When("the metadata is invalid", func() { + BeforeEach(func() { + payload.Metadata.Labels["cloudfoundry.org/test"] = tools.PtrTo("production") + }) + + It("returns an unprocessable entity error", func() { + expectUnprocessableEntityError( + validatorErr, + "label/annotation key cannot use the cloudfoundry.org domain", + ) + }) + }) + }) +}) diff --git a/api/payloads/task.go b/api/payloads/task.go index 5da4ae805..58dc18bbc 100644 --- a/api/payloads/task.go +++ b/api/payloads/task.go @@ -6,13 +6,21 @@ import ( "strings" "code.cloudfoundry.org/korifi/api/repositories" + "github.com/jellydator/validation" ) type TaskCreate struct { - Command string `json:"command" validate:"required"` + Command string `json:"command"` Metadata Metadata `json:"metadata"` } +func (c TaskCreate) Validate() error { + return validation.ValidateStruct(&c, + validation.Field(&c.Command, validation.Required), + validation.Field(&c.Metadata), + ) +} + func (p TaskCreate) ToMessage(appRecord repositories.AppRecord) repositories.CreateTaskMessage { return repositories.CreateTaskMessage{ Command: p.Command, @@ -59,6 +67,12 @@ type TaskUpdate struct { Metadata MetadataPatch `json:"metadata"` } +func (u TaskUpdate) Validate() error { + return validation.ValidateStruct(&u, + validation.Field(&u.Metadata), + ) +} + func (u *TaskUpdate) ToMessage(taskGUID, spaceGUID string) repositories.PatchTaskMetadataMessage { return repositories.PatchTaskMetadataMessage{ TaskGUID: taskGUID, diff --git a/api/payloads/task_test.go b/api/payloads/task_test.go index b20050ec6..044912b99 100644 --- a/api/payloads/task_test.go +++ b/api/payloads/task_test.go @@ -38,15 +38,10 @@ var _ = Describe("TaskList", func() { }) var _ = Describe("TaskCreate", func() { - var ( - createPayload payloads.TaskCreate - taskCreate *payloads.TaskCreate - validatorErr error - ) + var payload payloads.TaskCreate BeforeEach(func() { - taskCreate = new(payloads.TaskCreate) - createPayload = payloads.TaskCreate{ + payload = payloads.TaskCreate{ Command: "sleep 9000", Metadata: payloads.Metadata{ Labels: map[string]string{ @@ -60,56 +55,50 @@ var _ = Describe("TaskCreate", func() { } }) - JustBeforeEach(func() { - validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(createPayload), taskCreate) - }) - - It("succeeds", func() { - Expect(validatorErr).NotTo(HaveOccurred()) - Expect(taskCreate).To(gstruct.PointTo(Equal(createPayload))) - }) + Describe("Validate", func() { + var ( + decodedPayload *payloads.TaskCreate + validatorErr error + ) - When("no command is set", func() { - BeforeEach(func() { - createPayload.Command = "" + JustBeforeEach(func() { + decodedPayload = new(payloads.TaskCreate) + validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(payload), decodedPayload) }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "Command is a required field") - }) - }) - - When("metadata.labels contains an invalid key", func() { - BeforeEach(func() { - createPayload.Metadata = payloads.Metadata{ - Labels: map[string]string{ - "foo.cloudfoundry.org/bar": "jim", - }, - } + It("succeeds", func() { + Expect(validatorErr).NotTo(HaveOccurred()) + Expect(decodedPayload).To(gstruct.PointTo(Equal(payload))) }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") - }) - }) + When("no command is set", func() { + BeforeEach(func() { + payload.Command = "" + }) - When("metadata.annotations contains an invalid key", func() { - BeforeEach(func() { - createPayload.Metadata = payloads.Metadata{ - Annotations: map[string]string{ - "foo.cloudfoundry.org/bar": "jim", - }, - } + It("returns an appropriate error", func() { + expectUnprocessableEntityError(validatorErr, "command cannot be blank") + }) }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + When("metadata is invalid", func() { + BeforeEach(func() { + payload.Metadata = payloads.Metadata{ + Labels: map[string]string{ + "foo.cloudfoundry.org/bar": "jim", + }, + } + }) + + It("returns an appropriate error", func() { + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") + }) }) }) - Context("ToMessage()", func() { + Describe("ToMessage()", func() { It("converts to repo message correctly", func() { - msg := taskCreate.ToMessage(repositories.AppRecord{GUID: "appGUID", SpaceGUID: "spaceGUID"}) + msg := payload.ToMessage(repositories.AppRecord{GUID: "appGUID", SpaceGUID: "spaceGUID"}) Expect(msg.AppGUID).To(Equal("appGUID")) Expect(msg.SpaceGUID).To(Equal("spaceGUID")) Expect(msg.Metadata.Labels).To(Equal(map[string]string{ @@ -124,15 +113,10 @@ var _ = Describe("TaskCreate", func() { }) var _ = Describe("TaskUpdate", func() { - var ( - updatePayload payloads.TaskUpdate - taskUpdate *payloads.TaskUpdate - validatorErr error - ) + var payload payloads.TaskUpdate BeforeEach(func() { - taskUpdate = new(payloads.TaskUpdate) - updatePayload = payloads.TaskUpdate{ + payload = payloads.TaskUpdate{ Metadata: payloads.MetadataPatch{ Labels: map[string]*string{ "foo": tools.PtrTo("bar"), @@ -145,46 +129,40 @@ var _ = Describe("TaskUpdate", func() { } }) - JustBeforeEach(func() { - validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(updatePayload), taskUpdate) - }) - - It("succeeds", func() { - Expect(validatorErr).NotTo(HaveOccurred()) - Expect(taskUpdate).To(gstruct.PointTo(Equal(updatePayload))) - }) - - When("metadata.labels contains an invalid key", func() { - BeforeEach(func() { - updatePayload.Metadata = payloads.MetadataPatch{ - Labels: map[string]*string{ - "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), - }, - } - }) + Describe("Validate", func() { + var ( + decodedPayload *payloads.TaskUpdate + validatorErr error + ) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + JustBeforeEach(func() { + decodedPayload = new(payloads.TaskUpdate) + validatorErr = validator.DecodeAndValidateJSONPayload(createRequest(payload), decodedPayload) }) - }) - When("metadata.annotations contains an invalid key", func() { - BeforeEach(func() { - updatePayload.Metadata = payloads.MetadataPatch{ - Annotations: map[string]*string{ - "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), - }, - } + It("succeeds", func() { + Expect(validatorErr).NotTo(HaveOccurred()) + Expect(decodedPayload).To(gstruct.PointTo(Equal(payload))) }) - It("returns an appropriate error", func() { - expectUnprocessableEntityError(validatorErr, "cannot begin with \"cloudfoundry.org\"") + When("metadata is invalid", func() { + BeforeEach(func() { + payload.Metadata = payloads.MetadataPatch{ + Labels: map[string]*string{ + "foo.cloudfoundry.org/bar": tools.PtrTo("jim"), + }, + } + }) + + It("returns an appropriate error", func() { + expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain") + }) }) }) - Context("toMessage()", func() { + Describe("ToMessage()", func() { It("converts to repo message correctly", func() { - msg := taskUpdate.ToMessage("taskGUID", "spaceGUID") + msg := payload.ToMessage("taskGUID", "spaceGUID") Expect(msg.TaskGUID).To(Equal("taskGUID")) Expect(msg.SpaceGUID).To(Equal("spaceGUID")) Expect(msg.MetadataPatch.Labels).To(Equal(map[string]*string{ diff --git a/api/repositories/task_repository_test.go b/api/repositories/task_repository_test.go index f4203fa26..d4d5cf250 100644 --- a/api/repositories/task_repository_test.go +++ b/api/repositories/task_repository_test.go @@ -6,13 +6,13 @@ import ( "sync" "time" - "code.cloudfoundry.org/korifi/tools/k8s" "code.cloudfoundry.org/korifi/api/authorization" apierrors "code.cloudfoundry.org/korifi/api/errors" "code.cloudfoundry.org/korifi/api/repositories" "code.cloudfoundry.org/korifi/api/repositories/conditions" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" "code.cloudfoundry.org/korifi/tests/matchers" + "code.cloudfoundry.org/korifi/tools/k8s" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/go.mod b/go.mod index 5f8f31859..ea92f124a 100644 --- a/go.mod +++ b/go.mod @@ -16,9 +16,6 @@ require ( github.com/distribution/distribution/v3 v3.0.0-20230223072852-e5d5810851d1 github.com/go-chi/chi v4.1.2+incompatible github.com/go-logr/logr v1.2.4 - github.com/go-playground/locales v0.14.1 - github.com/go-playground/universal-translator v0.18.1 - github.com/go-playground/validator/v10 v10.14.1 github.com/go-resty/resty/v2 v2.7.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-containerregistry v0.15.2 @@ -46,10 +43,7 @@ require ( sigs.k8s.io/controller-tools v0.11.4 ) -require ( - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/vmware-labs/reconciler-runtime v0.12.0 // indirect -) +require github.com/vmware-labs/reconciler-runtime v0.12.0 // indirect require ( cloud.google.com/go/compute v1.19.1 // indirect @@ -124,7 +118,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.5 // indirect - github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.18 // indirect diff --git a/go.sum b/go.sum index 97429c269..ddd8b39ef 100644 --- a/go.sum +++ b/go.sum @@ -187,8 +187,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= @@ -209,13 +207,6 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -332,8 +323,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/loggregator/go-bindata v0.0.0-20190422223605-5f11cfb2d7d9/go.mod h1:PvsJfK9t/8OdGvSanpYlwJ1EPoJ/hwT3c52txAzqooY= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -453,7 +442,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=