internal/frontend: reorganize search.go
Pure code in motion.
Change-Id: Icb488458746ec3fdf7d210dbe9f7b2c4f88bfc31
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/348110
Trust: Julie Qiu <julie@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index d09551e..b2e2af1 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -30,7 +30,114 @@
"golang.org/x/text/message"
)
-const defaultSearchLimit = 25
+// serveSearch applies database data to the search template. Handles endpoint
+// /search?q=<query>. If <query> is an exact match for a package path, the user
+// will be redirected to the details page.
+func (s *Server) serveSearch(w http.ResponseWriter, r *http.Request, ds internal.DataSource) error {
+ if r.Method != http.MethodGet && r.Method != http.MethodHead {
+ return &serverError{status: http.StatusMethodNotAllowed}
+ }
+ db, ok := ds.(*postgres.DB)
+ if !ok {
+ // The proxydatasource does not support the imported by page.
+ return proxydatasourceNotSupportedErr()
+ }
+
+ ctx := r.Context()
+ query, searchSymbols := searchQueryAndMode(r)
+ if !utf8.ValidString(query) {
+ return &serverError{status: http.StatusBadRequest}
+ }
+ if len(query) > maxSearchQueryLength {
+ return &serverError{
+ status: http.StatusBadRequest,
+ epage: &errorPage{
+ messageTemplate: template.MakeTrustedTemplate(
+ `<h3 class="Error-message">Search query too long.</h3>`),
+ },
+ }
+ }
+ if query == "" {
+ http.Redirect(w, r, "/", http.StatusFound)
+ return nil
+ }
+ pageParams := newPaginationParams(r, defaultSearchLimit)
+ if pageParams.offset() > maxSearchOffset {
+ return &serverError{
+ status: http.StatusBadRequest,
+ epage: &errorPage{
+ messageTemplate: template.MakeTrustedTemplate(
+ `<h3 class="Error-message">Search page number too large.</h3>`),
+ },
+ }
+ }
+ if pageParams.limit > maxSearchPageSize {
+ return &serverError{
+ status: http.StatusBadRequest,
+ epage: &errorPage{
+ messageTemplate: template.MakeTrustedTemplate(
+ `<h3 class="Error-message">Search page size too large.</h3>`),
+ },
+ }
+ }
+
+ if path := searchRequestRedirectPath(ctx, ds, query); path != "" {
+ http.Redirect(w, r, path, http.StatusFound)
+ return nil
+ }
+
+ page, err := fetchSearchPage(ctx, db, query, pageParams, searchSymbols)
+ if err != nil {
+ return fmt.Errorf("fetchSearchPage(ctx, db, %q): %v", query, err)
+ }
+ page.basePage = s.newBasePage(r, fmt.Sprintf("%s - Search Results", query))
+ if searchSymbols {
+ page.SearchMode = searchModeSymbol
+ } else {
+ page.SearchMode = searchModePackage
+ }
+ if s.shouldServeJSON(r) {
+ return s.serveJSONPage(w, r, page)
+ }
+ tmpl := "legacy_search"
+ if experiment.IsActive(ctx, internal.ExperimentSearchGrouping) {
+ tmpl = "search"
+ }
+ s.servePage(ctx, w, tmpl, page)
+ return nil
+}
+
+const (
+ // defaultSearchLimit is the default number of items that appears on the
+ // search results page if limit is not specified.
+ defaultSearchLimit = 25
+
+ // maxSearchQueryLength represents the max number of characters that a search
+ // query can be. For PostgreSQL 11, there is a max length of 2K bytes:
+ // https://www.postgresql.org/docs/11/textsearch-limitations.html. No valid
+ // searches on pkg.go.dev will need more than the maxSearchQueryLength.
+ maxSearchQueryLength = 500
+
+ // maxSearchOffset is the maximum allowed offset into the search results.
+ // This prevents some very CPU-intensive queries from running.
+ maxSearchOffset = 90
+
+ // maxSearchPageSize is the maximum allowed limit for search results.
+ maxSearchPageSize = 100
+
+ // searchModePackage is the keyword prefix and query param for searching
+ // by packages.
+ searchModePackage = "package"
+
+ // searchModeSymbol is the keyword prefix and query param for searching
+ // by symbols.
+ searchModeSymbol = "symbol"
+
+ // symbolSearchFilter is a filter that can be used to indicate that the query
+ // contains a symbol. For example, searching for "#unmarshal json" indicates
+ // that unmarshal is a symbol.
+ symbolSearchFilter = "#"
+)
// SearchPage contains all of the data that the search template needs to
// populate.
@@ -70,7 +177,8 @@
// fetchSearchPage fetches data matching the search query from the database and
// returns a SearchPage.
-func fetchSearchPage(ctx context.Context, db *postgres.DB, query string, pageParams paginationParams, searchSymbols bool) (*SearchPage, error) {
+func fetchSearchPage(ctx context.Context, db *postgres.DB, query string,
+ pageParams paginationParams, searchSymbols bool) (*SearchPage, error) {
maxResultCount := maxSearchOffset + pageParams.limit
offset := pageParams.offset()
@@ -175,182 +283,6 @@
return sr
}
-func symbolSynopsis(r *postgres.SearchResult) string {
- switch r.SymbolKind {
- case internal.SymbolKindField:
- return fmt.Sprintf(`
-type %s struct {
- %s
-}
-`, strings.Split(r.SymbolName, ".")[0], r.SymbolSynopsis)
- case internal.SymbolKindMethod:
- if !strings.HasPrefix(r.SymbolSynopsis, "func (") {
- return fmt.Sprintf(`
-type %s interface {
- %s
-}
-`, strings.Split(r.SymbolName, ".")[0], r.SymbolSynopsis)
- }
- }
- return r.SymbolSynopsis
-}
-
-// approximateNumber returns an approximation of the estimate, calibrated by
-// the statistical estimate of standard error.
-// i.e., a number that isn't misleading when we say '1-10 of approximately N
-// results', but that is still close to our estimate.
-func approximateNumber(estimate int, sigma float64) int {
- expectedErr := sigma * float64(estimate)
- // Compute the unit by rounding the error the logarithmically closest power
- // of 10, so that 300->100, but 400->1000.
- unit := math.Pow(10, math.Round(math.Log10(expectedErr)))
- // Now round the estimate to the nearest unit.
- return int(unit * math.Round(float64(estimate)/unit))
-}
-
-func packagePaths(heading string, rs []*postgres.SearchResult) *subResult {
- if len(rs) == 0 {
- return nil
- }
- var links []link
- for _, r := range rs {
- links = append(links, link{Href: r.PackagePath, Body: internal.Suffix(r.PackagePath, r.ModulePath)})
- }
- return &subResult{
- Heading: heading,
- Links: links,
- }
-}
-
-func modulePaths(heading string, mpaths map[string]bool) *subResult {
- if len(mpaths) == 0 {
- return nil
- }
- var mps []string
- for m := range mpaths {
- mps = append(mps, m)
- }
- sort.Slice(mps, func(i, j int) bool {
- _, v1 := internal.SeriesPathAndMajorVersion(mps[i])
- _, v2 := internal.SeriesPathAndMajorVersion(mps[j])
- return v1 > v2
- })
- links := make([]link, len(mps))
- for i, m := range mps {
- links[i] = link{Href: m, Body: m}
- }
- return &subResult{
- Heading: heading,
- Links: links,
- }
-}
-
-// Search constraints.
-const (
- // maxSearchQueryLength represents the max number of characters that a search
- // query can be. For PostgreSQL 11, there is a max length of 2K bytes:
- // https://www.postgresql.org/docs/11/textsearch-limitations.html. No valid
- // searches on pkg.go.dev will need more than the maxSearchQueryLength.
- maxSearchQueryLength = 500
-
- // maxSearchOffset is the maximum allowed offset into the search results.
- // This prevents some very CPU-intensive queries from running.
- maxSearchOffset = 90
-
- // maxSearchPageSize is the maximum allowed limit for search results.
- maxSearchPageSize = 100
-
- // searchModePackage is the keyword prefix and query param for searching
- // by packages.
- searchModePackage = "package"
-
- // searchModeSymbol is the keyword prefix and query param for searching
- // by symbols.
- searchModeSymbol = "symbol"
-)
-
-// symbolSearchFilter is a filter that can be used to indicate that the query
-// contains a symbol. For example, searching for "#unmarshal json" indicates
-// that unmarshal is a symbol.
-const symbolSearchFilter = "#"
-
-// serveSearch applies database data to the search template. Handles endpoint
-// /search?q=<query>. If <query> is an exact match for a package path, the user
-// will be redirected to the details page.
-func (s *Server) serveSearch(w http.ResponseWriter, r *http.Request, ds internal.DataSource) error {
- if r.Method != http.MethodGet && r.Method != http.MethodHead {
- return &serverError{status: http.StatusMethodNotAllowed}
- }
- db, ok := ds.(*postgres.DB)
- if !ok {
- // The proxydatasource does not support the imported by page.
- return proxydatasourceNotSupportedErr()
- }
-
- ctx := r.Context()
- query, searchSymbols := searchQueryAndMode(r)
- if !utf8.ValidString(query) {
- return &serverError{status: http.StatusBadRequest}
- }
- if len(query) > maxSearchQueryLength {
- return &serverError{
- status: http.StatusBadRequest,
- epage: &errorPage{
- messageTemplate: template.MakeTrustedTemplate(
- `<h3 class="Error-message">Search query too long.</h3>`),
- },
- }
- }
- if query == "" {
- http.Redirect(w, r, "/", http.StatusFound)
- return nil
- }
- pageParams := newPaginationParams(r, defaultSearchLimit)
- if pageParams.offset() > maxSearchOffset {
- return &serverError{
- status: http.StatusBadRequest,
- epage: &errorPage{
- messageTemplate: template.MakeTrustedTemplate(
- `<h3 class="Error-message">Search page number too large.</h3>`),
- },
- }
- }
- if pageParams.limit > maxSearchPageSize {
- return &serverError{
- status: http.StatusBadRequest,
- epage: &errorPage{
- messageTemplate: template.MakeTrustedTemplate(
- `<h3 class="Error-message">Search page size too large.</h3>`),
- },
- }
- }
-
- if path := searchRequestRedirectPath(ctx, ds, query); path != "" {
- http.Redirect(w, r, path, http.StatusFound)
- return nil
- }
-
- page, err := fetchSearchPage(ctx, db, query, pageParams, searchSymbols)
- if err != nil {
- return fmt.Errorf("fetchSearchPage(ctx, db, %q): %v", query, err)
- }
- page.basePage = s.newBasePage(r, fmt.Sprintf("%s - Search Results", query))
- if searchSymbols {
- page.SearchMode = searchModeSymbol
- } else {
- page.SearchMode = searchModePackage
- }
- if s.shouldServeJSON(r) {
- return s.serveJSONPage(w, r, page)
- }
- tmpl := "legacy_search"
- if experiment.IsActive(ctx, internal.ExperimentSearchGrouping) {
- tmpl = "search"
- }
- s.servePage(ctx, w, tmpl, page)
- return nil
-}
-
// searchRequestRedirectPath returns the path that a search request should be
// redirected to, or the empty string if there is no such path. If the user
// types an existing package path into the search bar, we will redirect the
@@ -423,6 +355,76 @@
return isCapitalized(q)
}
+func symbolSynopsis(r *postgres.SearchResult) string {
+ switch r.SymbolKind {
+ case internal.SymbolKindField:
+ return fmt.Sprintf(`
+type %s struct {
+ %s
+}
+`, strings.Split(r.SymbolName, ".")[0], r.SymbolSynopsis)
+ case internal.SymbolKindMethod:
+ if !strings.HasPrefix(r.SymbolSynopsis, "func (") {
+ return fmt.Sprintf(`
+type %s interface {
+ %s
+}
+`, strings.Split(r.SymbolName, ".")[0], r.SymbolSynopsis)
+ }
+ }
+ return r.SymbolSynopsis
+}
+
+// approximateNumber returns an approximation of the estimate, calibrated by
+// the statistical estimate of standard error.
+// i.e., a number that isn't misleading when we say '1-10 of approximately N
+// results', but that is still close to our estimate.
+func approximateNumber(estimate int, sigma float64) int {
+ expectedErr := sigma * float64(estimate)
+ // Compute the unit by rounding the error the logarithmically closest power
+ // of 10, so that 300->100, but 400->1000.
+ unit := math.Pow(10, math.Round(math.Log10(expectedErr)))
+ // Now round the estimate to the nearest unit.
+ return int(unit * math.Round(float64(estimate)/unit))
+}
+
+func packagePaths(heading string, rs []*postgres.SearchResult) *subResult {
+ if len(rs) == 0 {
+ return nil
+ }
+ var links []link
+ for _, r := range rs {
+ links = append(links, link{Href: r.PackagePath, Body: internal.Suffix(r.PackagePath, r.ModulePath)})
+ }
+ return &subResult{
+ Heading: heading,
+ Links: links,
+ }
+}
+
+func modulePaths(heading string, mpaths map[string]bool) *subResult {
+ if len(mpaths) == 0 {
+ return nil
+ }
+ var mps []string
+ for m := range mpaths {
+ mps = append(mps, m)
+ }
+ sort.Slice(mps, func(i, j int) bool {
+ _, v1 := internal.SeriesPathAndMajorVersion(mps[i])
+ _, v2 := internal.SeriesPathAndMajorVersion(mps[j])
+ return v1 > v2
+ })
+ links := make([]link, len(mps))
+ for i, m := range mps {
+ links[i] = link{Href: m, Body: m}
+ }
+ return &subResult{
+ Heading: heading,
+ Links: links,
+ }
+}
+
func isCapitalized(s string) bool {
if len(s) == 0 {
return false