diff --git a/internal/datasource.go b/internal/datasource.go index 81bf69cbf..0bb06ed60 100644 --- a/internal/datasource.go +++ b/internal/datasource.go @@ -27,6 +27,8 @@ type DataSource interface { GetLicenses(ctx context.Context, fullPath, modulePath, resolvedVersion string) ([]*licenses.License, error) // GetPathInfo returns information about a path. GetPathInfo(ctx context.Context, path, inModulePath, inVersion string) (outModulePath, outVersion string, isPackage bool, err error) + // GetLatestMajorVersion returns the latest major version of a module path. + GetLatestMajorVersion(ctx context.Context, seriesPath string) (_ string, err error) // TODO(golang/go#39629): Deprecate these methods. // diff --git a/internal/proxydatasource/datasource.go b/internal/proxydatasource/datasource.go index 344d54aa6..9a0e260b6 100644 --- a/internal/proxydatasource/datasource.go +++ b/internal/proxydatasource/datasource.go @@ -279,3 +279,30 @@ func (ds *DataSource) directoryFromVersion(ctx context.Context, fullPath, module } return nil, fmt.Errorf("%q missing from module %s: %w", fullPath, m.ModulePath, derrors.NotFound) } + +// GetLatestMajorVersion finds the latest major version of a modulePath that +// is found in the proxy by iterating through vN versions. +func (ds *DataSource) GetLatestMajorVersion(ctx context.Context, seriesPath string) (_ string, err error) { + // We are checking if the series path is valid so that we can forward the error if not. + _, err = ds.proxyClient.GetInfo(ctx, seriesPath, internal.LatestVersion) + if err != nil { + return "", err + } + const startVersion = 2 + // We start checking versions from "/v2", since v1 and v0 versions don't + // have a major version at the end of the modulepath. + for v := startVersion; ; v++ { + query := fmt.Sprintf("%s/v%d", seriesPath, v) + + _, err := ds.proxyClient.GetInfo(ctx, query, internal.LatestVersion) + if errors.Is(err, derrors.NotFound) { + if v == 2 { + return "", nil + } + return fmt.Sprintf("/v%d", v-1), nil + } + if err != nil { + return "", err + } + } +} diff --git a/internal/proxydatasource/datasource_test.go b/internal/proxydatasource/datasource_test.go index 8d9ebe733..e097eebf1 100644 --- a/internal/proxydatasource/datasource_test.go +++ b/internal/proxydatasource/datasource_test.go @@ -432,3 +432,58 @@ func TestDataSource_Bypass(t *testing.T) { }) } } + +func TestDataSource_GetLatestMajorVersion(t *testing.T) { + t.Helper() + testModules := []*proxy.Module{ + { + ModulePath: "foo.com/bar", + }, + { + ModulePath: "foo.com/bar/v2", + }, + { + ModulePath: "foo.com/bar/v3", + }, + { + ModulePath: "bar.com/foo", + }, + } + client, teardownProxy := proxy.SetupTestClient(t, testModules) + defer teardownProxy() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ds := New(client) + + for _, test := range []struct { + seriesPath string + wantVersion string + wantErr error + }{ + { + seriesPath: "foo.com/bar", + wantVersion: "/v3", + }, + { + seriesPath: "bar.com/foo", + wantVersion: "", + }, + { + seriesPath: "boo.com/far", + wantErr: derrors.NotFound, + }, + } { + gotVersion, err := ds.GetLatestMajorVersion(ctx, test.seriesPath) + if err != nil { + if test.wantErr == nil { + t.Fatalf("got unexpected error %v", err) + } + if !errors.Is(err, test.wantErr) { + t.Errorf("got err = %v, want Is(%v)", err, test.wantErr) + } + } + if gotVersion != test.wantVersion { + t.Errorf("GetLatestMajorVersion(%v) = %v, want %v", test.seriesPath, gotVersion, test.wantVersion) + } + } +}