diff --git a/internal/mod/module/module_test.go b/internal/mod/module/module_test.go index 6f1532a04f6..b161cb8d201 100644 --- a/internal/mod/module/module_test.go +++ b/internal/mod/module/module_test.go @@ -43,6 +43,32 @@ func TestCheck(t *testing.T) { } } +var checkPathWithoutVersionTests = []struct { + path string + wantErr string +}{{ + path: "rsc io/quote", + wantErr: `invalid char ' '`, +}, { + path: "foo.com@v0", + wantErr: `module path inappropriately contains major version`, +}, { + path: "foo.com/bar/baz", +}} + +func TestCheckPathWithoutVersion(t *testing.T) { + for _, test := range checkPathWithoutVersionTests { + t.Run(test.path, func(t *testing.T) { + err := CheckPathWithoutVersion(test.path) + if test.wantErr != "" { + qt.Assert(t, qt.ErrorMatches(err, test.wantErr)) + return + } + qt.Assert(t, qt.IsNil(err)) + }) + } +} + var newVersionTests = []struct { path, vers string wantError string diff --git a/internal/mod/module/path.go b/internal/mod/module/path.go index b8eb4989829..c63e60112d8 100644 --- a/internal/mod/module/path.go +++ b/internal/mod/module/path.go @@ -104,6 +104,40 @@ func fileNameOK(r rune) bool { return unicode.IsLetter(r) } +// CheckPathWithoutVersion is like CheckPath except that +// it expects a module path without a major version. +func CheckPathWithoutVersion(basePath string) (err error) { + if _, _, ok := SplitPathVersion(basePath); ok { + return fmt.Errorf("module path inappropriately contains major version") + } + if err := checkPath(basePath, modulePath); err != nil { + return err + } + i := strings.Index(basePath, "/") + if i < 0 { + i = len(basePath) + } + if i == 0 { + return fmt.Errorf("leading slash") + } + if !strings.Contains(basePath[:i], ".") { + return fmt.Errorf("missing dot in first path element") + } + if basePath[0] == '-' { + return fmt.Errorf("leading dash in first path element") + } + for _, r := range basePath[:i] { + if !firstPathOK(r) { + return fmt.Errorf("invalid char %q in first path element", r) + } + } + // Sanity check agreement with OCI specs. + if !basePathPat.MatchString(basePath) { + return fmt.Errorf("non-conforming path %q", basePath) + } + return nil +} + // CheckPath checks that a module path is valid. // A valid module path is a valid import path, as checked by CheckImportPath, // with three additional constraints. @@ -135,32 +169,9 @@ func CheckPath(mpath string) (err error) { if semver.Major(vers) != vers { return fmt.Errorf("path can contain major version only") } - - if err := checkPath(basePath, modulePath); err != nil { + if err := CheckPathWithoutVersion(basePath); err != nil { return err } - i := strings.Index(basePath, "/") - if i < 0 { - i = len(basePath) - } - if i == 0 { - return fmt.Errorf("leading slash") - } - if !strings.Contains(basePath[:i], ".") { - return fmt.Errorf("missing dot in first path element") - } - if basePath[0] == '-' { - return fmt.Errorf("leading dash in first path element") - } - for _, r := range basePath[:i] { - if !firstPathOK(r) { - return fmt.Errorf("invalid char %q in first path element", r) - } - } - // Sanity check agreement with OCI specs. - if !basePathPat.MatchString(basePath) { - return fmt.Errorf("non-conforming path %q", basePath) - } if !tagPat.MatchString(vers) { return fmt.Errorf("non-conforming version %q", vers) }