aboutsummaryrefslogtreecommitdiff
path: root/internal/api/api.go
diff options
context:
space:
mode:
authorHana Kim <hyangah@gmail.com>2026-04-12 22:10:16 -0400
committerHyang-Ah Hana Kim <hyangah@gmail.com>2026-04-13 11:42:30 -0700
commitaca48c6d9dcc4892027cdad3243527882201358b (patch)
tree8827e389417a4d7ef4344fd7f28dac0c6be19934 /internal/api/api.go
parent70e5087371296e2632232f4a3a795f124c73baf3 (diff)
downloadgo-x-pkgsite-aca48c6d9dcc4892027cdad3243527882201358b.tar.xz
internal/api: improve ambiguous package path resolution
Instead of falling back to UnknownModulePath, we now: 1. Query all candidate module paths. 2. Filter out candidates where the database fell back to a different module path (preventing false positives like google.golang.org). 3. Filter out deprecated or retracted candidates if at least one good candidate exists. 4. Return 400 if ambiguity remains among good candidates. This commit fixes the issue where /v1/package/google.golang.org/adk/agent was returning HTTP 400 because all candidate module paths matched: ``` { "code":400, "message":"ambiguous package path", "candidates":[ {"modulePath":"google.golang.org/adk/agent","packagePath":"google.golang.org/adk/agent"}, {"modulePath":"google.golang.org/adk","packagePath":"google.golang.org/adk/agent"}, {"modulePath":"google.golang.org","packagePath":"google.golang.org/adk/agent"}] } ``` Change-Id: I3ea24bca5144d536490019efd85fb597da214029 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/766380 kokoro-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'internal/api/api.go')
-rw-r--r--internal/api/api.go95
1 files changed, 65 insertions, 30 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
index 87339e19..0f610faf 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"net/http"
+ "slices"
"strconv"
"strings"
"time"
@@ -451,48 +452,82 @@ func trimPath(r *http.Request, prefix string) string {
// resolveModulePath determines the correct module path for a given package path and version.
// If the module path is not provided, it searches through potential candidate module paths
-// derived from the package path. If multiple valid modules contain the package, it returns
-// a list of candidates to help the user disambiguate the request.
+// derived from the package path.
+//
+// Resolution logic:
+// 1. Use internal.CandidateModulePaths(pkgPath) to get potential candidates (ordered longest first).
+// 2. Fetch UnitMeta for each candidate that exists in the data source.
+// 3. Check if um.ModulePath == mp (where mp is the candidate module path). If not, ignore it
+// (this handles the case where GetUnitMeta falls back to another module when the requested
+// module does not exist).
+// 4. Filter candidates by eliminating those that are deprecated or retracted.
+// 5. If exactly one candidate remains after filtering, return it (HTTP 200).
+// 6. If multiple candidates remain, return HTTP 400 with the list of candidates (ambiguity).
+// 7. If all candidates are eliminated (e.g., all are deprecated or retracted), fall back to
+// the longest matching candidate among those that exist (HTTP 200).
func resolveModulePath(r *http.Request, ds internal.DataSource, pkgPath, modulePath, requestedVersion string) (*internal.UnitMeta, error) {
if requestedVersion == "" {
requestedVersion = version.Latest
}
- if modulePath == "" {
- // Handle potential ambiguity if module is not specified.
- candidates := internal.CandidateModulePaths(pkgPath)
- var validCandidates []Candidate
- var foundUM *internal.UnitMeta
- for _, mp := range candidates {
- // Check if this module actually exists and contains the package at the requested version.
- if m, err := ds.GetUnitMeta(r.Context(), pkgPath, mp, requestedVersion); err == nil {
- foundUM = m
- validCandidates = append(validCandidates, Candidate{
- ModulePath: mp,
- PackagePath: pkgPath,
- })
- } else if !errors.Is(err, derrors.NotFound) {
- return nil, err
- }
+ if modulePath != "" {
+ um, err := ds.GetUnitMeta(r.Context(), pkgPath, modulePath, requestedVersion)
+ if err != nil {
+ return nil, err
}
+ return um, nil
+ }
- if len(validCandidates) > 1 {
- return nil, &Error{
- Code: http.StatusBadRequest,
- Message: "ambiguous package path",
- Candidates: validCandidates,
+ candidates := internal.CandidateModulePaths(pkgPath)
+ var validCandidates []*internal.UnitMeta
+ for _, mp := range candidates {
+ if um, err := ds.GetUnitMeta(r.Context(), pkgPath, mp, requestedVersion); err == nil {
+ // Critical check: ensure the DB actually found the candidate module we requested.
+ // GetUnitMeta falls back to the best match if the requested module doesn't exist,
+ // which could lead to false positives (e.g. google.golang.org matching because it
+ // falls back to google.golang.org/adk/agent).
+ if um.ModulePath == mp {
+ validCandidates = append(validCandidates, um)
}
+ } else if !errors.Is(err, derrors.NotFound) {
+ return nil, err
}
- if len(validCandidates) == 0 {
- return nil, derrors.NotFound
+ }
+
+ if len(validCandidates) == 0 {
+ return nil, derrors.NotFound
+ }
+
+ // Filter candidates based on signals (deprecation, retraction).
+ goodCandidates := slices.Clone(validCandidates)
+ goodCandidates = slices.DeleteFunc(goodCandidates, func(um *internal.UnitMeta) bool {
+ return um.Deprecated || um.Retracted
+ })
+
+ switch len(goodCandidates) {
+ case 1:
+ return goodCandidates[0], nil
+ case 0:
+ // If all candidates are deprecated or retracted, fall back to the longest match.
+ // Since candidates are ordered longest first, validCandidates[0] is the longest match.
+ return validCandidates[0], nil
+ default:
+ return nil, &Error{
+ Code: http.StatusBadRequest,
+ Message: "ambiguous package path",
+ Candidates: makeCandidates(goodCandidates),
}
- return foundUM, nil
}
+}
- um, err := ds.GetUnitMeta(r.Context(), pkgPath, modulePath, requestedVersion)
- if err != nil {
- return nil, err
+func makeCandidates(ums []*internal.UnitMeta) []Candidate {
+ var r []Candidate
+ for _, um := range ums {
+ r = append(r, Candidate{
+ ModulePath: um.ModulePath,
+ PackagePath: um.Path,
+ })
}
- return um, nil
+ return r
}
// Values for the Cache-Control header.