Skip to content

Commit 8f46ad8

Browse files
committed
internal/frontend: redirect to module/directory view on exact search match
When a user searches for a path that exactly matches a package, module or directory, it will redirect to one of those views in that order. See golang/go#36803 for context. Change-Id: Ib0630b8357643b17dc834977a38db3cae52c0c1e Reviewed-on: https://team-review.git.corp.google.com/c/golang/discovery/+/650122 CI-Result: Cloud Build <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent aea9c7d commit 8f46ad8

File tree

2 files changed

+85
-8
lines changed

2 files changed

+85
-8
lines changed

internal/frontend/search.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,9 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
110110
return
111111
}
112112

113-
if strings.Contains(query, "/") {
114-
pkg, err := s.ds.GetPackage(ctx, path.Clean(query), internal.UnknownModulePath, internal.LatestVersion)
115-
if err == nil {
116-
http.Redirect(w, r, fmt.Sprintf("/%s", pkg.Path), http.StatusFound)
117-
return
118-
} else if !errors.Is(err, derrors.NotFound) {
119-
log.Errorf(ctx, "error getting package for %s: %v", path.Clean(query), err)
120-
}
113+
if path := searchRequestRedirectPath(ctx, s.ds, query); path != "" {
114+
http.Redirect(w, r, path, http.StatusFound)
115+
return
121116
}
122117

123118
page, err := fetchSearchPage(ctx, s.ds, query, newPaginationParams(r, defaultSearchLimit))
@@ -130,6 +125,41 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
130125
s.servePage(ctx, w, "search.tmpl", page)
131126
}
132127

128+
// searchRequestRedirectPath returns the path that a search request should be
129+
// redirected to, or the empty string if there is no such path. Standard
130+
// library packages that only contain one element (such as fmt, errors, etc.)
131+
// will not redirect to allow users to search by those terms.
132+
func searchRequestRedirectPath(ctx context.Context, ds internal.DataSource, query string) string {
133+
requestedPath := path.Clean(query)
134+
if !strings.Contains(requestedPath, "/") {
135+
return ""
136+
}
137+
pkg, err := ds.GetPackage(ctx, requestedPath, internal.UnknownModulePath, internal.LatestVersion)
138+
if err == nil {
139+
return fmt.Sprintf("/%s", pkg.Path)
140+
} else if !errors.Is(err, derrors.NotFound) {
141+
log.Errorf(ctx, "error getting package for %s: %v", requestedPath, err)
142+
return ""
143+
}
144+
145+
vi, err := ds.GetVersionInfo(ctx, requestedPath, internal.LatestVersion)
146+
if err == nil {
147+
return fmt.Sprintf("/mod/%s", vi.ModulePath)
148+
} else if !errors.Is(err, derrors.NotFound) {
149+
log.Errorf(ctx, "error getting module for %s: %v", requestedPath, err)
150+
return ""
151+
}
152+
153+
dir, err := ds.GetDirectory(ctx, requestedPath, internal.UnknownModulePath, internal.LatestVersion, internal.AllFields)
154+
if err == nil {
155+
return fmt.Sprintf("/%s", dir.Path)
156+
} else if !errors.Is(err, derrors.NotFound) {
157+
log.Errorf(ctx, "error getting directory for %s: %v", requestedPath, err)
158+
return ""
159+
}
160+
return ""
161+
}
162+
133163
// searchQuery extracts a search query from the request.
134164
func searchQuery(r *http.Request) string {
135165
return strings.TrimSpace(r.FormValue("q"))

internal/frontend/search_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,50 @@ func TestApproximateNumber(t *testing.T) {
173173
}
174174
}
175175
}
176+
177+
func TestSearchRequestRedirectPath(t *testing.T) {
178+
ctx := context.Background()
179+
180+
golangTools := sample.Version()
181+
golangTools.ModulePath = "golang.org/x/tools"
182+
lspPkg := sample.Package()
183+
lspPkg.Path = "golang.org/x/tools/internal/lsp"
184+
golangTools.Packages = []*internal.Package{lspPkg}
185+
186+
std := sample.Version()
187+
std.ModulePath = "std"
188+
var stdlibPackages []*internal.Package
189+
for _, path := range []string{"cmd/go", "cmd/go/internal/auth", "fmt"} {
190+
pkg := sample.Package()
191+
pkg.Path = path
192+
stdlibPackages = append(stdlibPackages, pkg)
193+
}
194+
std.Packages = stdlibPackages
195+
versions := []*internal.Version{golangTools, std}
196+
197+
for _, v := range versions {
198+
if err := testDB.InsertVersion(ctx, v); err != nil {
199+
t.Fatal(err)
200+
}
201+
}
202+
for _, tc := range []struct {
203+
name string
204+
query string
205+
want string
206+
}{
207+
{"module", "golang.org/x/tools", "/mod/golang.org/x/tools"},
208+
{"directory", "golang.org/x/tools/internal", "/golang.org/x/tools/internal"},
209+
{"package", "golang.org/x/tools/internal/lsp", "/golang.org/x/tools/internal/lsp"},
210+
{"stdlib package does not redirect", "errors", ""},
211+
{"stdlib package does redirect", "cmd/go", "/cmd/go"},
212+
{"stdlib directory does redirect", "cmd/go/internal", "/cmd/go/internal"},
213+
{"std does not redirect", "std", ""},
214+
{"non-existent path does not redirect", "github.com/non-existent", ""},
215+
} {
216+
t.Run(tc.name, func(t *testing.T) {
217+
if got := searchRequestRedirectPath(ctx, testDB, tc.query); got != tc.want {
218+
t.Errorf("searchRequestRedirectPath(ctx, %q) = %q; want = %q", tc.query, got, tc.want)
219+
}
220+
})
221+
}
222+
}

0 commit comments

Comments
 (0)