-
Notifications
You must be signed in to change notification settings - Fork 213
Add formatting and validation for free-form fields #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+484
−12
Merged
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package validators | ||
|
||
import "errors" | ||
|
||
// Error messages for validation | ||
var ( | ||
// Server validation errors | ||
ErrNameRequired = errors.New("name is required") | ||
ErrServerNameTooLong = errors.New("server name is too long") | ||
ErrVersionRequired = errors.New("version is required") | ||
ErrDescriptionTooLong = errors.New("description is too long") | ||
|
||
// Repository validation errors | ||
ErrInvalidRepositorySource = errors.New("invalid repository source") | ||
ErrInvalidRepositoryURL = errors.New("invalid repository URL") | ||
|
||
// Package validation errors | ||
ErrPackageNameTooLong = errors.New("package name is too long") | ||
ErrPackageNameHasSpaces = errors.New("package name cannot contain spaces") | ||
|
||
// Remote validation errors | ||
ErrInvalidRemoteURL = errors.New("invalid remote URL") | ||
) | ||
|
||
// Constants for validation limits | ||
const ( | ||
MaxLengthForServerName = 255 | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
MaxLengthForDescription = 1000 | ||
MaxLengthForPackageName = 255 | ||
) | ||
|
||
// RepositorySource represents valid repository sources | ||
type RepositorySource string | ||
|
||
const ( | ||
SourceGitHub RepositorySource = "github" | ||
SourceGitLab RepositorySource = "gitlab" | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package validators | ||
|
||
import ( | ||
"net/url" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
var ( | ||
// Regular expressions for validating repository URLs | ||
// These regex patterns ensure the URL is in the format of a valid GitHub or GitLab repository | ||
// For example: // - GitHub: https://github.com/user/repo | ||
githubURLRegex = regexp.MustCompile(`^https?://(www\.)?github\.com/[\w.-]+/[\w.-]+/?$`) | ||
gitlabURLRegex = regexp.MustCompile(`^https?://(www\.)?gitlab\.com/[\w.-]+/[\w.-]+/?$`) | ||
) | ||
|
||
// IsValidRepositoryURL checks if the given URL is valid for the specified repository source | ||
func IsValidRepositoryURL(source RepositorySource, url string) bool { | ||
switch source { | ||
case SourceGitHub: | ||
return githubURLRegex.MatchString(url) | ||
case SourceGitLab: | ||
return gitlabURLRegex.MatchString(url) | ||
} | ||
return false | ||
} | ||
|
||
// HasNoSpaces checks if a string contains no spaces | ||
func HasNoSpaces(s string) bool { | ||
return !strings.Contains(s, " ") | ||
} | ||
|
||
// IsValidURL checks if a URL is in valid format | ||
func IsValidURL(rawURL string) bool { | ||
// Parse the URL | ||
u, err := url.Parse(rawURL) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
// Check if scheme is present (http or https) | ||
if u.Scheme != "http" && u.Scheme != "https" { | ||
return false | ||
} | ||
|
||
if u.Host == "" { | ||
return false | ||
} | ||
return true | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package validators | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/modelcontextprotocol/registry/internal/model" | ||
) | ||
|
||
// ServerValidator validates server details | ||
type ServerValidator struct { | ||
*RespositoryValidator // Embedded RespositoryValidator for repository validation | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Validate checks if the server details are valid | ||
func (v *ServerValidator) Validate(obj *model.ServerDetail) error { | ||
if obj.Name == "" { | ||
Avish34 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ErrNameRequired | ||
} | ||
|
||
// Add format validation according to https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1086 in the next PR | ||
if len(obj.Name) > MaxLengthForServerName { | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ErrServerNameTooLong | ||
} | ||
|
||
// Version is required | ||
if obj.VersionDetail.Version == "" { | ||
Avish34 marked this conversation as resolved.
Show resolved
Hide resolved
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ErrVersionRequired | ||
} | ||
|
||
if len(obj.Description) > MaxLengthForDescription { | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ErrDescriptionTooLong | ||
} | ||
|
||
if err := v.RespositoryValidator.Validate(&obj.Repository); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// NewServerValidator creates a new ServerValidator instance | ||
func NewServerValidator() *ServerValidator { | ||
return &ServerValidator{ | ||
RespositoryValidator: NewRepositoryValidator(), | ||
} | ||
} | ||
|
||
// RepositoryValidator validates repository details | ||
type RespositoryValidator struct { | ||
validSources map[RepositorySource]bool | ||
} | ||
|
||
// Validate checks if the repository details are valid | ||
func (rv *RespositoryValidator) Validate(obj *model.Repository) error { | ||
// empty repository object, this check is needed because we get empty repo object (if not present in request) everytime we unmarshal the request json | ||
if len(obj.URL) == 0 && len(obj.Source) == 0 && len(obj.ID) == 0 { | ||
Avish34 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return nil | ||
} | ||
// validate the repository URL | ||
repoSource := RepositorySource(obj.Source) | ||
if _, ok := rv.validSources[repoSource]; !ok { | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return fmt.Errorf("%w: %s", ErrInvalidRepositorySource, obj.Source) | ||
} | ||
if !IsValidRepositoryURL(repoSource, obj.URL) { | ||
return fmt.Errorf("%w: %s", ErrInvalidRepositoryURL, obj.URL) | ||
} | ||
|
||
// Add validator for repo ID after confirming ID type | ||
return nil | ||
} | ||
|
||
// NewRepositoryValidator creates a new RespositoryValidator instance | ||
func NewRepositoryValidator() *RespositoryValidator { | ||
return &RespositoryValidator{ | ||
validSources: map[RepositorySource]bool{SourceGitHub: true, SourceGitLab: true}, | ||
} | ||
} | ||
|
||
// PackageValidator validates package details | ||
type PackageValidator struct{} | ||
|
||
// Validate checks if the package details are valid | ||
func (pv *PackageValidator) Validate(obj *model.Package) error { | ||
domdomegg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if len(obj.Name) > MaxLengthForPackageName { | ||
return ErrPackageNameTooLong | ||
} | ||
|
||
if !HasNoSpaces(obj.Name) { | ||
return ErrPackageNameHasSpaces | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// NewPackageValidator creates a new PackageValidator instance | ||
func NewPackageValidator() *PackageValidator { | ||
return &PackageValidator{} | ||
} | ||
|
||
// RemoteValidator validates remote connection details | ||
type RemoteValidator struct{} | ||
|
||
// Validate checks if the remote connection details are valid | ||
func (rv *RemoteValidator) Validate(obj *model.Remote) error { | ||
if !IsValidURL(obj.URL) { | ||
Avish34 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, obj.URL) | ||
} | ||
return nil | ||
} | ||
|
||
// NewRemoteValidator creates a new RemoteValidator instance | ||
func NewRemoteValidator() *RemoteValidator { | ||
return &RemoteValidator{} | ||
} | ||
|
||
// ObjectValidator aggregates multiple validators for different object types | ||
// This allows for a single entry point to validate complex objects that may contain multiple fields | ||
// that need validation. | ||
type ObjectValidator struct { | ||
ServerValidator *ServerValidator | ||
PackageValidator *PackageValidator | ||
RemoteValidator *RemoteValidator | ||
} | ||
|
||
func NewObjectValidator() *ObjectValidator { | ||
return &ObjectValidator{ | ||
ServerValidator: NewServerValidator(), | ||
PackageValidator: NewPackageValidator(), | ||
RemoteValidator: NewRemoteValidator(), | ||
} | ||
} | ||
|
||
func (ov *ObjectValidator) Validate(obj *model.ServerDetail) error { | ||
if err := ov.ServerValidator.Validate(obj); err != nil { | ||
return err | ||
} | ||
|
||
for _, pkg := range obj.Packages { | ||
if err := ov.PackageValidator.Validate(&pkg); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
for _, remote := range obj.Remotes { | ||
if err := ov.RemoteValidator.Validate(&remote); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.