Skip to content

Commit 6ea4722

Browse files
taorijoelverhagenloic-sharma
authored
Improved link functionality for nuget.org (#7942)
* Support for packages/{id}/latest, packages/{id}/latest/prerelease and packages/{id}/latest/prerelease/{tagName} * more tests as per review * functional tests as per request * more tests as per review * functional tests as per request * refactoring * refactoring of complexity of the DisplayPackage method in the PackagesController * wip sorting fixes * wip semver sorting fixes * changed according to review * semver sort fixes done * unit test fix * regression fix * test fix * changes according to review * unit test fixes + code fixes * Fix functional tests and add absoluteLatest coverage * Update src/NuGetGallery.Services/PackageManagement/PackageFilter.cs Co-Authored-By: Loïc Sharma <[email protected]> * review changes Co-authored-by: Joel Verhagen <[email protected]> Co-authored-by: Loïc Sharma <[email protected]>
1 parent 8178e16 commit 6ea4722

21 files changed

Lines changed: 644 additions & 27 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
using System;
4+
using System.Web.Routing;
5+
6+
namespace NuGetGallery.Services.Helpers
7+
{
8+
public static class LatestPackageRouteVerifier
9+
{
10+
public static class SupportedRoutes
11+
{
12+
public const string LatestUrlString = "packages/{id}/latest";
13+
public const string LatestUrlWithPreleaseString = "packages/{id}/latest/prerelease";
14+
public const string LatestUrlWithPreleaseAndVersionString = "packages/{id}/latest/prerelease/{version}";
15+
public const string AbsoluteLatestUrlString = "absoluteLatest";
16+
}
17+
18+
public static bool IsLatestRoute(string routeUrl, out bool prerelease)
19+
{
20+
prerelease = false;
21+
22+
if (routeUrl == null)
23+
return false;
24+
25+
if (routeUrl.Equals(SupportedRoutes.LatestUrlString, StringComparison.InvariantCultureIgnoreCase))
26+
{
27+
return true;
28+
}
29+
if (routeUrl.Equals(SupportedRoutes.LatestUrlWithPreleaseString, StringComparison.InvariantCultureIgnoreCase))
30+
{
31+
prerelease = true;
32+
return true;
33+
}
34+
if (routeUrl.Equals(SupportedRoutes.LatestUrlWithPreleaseAndVersionString, StringComparison.InvariantCultureIgnoreCase))
35+
{
36+
prerelease = true;
37+
return true;
38+
}
39+
40+
return false;
41+
}
42+
}
43+
}

src/NuGetGallery.Services/NuGetGallery.Services.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,14 @@
142142
<Compile Include="Extensions\ScopeExtensions.cs" />
143143
<Compile Include="Extensions\UriExtensions.cs" />
144144
<Compile Include="Extensions\UserExtensions.cs" />
145+
<Compile Include="Helpers\LatestPackageRouteVerifier.cs" />
145146
<Compile Include="Helpers\MailAddressConverter.cs" />
146147
<Compile Include="Helpers\UploadHelper.cs" />
147148
<Compile Include="Models\LuceneIndexLocation.cs" />
148149
<Compile Include="Models\ReportPackageReason.cs" />
149150
<Compile Include="Models\StorageType.cs" />
150151
<Compile Include="PackageManagement\ActionOnNewPackageContext.cs" />
152+
<Compile Include="PackageManagement\IPackageFilter.cs" />
151153
<Compile Include="PackageManagement\IPackageOwnerRequestService.cs" />
152154
<Compile Include="PackageManagement\IIndexingService.cs" />
153155
<Compile Include="PackageManagement\IPackageOwnershipManagementService.cs" />
@@ -156,6 +158,8 @@
156158
<Compile Include="PackageManagement\IPackageVulnerabilityService.cs" />
157159
<Compile Include="PackageManagement\IReservedNamespaceService.cs" />
158160
<Compile Include="PackageManagement\PackageDeleteDecision.cs" />
161+
<Compile Include="PackageManagement\PackageFilter.cs" />
162+
<Compile Include="PackageManagement\PackageFilterContext.cs" />
159163
<Compile Include="PackageManagement\PackageHelper.cs" />
160164
<Compile Include="PackageManagement\PackageOwnerRequestService.cs" />
161165
<Compile Include="PackageManagement\PackageOwnershipManagementService.cs" />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using NuGet.Services.Entities;
6+
7+
namespace NuGetGallery.Services
8+
{
9+
public interface IPackageFilter
10+
{
11+
Package GetFiltered(IReadOnlyCollection<Package> packages, PackageFilterContext context);
12+
}
13+
}

src/NuGetGallery.Services/PackageManagement/IPackageService.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ Package FilterLatestPackage(
7474
int? semVerLevelKey = SemVerLevelKey.SemVer2,
7575
bool allowPrerelease = true);
7676

77+
Package FilterLatestPackageBySuffix(IReadOnlyCollection<Package> packages,
78+
string version, bool prerelease);
79+
7780
IEnumerable<Package> FindPackagesByOwner(User user, bool includeUnlisted, bool includeVersions = false);
7881

7982
IEnumerable<Package> FindPackagesByAnyMatchingOwner(User user, bool includeUnlisted, bool includeVersions = false);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Web.Routing;
8+
using NuGet.Services.Entities;
9+
using NuGetGallery.Services.Helpers;
10+
11+
namespace NuGetGallery.Services
12+
{
13+
public class PackageFilter : IPackageFilter
14+
{
15+
private readonly IPackageService _packageService;
16+
17+
public PackageFilter(IPackageService packageService)
18+
{
19+
_packageService = packageService ?? throw new ArgumentNullException(nameof(packageService));
20+
}
21+
22+
/// <inheritdoc />
23+
public Package GetFiltered(IReadOnlyCollection<Package> packages, PackageFilterContext context)
24+
{
25+
Package result = null;
26+
string routeUrl = null;
27+
if (context.RouteBase is Route route)
28+
{
29+
routeUrl = route.Url;
30+
}
31+
32+
var version = context.Version;
33+
34+
if (string.Equals(version, LatestPackageRouteVerifier.SupportedRoutes.AbsoluteLatestUrlString, StringComparison.InvariantCultureIgnoreCase))
35+
{
36+
// The user is looking for the absolute latest version and not an exact version.
37+
result = packages.FirstOrDefault(p => p.IsLatestSemVer2);
38+
}
39+
40+
result = result ?? _packageService.FilterExactPackage(packages, version);
41+
42+
if (LatestPackageRouteVerifier.IsLatestRoute(routeUrl, out var preRelease))
43+
{
44+
result = result ?? _packageService.FilterLatestPackageBySuffix(packages, version, preRelease);
45+
}
46+
47+
result = result ?? _packageService.FilterLatestPackage(packages, SemVerLevelKey.SemVer2, allowPrerelease: true);
48+
49+
return result;
50+
}
51+
}
52+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Web.Routing;
5+
6+
namespace NuGetGallery.Services
7+
{
8+
public class PackageFilterContext
9+
{
10+
public RouteBase RouteBase { get; }
11+
12+
public string Version { get; }
13+
14+
public PackageFilterContext(RouteBase routeBase, string version)
15+
{
16+
RouteBase = routeBase;
17+
Version = version;
18+
}
19+
}
20+
}

src/NuGetGallery.Services/PackageManagement/PackageService.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,51 @@ public virtual Package FilterLatestPackage(
227227
allowPrerelease);
228228
}
229229

230+
/// <inheritdoc />
231+
public Package FilterLatestPackageBySuffix(IReadOnlyCollection<Package> packages, string version, bool prerelease)
232+
{
233+
IEnumerable<Package> GetSortedFiltered(IEnumerable<Package> localPackages, bool applyPrereleaseFilter = true)
234+
{
235+
var semvered = localPackages
236+
.Select(package => new {package, semVer= NuGetVersion.Parse(package.NormalizedVersion)})
237+
.ToList();
238+
239+
return semvered
240+
.Where(d => d.semVer.IsPrerelease == prerelease || !applyPrereleaseFilter)
241+
.OrderByDescending(d => d.semVer)
242+
.Select(d => d.package)
243+
.ToList();
244+
}
245+
246+
Package GetPrereleaseByVersion()
247+
{
248+
if (string.IsNullOrEmpty(version))
249+
{
250+
return GetSortedFiltered(packages)
251+
.FirstOrDefault();
252+
}
253+
else
254+
{
255+
return GetSortedFiltered(packages.Where(package => package.NormalizedVersion.IndexOf(version, StringComparison.InvariantCultureIgnoreCase) >= 0))
256+
.FirstOrDefault();
257+
}
258+
}
259+
260+
Package GetLatestPrerelease()
261+
{
262+
return GetSortedFiltered(packages)
263+
.FirstOrDefault();
264+
}
265+
266+
Package GetLatestStable()
267+
{
268+
return GetSortedFiltered(packages, false)
269+
.FirstOrDefault();
270+
}
271+
272+
return GetPrereleaseByVersion() ?? GetLatestPrerelease() ?? GetLatestStable();
273+
}
274+
230275
private static Package FilterLatestPackageHelper(
231276
IReadOnlyCollection<Package> packages,
232277
int? semVerLevelKey,

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
using NuGetGallery.Infrastructure.Search;
5656
using NuGetGallery.Infrastructure.Search.Correlation;
5757
using NuGetGallery.Security;
58+
using NuGetGallery.Services;
5859
using Role = NuGet.Services.Entities.Role;
5960
using SecretReaderFactory = NuGetGallery.Configuration.SecretReader.SecretReaderFactory;
6061

@@ -291,6 +292,11 @@ protected override void Load(ContainerBuilder builder)
291292
.As<IPackageService>()
292293
.InstancePerLifetimeScope();
293294

295+
builder.RegisterType<PackageFilter>()
296+
.AsSelf()
297+
.As<IPackageFilter>()
298+
.InstancePerLifetimeScope();
299+
294300
builder.RegisterType<PackageDeleteService>()
295301
.AsSelf()
296302
.As<IPackageDeleteService>()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Web;
6+
using System.Web.Mvc;
7+
using System.Web.Routing;
8+
using NuGet.Versioning;
9+
using NuGetGallery.Services.Helpers;
10+
11+
namespace NuGetGallery
12+
{
13+
public class LatestVersionRouteConstraint : IRouteConstraint
14+
{
15+
/// <inheritdoc />
16+
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
17+
{
18+
object versionValue;
19+
if (!values.TryGetValue(parameterName, out versionValue))
20+
{
21+
return true;
22+
}
23+
24+
if (versionValue == null || versionValue == UrlParameter.Optional)
25+
{
26+
return true;
27+
}
28+
29+
string versionText = versionValue.ToString();
30+
if (versionText.Length == 0)
31+
{
32+
return true;
33+
}
34+
35+
if (route.Url.Equals(LatestPackageRouteVerifier.SupportedRoutes.LatestUrlString, StringComparison.InvariantCultureIgnoreCase)
36+
|| route.Url.Equals(LatestPackageRouteVerifier.SupportedRoutes.LatestUrlWithPreleaseString, StringComparison.InvariantCultureIgnoreCase)
37+
|| route.Url.Equals(LatestPackageRouteVerifier.SupportedRoutes.LatestUrlWithPreleaseAndVersionString, StringComparison.InvariantCultureIgnoreCase))
38+
return true;
39+
40+
NuGetVersion ignored;
41+
return NuGetVersion.TryParse(versionText, out ignored);
42+
}
43+
}
44+
}

src/NuGetGallery/App_Start/Routes.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Web.Mvc;
44
using System.Web.Routing;
55
using NuGetGallery.Controllers;
6+
using NuGetGallery.Services.Helpers;
67
using RouteMagic;
78

89
namespace NuGetGallery
@@ -229,6 +230,26 @@ public static void RegisterUIRoutes(RouteCollection routes)
229230
},
230231
new { version = new VersionRouteConstraint() });
231232

233+
routes.MapRoute(
234+
RouteName.DisplayReleasePackage,
235+
LatestPackageRouteVerifier.SupportedRoutes.LatestUrlString,
236+
new
237+
{
238+
controller = "Packages",
239+
action = "DisplayPackage",
240+
});
241+
242+
routes.MapRoute(
243+
RouteName.DisplayPrereleasePackage,
244+
LatestPackageRouteVerifier.SupportedRoutes.LatestUrlWithPreleaseAndVersionString,
245+
new
246+
{
247+
controller = "Packages",
248+
action = "DisplayPackage",
249+
version = UrlParameter.Optional
250+
},
251+
new {version = new LatestVersionRouteConstraint()});
252+
232253
routes.MapRoute(
233254
RouteName.DisplayPackageFeed,
234255
"packages/{id}/atom.xml",

0 commit comments

Comments
 (0)