Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 92 additions & 10 deletions internal/pkgsite/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,25 +252,107 @@ func resultError(statusCode int, status string, body []byte, resp *http.Response
return Result{Error: &APIError{StatusCode: statusCode, Status: status, Message: message, Body: raw}, UpstreamURL: requestURL(resp), FromCache: fromCache(resp)}
}

func paginatedItems(page *pkgsiteapi.PaginatedResponse) []map[string]any {
if page == nil || page.Items == nil {
func paginatedItems(page any) []map[string]any {
switch page := page.(type) {
case *pkgsiteapi.PaginatedResponseModuleVersion:
if page == nil {
return nil
}
return objectItems(page.Items)
case *pkgsiteapi.PaginatedResponsePackageInfo:
if page == nil {
return nil
}
return objectItems(page.Items)
case *pkgsiteapi.PaginatedResponseSearchResult:
if page == nil {
return nil
}
return objectItems(page.Items)
case *pkgsiteapi.PaginatedResponseSymbol:
if page == nil {
return nil
}
return objectItems(page.Items)
case *pkgsiteapi.PaginatedResponseVulnerability:
if page == nil {
return nil
}
return objectItems(page.Items)
case *pkgsiteapi.PaginatedResponseString:
if page == nil || page.Items == nil {
return nil
}
items := make([]map[string]any, 0, len(*page.Items))
for _, item := range *page.Items {
items = append(items, map[string]any{"path": item})
}
return items
default:
return nil
}
return *page.Items
}

func pagination(page *pkgsiteapi.PaginatedResponse, count int) map[string]any {
func pagination(page any, count int) map[string]any {
total := count
next := ""
if page != nil {
if page.Total != nil {
total = *page.Total
pageTotal, pageNext := pageMetadata(page)
if pageTotal != nil {
total = *pageTotal
}
if pageNext != nil {
next = *pageNext
}
return map[string]any{"total": total, "displayedItems": count, "startAt": 0, "nextStartAt": nil, "upstreamNextPageToken": next}
}

func pageMetadata(page any) (total *int, next *string) {
switch page := page.(type) {
case *pkgsiteapi.PaginatedResponseModuleVersion:
if page != nil {
return page.Total, page.NextPageToken
}
case *pkgsiteapi.PaginatedResponsePackageInfo:
if page != nil {
return page.Total, page.NextPageToken
}
if page.NextPageToken != nil {
next = *page.NextPageToken
case *pkgsiteapi.PaginatedResponseSearchResult:
if page != nil {
return page.Total, page.NextPageToken
}
case *pkgsiteapi.PaginatedResponseSymbol:
if page != nil {
return page.Total, page.NextPageToken
}
case *pkgsiteapi.PaginatedResponseVulnerability:
if page != nil {
return page.Total, page.NextPageToken
}
case *pkgsiteapi.PaginatedResponseString:
if page != nil {
return page.Total, page.NextPageToken
}
}
return map[string]any{"total": total, "displayedItems": count, "startAt": 0, "nextStartAt": nil, "upstreamNextPageToken": next}
return nil, nil
}

func objectItems[T any](items *[]T) []map[string]any {
if items == nil {
return nil
}
result := make([]map[string]any, 0, len(*items))
for _, item := range *items {
value := map[string]any{}
data, err := json.Marshal(item)
if err != nil {
continue
}
if err := json.Unmarshal(data, &value); err != nil {
continue
}
result = append(result, value)
}
return result
}

func optionalString(v string) *string {
Expand Down
41 changes: 28 additions & 13 deletions internal/pkgsite/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,9 @@ func TestClientSearchSuccessFromFakeUpstream(t *testing.T) {
"total": 1,
"items": []map[string]any{
{
"name": "uuid",
"path": "github.com/google/uuid",
"modulePath": "github.com/google/uuid",
"version": "v1.6.0",
"packagePath": "github.com/google/uuid",
"modulePath": "github.com/google/uuid",
"version": "v1.6.0",
},
},
})
Expand All @@ -211,7 +210,7 @@ func TestClientSearchSuccessFromFakeUpstream(t *testing.T) {
"symbol": "",
"count": 1,
})
assertItemNames(t, got.Items, []string{"uuid"})
assertItemPackagePaths(t, got.Items, []string{"github.com/google/uuid"})
assertPagination(t, got, 1, 1, "")
}

Expand All @@ -225,9 +224,9 @@ func TestClientSearchSingleResultSchedulesPackageWarm(t *testing.T) {
writeJSON(t, w, http.StatusOK, map[string]any{
"total": 1,
"items": []map[string]any{{
"path": "github.com/google/uuid",
"modulePath": "github.com/google/uuid",
"version": "v1.6.0",
"packagePath": "github.com/google/uuid",
"modulePath": "github.com/google/uuid",
"version": "v1.6.0",
}},
})
}, WithWarmer(warmer))
Expand All @@ -251,8 +250,8 @@ func TestClientSearchMultipleResultsDoesNotWarm(t *testing.T) {
writeJSON(t, w, http.StatusOK, map[string]any{
"total": 2,
"items": []map[string]any{
{"path": "example.com/one"},
{"path": "example.com/two"},
{"packagePath": "example.com/one"},
{"packagePath": "example.com/two"},
},
})
}, WithWarmer(warmer))
Expand Down Expand Up @@ -319,7 +318,7 @@ func TestClientUpstream4xxReturnsStructuredResultError(t *testing.T) {
name: "module not found",
status: http.StatusNotFound,
body: map[string]any{
"code": "not_found",
"code": http.StatusNotFound,
"message": "module not found",
},
callFunc: func(t *testing.T, client *Client) (Result, error) {
Expand All @@ -331,7 +330,7 @@ func TestClientUpstream4xxReturnsStructuredResultError(t *testing.T) {
name: "search bad request",
status: http.StatusBadRequest,
body: map[string]any{
"code": "bad_request",
"code": http.StatusBadRequest,
"message": "missing query",
},
callFunc: func(t *testing.T, client *Client) (Result, error) {
Expand Down Expand Up @@ -367,7 +366,7 @@ func TestClientUpstream4xxReturnsStructuredResultError(t *testing.T) {
if !json.Valid(got.Error.Body) {
t.Fatalf("Result.Error.Body is not valid JSON: %q", string(got.Error.Body))
}
for _, want := range []string{tt.body["code"].(string), tt.body["message"].(string)} {
for _, want := range []string{fmt.Sprint(tt.body["code"]), tt.body["message"].(string)} {
if !strings.Contains(got.Error.Message, want) {
t.Fatalf("message %q does not contain %q", got.Error.Message, want)
}
Expand Down Expand Up @@ -623,6 +622,22 @@ func assertItemNames(t testing.TB, items []map[string]any, want []string) {
}
}

func assertItemPackagePaths(t testing.TB, items []map[string]any, want []string) {
t.Helper()

got := make([]string, 0, len(items))
for _, item := range items {
path, ok := item["packagePath"].(string)
if !ok {
t.Fatalf("item package path = %#v, want string", item["packagePath"])
}
got = append(got, path)
}
if !slices.Equal(got, want) {
t.Fatalf("item package paths = %#v, want %#v", got, want)
}
}

func assertItemPaths(t testing.TB, items []map[string]any, want []string) {
t.Helper()

Expand Down
77 changes: 69 additions & 8 deletions internal/pkgsite/pagination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ func TestPaginationMetadata(t *testing.T) {

tests := []struct {
name string
page *pkgsiteapi.PaginatedResponse
page any
count int
want map[string]any
}{
{
name: "upstream total and next token",
page: &pkgsiteapi.PaginatedResponse{
page: &pkgsiteapi.PaginatedResponseSymbol{
NextPageToken: new("next-page-token"),
Total: new(42),
},
Expand All @@ -45,7 +45,7 @@ func TestPaginationMetadata(t *testing.T) {
},
{
name: "missing upstream token is empty string",
page: &pkgsiteapi.PaginatedResponse{},
page: &pkgsiteapi.PaginatedResponseSymbol{},
count: 0,
want: map[string]any{
"total": 0,
Expand Down Expand Up @@ -74,16 +74,16 @@ func TestPaginatedItems(t *testing.T) {

tests := []struct {
name string
page *pkgsiteapi.PaginatedResponse
page any
want []map[string]any
}{
{name: "nil page", page: nil, want: nil},
{name: "nil items", page: &pkgsiteapi.PaginatedResponse{}, want: nil},
{name: "nil items", page: &pkgsiteapi.PaginatedResponseSymbol{}, want: nil},
{
name: "items",
page: &pkgsiteapi.PaginatedResponse{Items: &[]map[string]any{
{"name": "Config"},
{"name": "Token"},
page: &pkgsiteapi.PaginatedResponseSymbol{Items: &[]pkgsiteapi.Symbol{
{Name: new("Config")},
{Name: new("Token")},
}},
want: []map[string]any{
{"name": "Config"},
Expand All @@ -103,3 +103,64 @@ func TestPaginatedItems(t *testing.T) {
})
}
}

func TestPaginatedItemsUsesGeneratedModels(t *testing.T) {
t.Parallel()

tests := []struct {
name string
page any
want []map[string]any
}{
{
name: "module versions",
page: &pkgsiteapi.PaginatedResponseModuleVersion{Items: &[]pkgsiteapi.ModuleVersion{{
ModulePath: new("example.com/module"), LatestVersion: new("v1.2.3"),
}}},
want: []map[string]any{{"modulePath": "example.com/module", "latestVersion": "v1.2.3"}},
},
{
name: "package info",
page: &pkgsiteapi.PaginatedResponsePackageInfo{Items: &[]pkgsiteapi.PackageInfo{{
Path: new("example.com/module/pkg"), Name: new("pkg"),
}}},
want: []map[string]any{{"name": "pkg", "path": "example.com/module/pkg"}},
},
{
name: "search results",
page: &pkgsiteapi.PaginatedResponseSearchResult{Items: &[]pkgsiteapi.SearchResult{{
PackagePath: new("example.com/module/pkg"), ModulePath: new("example.com/module"),
}}},
want: []map[string]any{{"modulePath": "example.com/module", "packagePath": "example.com/module/pkg"}},
},
{
name: "symbols",
page: &pkgsiteapi.PaginatedResponseSymbol{Items: &[]pkgsiteapi.Symbol{{
Name: new("Config"), Kind: new("Type"),
}}},
want: []map[string]any{{"kind": "Type", "name": "Config"}},
},
{
name: "vulnerabilities",
page: &pkgsiteapi.PaginatedResponseVulnerability{Items: &[]pkgsiteapi.Vulnerability{{
Id: new("GO-2026-0001"), Summary: new("example vulnerability"),
}}},
want: []map[string]any{{"id": "GO-2026-0001", "summary": "example vulnerability"}},
},
{
name: "strings",
page: &pkgsiteapi.PaginatedResponseString{Items: &[]string{"example.com/importer"}},
want: []map[string]any{{"path": "example.com/importer"}},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

if got := paginatedItems(tt.page); !reflect.DeepEqual(got, tt.want) {
t.Fatalf("paginatedItems() = %#v, want %#v", got, tt.want)
}
})
}
}
Loading
Loading