Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0bb1aca

Browse files
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 := range10 {
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 publisher.name@0.0.1.
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 publisher.name@0.0.1.
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)

AltStyle によって変換されたページ (->オリジナル) /