From be0c97339875ebf083a06bf1a5686f763024e84d Mon Sep 17 00:00:00 2001 From: samrusani <14844597+samrusani@users.noreply.github.com> Date: Thu, 20 Aug 2026 01:02:08 +0200 Subject: [PATCH] fix(validators): reject incomplete repository metadata --- internal/api/handlers/v0/publish_test.go | 19 ++++++++++ internal/validators/validators.go | 28 +++++++++++++- internal/validators/validators_test.go | 47 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/internal/api/handlers/v0/publish_test.go b/internal/api/handlers/v0/publish_test.go index 94502bead..7c9dee879 100644 --- a/internal/api/handlers/v0/publish_test.go +++ b/internal/api/handlers/v0/publish_test.go @@ -102,6 +102,25 @@ func TestPublishEndpoint(t *testing.T) { }, expectedStatus: http.StatusOK, }, + { + name: "reject publish with empty repository", + requestBody: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "example/test-server", + Description: "A test server", + Repository: &model.Repository{}, + Version: "1.0.0", + }, + tokenClaims: &auth.JWTClaims{ + AuthMethod: auth.MethodNone, + Permissions: []auth.Permission{ + {Action: auth.PermissionActionPublish, ResourcePattern: "example/*"}, + }, + }, + setupRegistryService: func(_ service.RegistryService) {}, + expectedStatus: http.StatusUnprocessableEntity, + expectedError: "Failed to publish server, invalid schema: call /validate for details", + }, { name: "missing authorization header", requestBody: apiv0.ServerJSON{}, diff --git a/internal/validators/validators.go b/internal/validators/validators.go index 114ed3c71..f6d6e3452 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -119,8 +119,32 @@ func ValidateServerJSON(serverJSON *apiv0.ServerJSON, opts ValidationOptions) *V func validateRepository(ctx *ValidationContext, obj *model.Repository) *ValidationResult { result := &ValidationResult{Valid: true, Issues: []ValidationIssue{}} - // Skip validation if repository is nil or empty (optional field) - if obj == nil || (obj.URL == "" && obj.Source == "") { + // The repository field is optional, but its required fields must be present + // when the repository object itself is provided. + if obj == nil { + return result + } + if obj.URL == "" { + issue := NewValidationIssue( + ValidationIssueTypeSemantic, + ctx.Field("url").String(), + "repository url is required when repository is provided", + ValidationIssueSeverityError, + "repository-url-required", + ) + result.AddIssue(issue) + } + if obj.Source == "" { + issue := NewValidationIssue( + ValidationIssueTypeSemantic, + ctx.Field("source").String(), + "repository source is required when repository is provided", + ValidationIssueSeverityError, + "repository-source-required", + ) + result.AddIssue(issue) + } + if !result.Valid { return result } diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index c3e5585bf..3da4434dd 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -249,6 +249,16 @@ func TestValidate(t *testing.T) { }, expectedError: validators.ErrMultipleSlashesInServerName.Error(), }, + { + name: "server without repository", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Version: "1.0.0", + }, + expectedError: "", + }, { name: "valid server detail with all fields", serverDetail: apiv0.ServerJSON{ @@ -281,6 +291,43 @@ func TestValidate(t *testing.T) { }, expectedError: "", }, + { + name: "server with empty repository", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{}, + Version: "1.0.0", + }, + expectedError: "repository url is required when repository is provided", + }, + { + name: "server with repository missing URL", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + Source: "github", + }, + Version: "1.0.0", + }, + expectedError: "repository url is required when repository is provided", + }, + { + name: "server with repository missing source", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/test-server", + Description: "A test server", + Repository: &model.Repository{ + URL: "https://github.com/owner/repo", + }, + Version: "1.0.0", + }, + expectedError: "repository source is required when repository is provided", + }, { name: "server with invalid repository source", serverDetail: apiv0.ServerJSON{