Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0bb1aca

Browse files
committed
Add /api/vscode/{publisher}/{extension}/latest endpoint
1 parent 96d9c0d commit 0bb1aca

File tree

8 files changed

+157
-10
lines changed

8 files changed

+157
-10
lines changed

api/api.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ func New(options *Options) *API {
128128
r.Get("/publishers/{publisher}/vsextensions/{extension}/{version}/{type}", api.assetRedirect)
129129
r.Get("/api/publishers/{publisher}/vsextensions/{extension}/{version}/{type}", api.assetRedirect)
130130

131+
// Return the specified extension with only the latest version included.
132+
r.Get("/api/vscode/{publisher}/{extension}/latest", api.latestExtension)
133+
131134
// This is the URL you get taken to when you click the extension's names,
132135
// ratings, etc from the extension details page.
133136
r.Get("/item", func(rw http.ResponseWriter, r *http.Request) {
@@ -256,3 +259,48 @@ func (api *API) assetRedirect(rw http.ResponseWriter, r *http.Request) {
256259

257260
http.Redirect(rw, r, url, http.StatusMovedPermanently)
258261
}
262+
263+
func (api *API) latestExtension(rw http.ResponseWriter, r *http.Request) {
264+
baseURL := httpapi.RequestBaseURL(r, "/")
265+
filter := database.Filter{
266+
Criteria: []database.Criteria{
267+
{
268+
Type: database.Target,
269+
Value: "Microsoft.VisualStudio.Code",
270+
},
271+
{
272+
// ExtensionName is the fully qualified name `publisher.extension`.
273+
Type: database.ExtensionName,
274+
Value: storage.ExtensionIDWithoutVersion(chi.URLParam(r, "publisher"), chi.URLParam(r, "extension")),
275+
},
276+
},
277+
PageNumber: 1,
278+
PageSize: 1,
279+
}
280+
flags := database.IncludeVersions |
281+
database.IncludeFiles |
282+
database.IncludeCategoryAndTags |
283+
database.IncludeVersionProperties |
284+
database.IncludeAssetURI |
285+
database.IncludeStatistics |
286+
database.IncludeLatestVersionOnly
287+
extensions, _, err := api.Database.GetExtensions(r.Context(), filter, flags, baseURL)
288+
if err != nil {
289+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.ErrorResponse{
290+
Message: "Unable to read extension",
291+
Detail: "Contact an administrator with the request ID",
292+
RequestID: httpmw.RequestID(r),
293+
})
294+
return
295+
}
296+
if len(extensions) == 0 {
297+
httpapi.Write(rw, http.StatusNotFound, httpapi.ErrorResponse{
298+
Message: "Extension does not exist",
299+
Detail: "Please check the publisher and extension name",
300+
RequestID: httpmw.RequestID(r),
301+
})
302+
return
303+
}
304+
305+
httpapi.Write(rw, http.StatusOK, extensions[0])
306+
}

api/api_test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestAPI(t *testing.T) {
2424
t.Parallel()
2525

2626
exts := []*database.Extension{}
27-
for i := 0; i < 10; i++ {
27+
for i := range 10 {
2828
exts = append(exts, &database.Extension{
2929
ID: fmt.Sprintf("extension-%d", i),
3030
})
@@ -266,6 +266,23 @@ func TestAPI(t *testing.T) {
266266
Path: "/api/publishers/vscodevim/extensions/vim/1.23.1/stats?statType=1",
267267
Status: http.StatusOK,
268268
},
269+
{
270+
Name: "LatestExtensionNotExist",
271+
Path: "/api/vscode/notexist/nope/latest",
272+
Method: http.MethodGet,
273+
Status: http.StatusNotFound,
274+
Response: &httpapi.ErrorResponse{
275+
Message: "Extension does not exist",
276+
Detail: "Please check the publisher and extension name",
277+
},
278+
},
279+
{
280+
Name: "LatestExtensionExists",
281+
Path: "/api/vscode/vscodevim/vim/latest",
282+
Method: http.MethodGet,
283+
Status: http.StatusOK,
284+
Response: exts[0],
285+
},
269286
}
270287

271288
for _, c := range cases {
@@ -324,11 +341,11 @@ func TestAPI(t *testing.T) {
324341
require.Equal(t, c.Status, resp.StatusCode)
325342

326343
if c.Response != nil {
327-
// Copy the request ID so the objects can match.
328344
if a, aok := c.Response.(*httpapi.ErrorResponse); aok {
329345
var body httpapi.ErrorResponse
330346
err := json.NewDecoder(resp.Body).Decode(&body)
331347
require.NoError(t, err)
348+
// Copy the request ID so the objects can match.
332349
a.RequestID = body.RequestID
333350
require.Equal(t, c.Response, &body)
334351
} else if c.Status == http.StatusMovedPermanently {
@@ -337,6 +354,11 @@ func TestAPI(t *testing.T) {
337354
b, err := io.ReadAll(resp.Body)
338355
require.NoError(t, err)
339356
require.Equal(t, a, string(b))
357+
} else if _, aok := c.Response.(*database.Extension); aok {
358+
var body database.Extension
359+
err := json.NewDecoder(resp.Body).Decode(&body)
360+
require.NoError(t, err)
361+
require.Equal(t, c.Response, &body)
340362
} else {
341363
var body api.QueryResponse
342364
err := json.NewDecoder(resp.Body).Decode(&body)

cli/add_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func TestAdd(t *testing.T) {
165165
_, err := os.Stat(dest)
166166
require.NoError(t, err)
167167
// Should tell you where it went.
168-
id := storage.ExtensionID(ext.Publisher, ext.Name, ext.LatestVersion)
168+
id := storage.ExtensionIDWithVersion(ext.Publisher, ext.Name, ext.LatestVersion)
169169
require.Contains(t, output, fmt.Sprintf("Unpacked %s to %s", id, dest))
170170
// Should mention the dependencies and pack.
171171
require.Contains(t, output, fmt.Sprintf("%s has %d dep", id, len(ext.Dependencies)))

database/nodb.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func (db *NoDB) GetExtensions(ctx context.Context, filter Filter, flags Flag, ba
5858
start := time.Now()
5959
err := db.Storage.WalkExtensions(ctx, func(manifest *storage.VSIXManifest, versions []storage.Version) error {
6060
vscodeExt := convertManifestToExtension(manifest)
61+
// TODO: Could return early if ExtensionID or ExtensionName match.
6162
if matched, distances := getMatches(vscodeExt, filter); matched {
6263
vscodeExt.versions = versions
6364
vscodeExt.distances = distances
@@ -134,7 +135,8 @@ func getMatches(extension *noDBExtension, filter Filter) (bool, []int) {
134135
match(containsFold(extension.Categories, c.Value))
135136
case ExtensionName:
136137
// The value here is the fully qualified name `publisher.extension`.
137-
match(strings.EqualFold(extension.Publisher.PublisherName+"."+extension.Name, c.Value))
138+
name := storage.ExtensionIDWithoutVersion(extension.Publisher.PublisherName, extension.Name)
139+
match(strings.EqualFold(name, c.Value))
138140
case Target:
139141
// Unlike the other criteria the target is an AND so if it does not match
140142
// we can abort early.

storage/artifactory.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func NewArtifactoryStorage(ctx context.Context, options *ArtifactoryOptions) (*A
102102
if err != nil && !errors.Is(err, context.Canceled) {
103103
return err
104104
} else if err != nil {
105-
id := ExtensionID(identity.Publisher, identity.ID, ver.Version)
105+
id := ExtensionIDWithVersion(identity.Publisher, identity.ID, ver.Version)
106106
s.logger.Error(ctx, "Unable to read extension manifest", slog.Error(err),
107107
slog.F("id", id),
108108
slog.F("targetPlatform", ver.TargetPlatform))
@@ -400,7 +400,7 @@ func (s *Artifactory) WalkExtensions(ctx context.Context, fn func(manifest *VSIX
400400
if err != nil && errors.Is(err, context.Canceled) {
401401
return err
402402
} else if err != nil {
403-
id := ExtensionID(ext.publisher, ext.name, ext.versions[0].Version)
403+
id := ExtensionIDWithVersion(ext.publisher, ext.name, ext.versions[0].Version)
404404
s.logger.Error(ctx, "Unable to read extension manifest; extension will be ignored", slog.Error(err),
405405
slog.F("id", id),
406406
slog.F("targetPlatform", ext.versions[0].TargetPlatform))

storage/storage.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,18 +385,24 @@ func ReadVSIX(ctx context.Context, source string) ([]byte, error) {
385385
// ExtensionIDFromManifest returns the full ID of an extension without the the
386386
// platform, for example [email protected].
387387
func ExtensionIDFromManifest(manifest *VSIXManifest) string {
388-
return ExtensionID(
388+
return ExtensionIDWithVersion(
389389
manifest.Metadata.Identity.Publisher,
390390
manifest.Metadata.Identity.ID,
391391
manifest.Metadata.Identity.Version)
392392
}
393393

394-
// ExtensionID returns the full ID of an extension without the platform, for
394+
// ExtensionIDWithVersion returns the full ID of an extension without the platform, for
395395
// example [email protected].
396-
func ExtensionID(publisher, name, version string) string {
396+
func ExtensionIDWithVersion(publisher, name, version string) string {
397397
return fmt.Sprintf("%s.%s@%s", publisher, name, version)
398398
}
399399

400+
// ExtensionID returns the full ID of an extension without the platform or
401+
// version, for example publisher.name.
402+
func ExtensionIDWithoutVersion(publisher, name string) string {
403+
return fmt.Sprintf("%s.%s", publisher, name)
404+
}
405+
400406
// ExtensionVSIXNameFromManifest returns the full ID of an extension including
401407
// the platform if not universal, for example publisher.name-0.0.1 or
402408
// publisher.name-0.0.1@linux-x64.

storage/storage_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ func testVersions(t *testing.T, factory storageFactory) {
10701070
}
10711071
}
10721072

1073-
func TestExtensionID(t *testing.T) {
1073+
func TestExtensionIDFromManifest(t *testing.T) {
10741074
t.Parallel()
10751075

10761076
tests := []struct {
@@ -1105,6 +1105,69 @@ func TestExtensionID(t *testing.T) {
11051105
}
11061106
}
11071107

1108+
func TestExtensionIDWithVersion(t *testing.T) {
1109+
t.Parallel()
1110+
1111+
tests := []struct {
1112+
// expected is the expected id.
1113+
expected string
1114+
// publisher is the publisher of the extension.
1115+
publisher string
1116+
// extension is name of the extension.
1117+
extension string
1118+
// version is version of the extension.
1119+
version string
1120+
// name is the name of the test.
1121+
name string
1122+
}{
1123+
{
1124+
name: "OK",
1125+
expected: "foo.bar@baz",
1126+
publisher: "foo",
1127+
version: "baz",
1128+
extension: "bar",
1129+
},
1130+
}
1131+
1132+
for _, test := range tests {
1133+
test := test
1134+
t.Run(test.name, func(t *testing.T) {
1135+
t.Parallel()
1136+
require.Equal(t, test.expected, storage.ExtensionIDWithVersion(test.publisher, test.extension, test.version))
1137+
})
1138+
}
1139+
}
1140+
1141+
func TestExtensionIDWithoutVersion(t *testing.T) {
1142+
t.Parallel()
1143+
1144+
tests := []struct {
1145+
// expected is the expected id.
1146+
expected string
1147+
// publisher is the publisher of the extension.
1148+
publisher string
1149+
// extension is name of the extension.
1150+
extension string
1151+
// name is the name of the test.
1152+
name string
1153+
}{
1154+
{
1155+
name: "OK",
1156+
expected: "foo.bar",
1157+
publisher: "foo",
1158+
extension: "bar",
1159+
},
1160+
}
1161+
1162+
for _, test := range tests {
1163+
test := test
1164+
t.Run(test.name, func(t *testing.T) {
1165+
t.Parallel()
1166+
require.Equal(t, test.expected, storage.ExtensionIDWithoutVersion(test.publisher, test.extension))
1167+
})
1168+
}
1169+
}
1170+
11081171
func TestExtensionVSIXNameWithPlatform(t *testing.T) {
11091172
t.Parallel()
11101173

testutil/mockdb.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,11 @@ func (db *MockDB) GetExtensions(ctx context.Context, filter database.Filter, fla
4141
if len(filter.Criteria) == 0 {
4242
return nil, 0, nil
4343
}
44+
if len(filter.Criteria) > 1 && filter.Criteria[1].Type == database.ExtensionName {
45+
if strings.HasPrefix(filter.Criteria[1].Value, "notexist") {
46+
return nil, 0, nil
47+
}
48+
return db.exts[:1], 1, nil
49+
}
4450
return db.exts, len(db.exts), nil
4551
}

0 commit comments

Comments
 (0)