fix: Named arguments should identify the argument name (#321)
Originally by @dmartinol. This branch is an attempt by @domdomegg to fix the merge conflicts... --- ## Motivation and Context Closes #145 Add comprehensive validation for named arguments in MCP server packages to ensure argument names, values, and defaults follow proper format conventions. This prevents common issues where argument names contain embedded values or descriptions, and where values/defaults incorrectly start with the argument name itself. Key changes: - Add ArgumentValidator with validation rules for named argument format - Enhance PackageValidator to validate runtime and package arguments - Update import process to log warnings for invalid servers instead of failing - Fix 396 servers in seed.json with malformed named arguments - Extended `validate-examples` tool to apply the object validator (it was completely missed before) - Add comprehensive test coverage for all validation scenarios Examples of fixes applied: - "--directory <path>" → "--directory" with value "<path>" - "--with-editable $REPO" → "--with-editable" with value "$REPO" - "recipient@example.com" → "--recipient" (proper argument naming) All servers now pass validation during both API publishing and data import. 🤖 Generated with [Claude Code](https://claude.ai/code) ## How Has This Been Tested? With ad-hoc unit tests. Manual integration tests of built artifact and the seed data. ## Breaking Changes New registrations need to follow the argument name conventions, otherwise the import would fail for the inconsistent servers. ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist - [x] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [x] My code follows the repository's style guidelines - [x] New and existing tests pass locally - [x] I have added appropriate error handling - [ ] I have added or updated documentation as needed --------- Signed-off-by: Daniele Martinoli <dmartino@redhat.com> Co-authored-by: Daniele Martinoli <dmartino@redhat.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8afe162f6f
commit
edf3929504
7 changed files with 1099 additions and 748 deletions
1392
data/seed.json
1392
data/seed.json
File diff suppressed because it is too large
Load diff
|
|
@ -5,10 +5,12 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/internal/validators"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
)
|
||||
|
||||
|
|
@ -48,14 +50,37 @@ func ReadSeedFile(ctx context.Context, path string) ([]*apiv0.ServerRecord, erro
|
|||
return []*apiv0.ServerRecord{}, nil
|
||||
}
|
||||
|
||||
// Convert ServerResponse to ServerRecord
|
||||
var records []*apiv0.ServerRecord
|
||||
// Validate servers and collect warnings instead of failing the whole batch
|
||||
var validRecords []*apiv0.ServerRecord
|
||||
var invalidServers []string
|
||||
var validationFailures []string
|
||||
|
||||
for _, response := range serverResponses {
|
||||
if err := validators.ValidateServerJSON(&response.Server); err != nil {
|
||||
// Log warning and track invalid server instead of failing
|
||||
invalidServers = append(invalidServers, response.Server.Name)
|
||||
validationFailures = append(validationFailures, fmt.Sprintf("Server '%s': %v", response.Server.Name, err))
|
||||
log.Printf("Warning: Skipping invalid server '%s': %v", response.Server.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert valid ServerResponse to ServerRecord
|
||||
record := convertServerResponseToRecord(response)
|
||||
records = append(records, record)
|
||||
validRecords = append(validRecords, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
// Print summary of validation results
|
||||
if len(invalidServers) > 0 {
|
||||
log.Printf("Import summary: %d valid servers imported, %d invalid servers skipped", len(validRecords), len(invalidServers))
|
||||
log.Printf("Invalid servers: %v", invalidServers)
|
||||
for _, failure := range validationFailures {
|
||||
log.Printf(" - %s", failure)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Import summary: All %d servers imported successfully", len(validRecords))
|
||||
}
|
||||
|
||||
return validRecords, nil
|
||||
}
|
||||
|
||||
func fetchFromHTTP(ctx context.Context, url string) ([]byte, error) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestReadSeedFile_LocalFile(t *testing.T) {
|
|||
seedData := []apiv0.ServerRecord{
|
||||
{
|
||||
Server: model.ServerJSON{
|
||||
Name: "test-server-1",
|
||||
Name: "com.example/test-server-1",
|
||||
Description: "Test server 1",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/test/repo1",
|
||||
|
|
@ -31,6 +31,11 @@ func TestReadSeedFile_LocalFile(t *testing.T) {
|
|||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Remotes: []model.Remote{
|
||||
{
|
||||
URL: "https://example.com/remote",
|
||||
},
|
||||
},
|
||||
},
|
||||
XIOModelContextProtocolRegistry: apiv0.RegistryExtensions{
|
||||
ID: "test-id-1",
|
||||
|
|
@ -61,7 +66,7 @@ func TestReadSeedFile_LocalFile(t *testing.T) {
|
|||
result, err := database.ReadSeedFile(context.Background(), tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "test-server-1", result[0].Server.Name)
|
||||
assert.Equal(t, "com.example/test-server-1", result[0].Server.Name)
|
||||
}
|
||||
|
||||
func TestReadSeedFile_DirectHTTPURL(t *testing.T) {
|
||||
|
|
@ -69,8 +74,13 @@ func TestReadSeedFile_DirectHTTPURL(t *testing.T) {
|
|||
seedData := []apiv0.ServerRecord{
|
||||
{
|
||||
Server: model.ServerJSON{
|
||||
Name: "test-server-1",
|
||||
Name: "com.example/test-server-1",
|
||||
Description: "Test server 1",
|
||||
Remotes: []model.Remote{
|
||||
{
|
||||
URL: "https://example.com/remote",
|
||||
},
|
||||
},
|
||||
},
|
||||
XIOModelContextProtocolRegistry: apiv0.RegistryExtensions{
|
||||
ID: "test-id-1",
|
||||
|
|
@ -93,7 +103,7 @@ func TestReadSeedFile_DirectHTTPURL(t *testing.T) {
|
|||
result, err := database.ReadSeedFile(context.Background(), server.URL+"/seed.json")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, "test-server-1", result[0].Server.Name)
|
||||
assert.Equal(t, "com.example/test-server-1", result[0].Server.Name)
|
||||
}
|
||||
|
||||
func TestReadSeedFile_RegistryURL(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ var (
|
|||
|
||||
// Remote validation errors
|
||||
ErrInvalidRemoteURL = errors.New("invalid remote URL")
|
||||
|
||||
// Argument validation errors
|
||||
ErrNamedArgumentNameRequired = errors.New("named argument name is required")
|
||||
ErrInvalidNamedArgumentName = errors.New("invalid named argument name format")
|
||||
ErrArgumentValueStartsWithName = errors.New("argument value cannot start with the argument name")
|
||||
ErrArgumentDefaultStartsWithName = errors.New("argument default cannot start with the argument name")
|
||||
)
|
||||
|
||||
// RepositorySource represents valid repository sources
|
||||
|
|
|
|||
|
|
@ -71,6 +71,66 @@ func validatePackageField(obj *model.Package) error {
|
|||
return ErrPackageNameHasSpaces
|
||||
}
|
||||
|
||||
// Validate runtime arguments
|
||||
for _, arg := range obj.RuntimeArguments {
|
||||
if err := validateArgument(&arg); err != nil {
|
||||
return fmt.Errorf("invalid runtime argument: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate package arguments
|
||||
for _, arg := range obj.PackageArguments {
|
||||
if err := validateArgument(&arg); err != nil {
|
||||
return fmt.Errorf("invalid package argument: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateArgument validates argument details
|
||||
func validateArgument(obj *model.Argument) error {
|
||||
if obj.Type == model.ArgumentTypeNamed {
|
||||
// Validate named argument name format
|
||||
if err := validateNamedArgumentName(obj.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate value and default don't start with the name
|
||||
if err := validateArgumentValueFields(obj.Name, obj.Value, obj.Default); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNamedArgumentName(name string) error {
|
||||
// Check if name is required for named arguments
|
||||
if name == "" {
|
||||
return ErrNamedArgumentNameRequired
|
||||
}
|
||||
|
||||
// Check for invalid characters that suggest embedded values or descriptions
|
||||
// Valid: "--directory", "--port", "-v", "config", "verbose"
|
||||
// Invalid: "--directory <absolute_path_to_adfin_mcp_folder>", "--port 8080"
|
||||
if strings.Contains(name, "<") || strings.Contains(name, ">") ||
|
||||
strings.Contains(name, " ") || strings.Contains(name, "$") {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidNamedArgumentName, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArgumentValueFields(name, value, defaultValue string) error {
|
||||
// Check if value starts with the argument name (using startsWith, not contains)
|
||||
if value != "" && strings.HasPrefix(value, name) {
|
||||
return fmt.Errorf("%w: value starts with argument name '%s': %s", ErrArgumentValueStartsWithName, name, value)
|
||||
}
|
||||
|
||||
if defaultValue != "" && strings.HasPrefix(defaultValue, name) {
|
||||
return fmt.Errorf("%w: default starts with argument name '%s': %s", ErrArgumentDefaultStartsWithName, name, defaultValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package validators_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/modelcontextprotocol/registry/internal/validators"
|
||||
|
|
@ -451,3 +452,225 @@ func TestValidate_ServerNameFormat(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateArgument_ValidNamedArguments(t *testing.T) {
|
||||
validCases := []model.Argument{
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "/path/to/dir"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--directory",
|
||||
},
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Default: "8080"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--port",
|
||||
},
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "true"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "-v",
|
||||
},
|
||||
{
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "-p",
|
||||
},
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "/etc/config.json"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "config",
|
||||
},
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Default: "false"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "verbose",
|
||||
},
|
||||
// No dash prefix requirement as per modification #1
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "json"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "output-format",
|
||||
},
|
||||
}
|
||||
|
||||
for _, arg := range validCases {
|
||||
t.Run("Valid_"+arg.Name, func(t *testing.T) {
|
||||
server := createValidServerWithArgument(arg)
|
||||
err := validators.ValidateServerJSON(&server)
|
||||
assert.NoError(t, err, "Expected valid argument %+v", arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateArgument_ValidPositionalArguments(t *testing.T) {
|
||||
positionalCases := []model.Argument{
|
||||
{Type: model.ArgumentTypePositional, Name: "anything with spaces"},
|
||||
{Type: model.ArgumentTypePositional, Name: "anything<with>brackets"},
|
||||
{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "--port 8080"}},
|
||||
Type: model.ArgumentTypePositional,
|
||||
}, // Can contain flags in value for positional
|
||||
}
|
||||
|
||||
for i, arg := range positionalCases {
|
||||
t.Run(fmt.Sprintf("ValidPositional_%d", i), func(t *testing.T) {
|
||||
server := createValidServerWithArgument(arg)
|
||||
err := validators.ValidateServerJSON(&server)
|
||||
assert.NoError(t, err, "Expected valid positional argument %+v", arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateArgument_InvalidNamedArgumentNames(t *testing.T) {
|
||||
invalidNameCases := []struct {
|
||||
name string
|
||||
arg model.Argument
|
||||
}{
|
||||
{"contains_description", model.Argument{Type: model.ArgumentTypeNamed, Name: "--directory <absolute_path_to_adfin_mcp_folder>"}},
|
||||
{"contains_value", model.Argument{Type: model.ArgumentTypeNamed, Name: "--port 8080"}},
|
||||
{"contains_dollar", model.Argument{Type: model.ArgumentTypeNamed, Name: "--config $CONFIG_FILE"}},
|
||||
{"contains_brackets", model.Argument{Type: model.ArgumentTypeNamed, Name: "--file <path>"}},
|
||||
{"empty_name", model.Argument{Type: model.ArgumentTypeNamed, Name: ""}},
|
||||
{"has_spaces", model.Argument{Type: model.ArgumentTypeNamed, Name: "name with spaces"}},
|
||||
}
|
||||
|
||||
for _, tc := range invalidNameCases {
|
||||
t.Run("Invalid_"+tc.name, func(t *testing.T) {
|
||||
server := createValidServerWithArgument(tc.arg)
|
||||
err := validators.ValidateServerJSON(&server)
|
||||
assert.Error(t, err, "Expected error for invalid named argument name: %+v", tc.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateArgument_InvalidValueFields(t *testing.T) {
|
||||
invalidValueCases := []struct {
|
||||
name string
|
||||
arg model.Argument
|
||||
}{
|
||||
{
|
||||
"value_starts_with_name",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "--port 8080"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--port",
|
||||
},
|
||||
},
|
||||
{
|
||||
"default_starts_with_name",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Default: "--config /etc/app.conf"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--config",
|
||||
},
|
||||
},
|
||||
{
|
||||
"value_starts_with_name_complex",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "--with-editable $REPOSITORY_DIRECTORY"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--with-editable",
|
||||
},
|
||||
},
|
||||
{
|
||||
"default_starts_with_name_complex",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Default: "--with-editable $REPOSITORY_DIRECTORY"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--with-editable",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range invalidValueCases {
|
||||
t.Run("Invalid_"+tc.name, func(t *testing.T) {
|
||||
server := createValidServerWithArgument(tc.arg)
|
||||
err := validators.ValidateServerJSON(&server)
|
||||
assert.Error(t, err, "Expected error for argument with value starting with name: %+v", tc.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateArgument_ValidValueFields(t *testing.T) {
|
||||
validValueCases := []struct {
|
||||
name string
|
||||
arg model.Argument
|
||||
}{
|
||||
{
|
||||
"value_without_name",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "8080"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--port",
|
||||
},
|
||||
},
|
||||
{
|
||||
"default_without_name",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Default: "/etc/app.conf"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--config",
|
||||
},
|
||||
},
|
||||
{
|
||||
"value_with_var",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "$REPOSITORY_DIRECTORY"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--with-editable",
|
||||
},
|
||||
},
|
||||
{
|
||||
"absolute_path",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "/absolute/path/to/directory"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--directory",
|
||||
},
|
||||
},
|
||||
{
|
||||
"contains_but_not_starts_with_name",
|
||||
model.Argument{
|
||||
InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "use --port for configuration"}},
|
||||
Type: model.ArgumentTypeNamed,
|
||||
Name: "--port",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range validValueCases {
|
||||
t.Run("Valid_"+tc.name, func(t *testing.T) {
|
||||
server := createValidServerWithArgument(tc.arg)
|
||||
err := validators.ValidateServerJSON(&server)
|
||||
assert.NoError(t, err, "Expected valid argument %+v", tc.arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a valid server with a specific argument for testing
|
||||
func createValidServerWithArgument(arg model.Argument) model.ServerJSON {
|
||||
return model.ServerJSON{
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/owner/repo",
|
||||
Source: "github",
|
||||
ID: "owner/repo",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Packages: []model.Package{
|
||||
{
|
||||
Identifier: "test-package",
|
||||
RegistryType: "npm",
|
||||
RegistryBaseURL: "https://registry.npmjs.org",
|
||||
RuntimeArguments: []model.Argument{arg},
|
||||
},
|
||||
},
|
||||
Remotes: []model.Remote{
|
||||
{
|
||||
URL: "https://example.com/remote",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import (
|
|||
"strings"
|
||||
|
||||
jsonschema "github.com/santhosh-tekuri/jsonschema/v5"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
"github.com/modelcontextprotocol/registry/internal/validators"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -65,52 +67,11 @@ func runValidation() error {
|
|||
}
|
||||
|
||||
validatedCount := 0
|
||||
|
||||
for i, example := range examples {
|
||||
log.Printf("Example %d:", i+1)
|
||||
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(example.content), &data); err != nil {
|
||||
log.Printf(" ❌ Invalid JSON: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract server portion if this is a PublishRequest format
|
||||
serverData := data
|
||||
publishRequestValid := true
|
||||
if dataMap, ok := data.(map[string]any); ok {
|
||||
if server, exists := dataMap["server"]; exists {
|
||||
// This is a PublishRequest format - validate only expected properties exist
|
||||
for key := range dataMap {
|
||||
if key != "server" && key != "x-publisher" {
|
||||
log.Printf(" Invalid PublishRequest property: ❌ %s (only 'server' and optional 'x-publisher' are allowed)", key)
|
||||
publishRequestValid = false
|
||||
}
|
||||
}
|
||||
serverData = server
|
||||
}
|
||||
}
|
||||
|
||||
baseValid := false
|
||||
registryValid := false
|
||||
|
||||
if err := baseSchema.Validate(serverData); err != nil {
|
||||
log.Printf(" Validating against server.schema.json: ❌")
|
||||
log.Printf(" Error: %v", err)
|
||||
} else {
|
||||
log.Printf(" Validating against server.schema.json: ✅")
|
||||
baseValid = true
|
||||
}
|
||||
|
||||
if err := registrySchema.Validate(serverData); err != nil {
|
||||
log.Printf(" Validating against registry-schema.json: ❌")
|
||||
log.Printf(" Error: %v", err)
|
||||
} else {
|
||||
log.Printf(" Validating against registry-schema.json: ✅")
|
||||
registryValid = true
|
||||
}
|
||||
|
||||
// Only count as validated if all validations passed
|
||||
if publishRequestValid && baseValid && registryValid {
|
||||
if validateExample(example, baseSchema, registrySchema) {
|
||||
validatedCount++
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +79,7 @@ func runValidation() error {
|
|||
}
|
||||
|
||||
if validatedCount != expectedExampleCount {
|
||||
return fmt.Errorf("validation failed: expected %d examples to pass both validations but only %d did",
|
||||
return fmt.Errorf("validation failed: expected %d examples to pass all validations but only %d did",
|
||||
expectedExampleCount, validatedCount)
|
||||
}
|
||||
|
||||
|
|
@ -126,6 +87,72 @@ func runValidation() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateExample(ex example, baseSchema, registrySchema *jsonschema.Schema) bool {
|
||||
var data any
|
||||
if err := json.Unmarshal([]byte(ex.content), &data); err != nil {
|
||||
log.Printf(" ❌ Invalid JSON: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Extract server portion if this is a PublishRequest format
|
||||
serverData := data
|
||||
publishRequestValid := true
|
||||
if dataMap, ok := data.(map[string]any); ok {
|
||||
if server, exists := dataMap["server"]; exists {
|
||||
// This is a PublishRequest format - validate only expected properties exist
|
||||
for key := range dataMap {
|
||||
if key != "server" && key != "x-publisher" {
|
||||
log.Printf(" Invalid PublishRequest property: ❌ %s (only 'server' and optional 'x-publisher' are allowed)", key)
|
||||
publishRequestValid = false
|
||||
}
|
||||
}
|
||||
serverData = server
|
||||
}
|
||||
}
|
||||
|
||||
baseValid := validateAgainstSchema(serverData, baseSchema, "server.schema.json")
|
||||
registryValid := validateAgainstSchema(serverData, registrySchema, "registry-schema.json")
|
||||
goValidatorValid := validateWithObjectValidator(serverData)
|
||||
|
||||
// Only count as validated if all validations passed
|
||||
return publishRequestValid && baseValid && registryValid && goValidatorValid
|
||||
}
|
||||
|
||||
func validateAgainstSchema(data any, schema *jsonschema.Schema, schemaName string) bool {
|
||||
if err := schema.Validate(data); err != nil {
|
||||
log.Printf(" Validating against %s: ❌", schemaName)
|
||||
log.Printf(" Error: %v", err)
|
||||
return false
|
||||
}
|
||||
log.Printf(" Validating against %s: ✅", schemaName)
|
||||
return true
|
||||
}
|
||||
|
||||
func validateWithObjectValidator(serverData any) bool {
|
||||
var serverDetail model.ServerJSON
|
||||
serverDataBytes, err := json.Marshal(serverData)
|
||||
if err != nil {
|
||||
log.Printf(" Validating with Go Validator: ❌")
|
||||
log.Printf(" Error marshaling server data: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(serverDataBytes, &serverDetail); err != nil {
|
||||
log.Printf(" Validating with Go Validator: ❌")
|
||||
log.Printf(" Error unmarshaling to ServerDetail: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
if err := validators.ValidateServerJSON(&serverDetail); err != nil {
|
||||
log.Printf(" Validating with Go Validator: ❌")
|
||||
log.Printf(" Error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Printf(" Validating with Go Validator: ✅")
|
||||
return true
|
||||
}
|
||||
|
||||
type example struct {
|
||||
content string
|
||||
line int
|
||||
|
|
|
|||
Loading…
Reference in a new issue