Skip to content

Commit 9057a00

Browse files
KN4CK3Rlunny
andauthored
Add /$count endpoints for NuGet v2 (#22855)
Fixes #22838 Co-authored-by: Lunny Xiao <[email protected]>
1 parent fb1a2a1 commit 9057a00

File tree

3 files changed

+80
-19
lines changed

3 files changed

+80
-19
lines changed

routers/api/packages/api.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,18 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
286286
}, reqPackageAccess(perm.AccessModeWrite))
287287
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
288288
r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2)
289-
r.Get("/Packages()", nuget.SearchServiceV2)
290-
r.Get("/FindPackagesById()", nuget.EnumeratePackageVersionsV2)
291-
r.Get("/Search()", nuget.SearchServiceV2)
289+
r.Group("/Packages()", func() {
290+
r.Get("", nuget.SearchServiceV2)
291+
r.Get("/$count", nuget.SearchServiceV2Count)
292+
})
293+
r.Group("/FindPackagesById()", func() {
294+
r.Get("", nuget.EnumeratePackageVersionsV2)
295+
r.Get("/$count", nuget.EnumeratePackageVersionsV2Count)
296+
})
297+
r.Group("/Search()", func() {
298+
r.Get("", nuget.SearchServiceV2)
299+
r.Get("/$count", nuget.SearchServiceV2Count)
300+
})
292301
}, reqPackageAccess(perm.AccessModeRead))
293302
})
294303
r.Group("/npm", func() {

routers/api/packages/nuget/nuget.go

+48-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"net/http"
1212
"regexp"
13+
"strconv"
1314
"strings"
1415

1516
"code.gitea.io/gitea/models/db"
@@ -94,8 +95,7 @@ func FeedCapabilityResource(ctx *context.Context) {
9495

9596
var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
9697

97-
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
98-
func SearchServiceV2(ctx *context.Context) {
98+
func getSearchTerm(ctx *context.Context) string {
9999
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
100100
if searchTerm == "" {
101101
// $filter contains a query like:
@@ -106,7 +106,11 @@ func SearchServiceV2(ctx *context.Context) {
106106
searchTerm = strings.TrimSpace(match[1])
107107
}
108108
}
109+
return searchTerm
110+
}
109111

112+
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
113+
func SearchServiceV2(ctx *context.Context) {
110114
skip, take := ctx.FormInt("skip"), ctx.FormInt("take")
111115
if skip == 0 {
112116
skip = ctx.FormInt("$skip")
@@ -116,9 +120,11 @@ func SearchServiceV2(ctx *context.Context) {
116120
}
117121

118122
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
119-
OwnerID: ctx.Package.Owner.ID,
120-
Type: packages_model.TypeNuGet,
121-
Name: packages_model.SearchValue{Value: searchTerm},
123+
OwnerID: ctx.Package.Owner.ID,
124+
Type: packages_model.TypeNuGet,
125+
Name: packages_model.SearchValue{
126+
Value: getSearchTerm(ctx),
127+
},
122128
IsInternal: util.OptionalBoolFalse,
123129
Paginator: db.NewAbsoluteListOptions(
124130
skip,
@@ -145,6 +151,24 @@ func SearchServiceV2(ctx *context.Context) {
145151
xmlResponse(ctx, http.StatusOK, resp)
146152
}
147153

154+
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
155+
func SearchServiceV2Count(ctx *context.Context) {
156+
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
157+
OwnerID: ctx.Package.Owner.ID,
158+
Type: packages_model.TypeNuGet,
159+
Name: packages_model.SearchValue{
160+
Value: getSearchTerm(ctx),
161+
},
162+
IsInternal: util.OptionalBoolFalse,
163+
})
164+
if err != nil {
165+
apiError(ctx, http.StatusInternalServerError, err)
166+
return
167+
}
168+
169+
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
170+
}
171+
148172
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
149173
func SearchServiceV3(ctx *context.Context) {
150174
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
@@ -288,6 +312,25 @@ func EnumeratePackageVersionsV2(ctx *context.Context) {
288312
xmlResponse(ctx, http.StatusOK, resp)
289313
}
290314

315+
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
316+
func EnumeratePackageVersionsV2Count(ctx *context.Context) {
317+
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
318+
OwnerID: ctx.Package.Owner.ID,
319+
Type: packages_model.TypeNuGet,
320+
Name: packages_model.SearchValue{
321+
ExactMatch: true,
322+
Value: strings.Trim(ctx.FormTrim("id"), "'"),
323+
},
324+
IsInternal: util.OptionalBoolFalse,
325+
})
326+
if err != nil {
327+
apiError(ctx, http.StatusInternalServerError, err)
328+
return
329+
}
330+
331+
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
332+
}
333+
291334
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
292335
func EnumeratePackageVersionsV3(ctx *context.Context) {
293336
packageName := ctx.Params("id")

tests/integration/api_packages_nuget_test.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"net/http"
1414
"net/http/httptest"
15+
"strconv"
1516
"testing"
1617
"time"
1718

@@ -109,8 +110,6 @@ func TestPackageNuGet(t *testing.T) {
109110
url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
110111

111112
t.Run("ServiceIndex", func(t *testing.T) {
112-
defer tests.PrintCurrentTest(t)()
113-
114113
t.Run("v2", func(t *testing.T) {
115114
defer tests.PrintCurrentTest(t)()
116115

@@ -374,8 +373,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
374373
})
375374

376375
t.Run("SearchService", func(t *testing.T) {
377-
defer tests.PrintCurrentTest(t)()
378-
379376
cases := []struct {
380377
Query string
381378
Skip int
@@ -391,8 +388,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
391388
}
392389

393390
t.Run("v2", func(t *testing.T) {
394-
defer tests.PrintCurrentTest(t)()
395-
396391
t.Run("Search()", func(t *testing.T) {
397392
defer tests.PrintCurrentTest(t)()
398393

@@ -406,14 +401,20 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
406401

407402
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
408403
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
404+
405+
req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
406+
req = AddBasicAuthHeader(req, user.Name)
407+
resp = MakeRequest(t, req, http.StatusOK)
408+
409+
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
409410
}
410411
})
411412

412413
t.Run("Packages()", func(t *testing.T) {
413414
defer tests.PrintCurrentTest(t)()
414415

415416
for i, c := range cases {
416-
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
417+
req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
417418
req = AddBasicAuthHeader(req, user.Name)
418419
resp := MakeRequest(t, req, http.StatusOK)
419420

@@ -422,6 +423,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
422423

423424
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
424425
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
426+
427+
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
428+
req = AddBasicAuthHeader(req, user.Name)
429+
resp = MakeRequest(t, req, http.StatusOK)
430+
431+
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
425432
}
426433
})
427434
})
@@ -512,8 +519,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
512519
})
513520

514521
t.Run("RegistrationLeaf", func(t *testing.T) {
515-
defer tests.PrintCurrentTest(t)()
516-
517522
t.Run("v2", func(t *testing.T) {
518523
defer tests.PrintCurrentTest(t)()
519524

@@ -549,8 +554,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
549554
})
550555

551556
t.Run("PackageService", func(t *testing.T) {
552-
defer tests.PrintCurrentTest(t)()
553-
554557
t.Run("v2", func(t *testing.T) {
555558
defer tests.PrintCurrentTest(t)()
556559

@@ -563,6 +566,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
563566

564567
assert.Len(t, result.Entries, 1)
565568
assert.Equal(t, packageVersion, result.Entries[0].Properties.Version)
569+
570+
req = NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()/$count?id='%s'", url, packageName))
571+
req = AddBasicAuthHeader(req, user.Name)
572+
resp = MakeRequest(t, req, http.StatusOK)
573+
574+
assert.Equal(t, "1", resp.Body.String())
566575
})
567576

568577
t.Run("v3", func(t *testing.T) {

0 commit comments

Comments
 (0)