Skip to content

Commit 03d692c

Browse files
authored
Implement semVerLevel query parameter (this is SemVer 2.0.0 protocol support) (#29)
Serialize and deserialize full version string from cache file Don't include version metadata in OData self links, since version metadata is not part of the version identity and IIS by default rejects pluses in the path part of URLs.
1 parent fe567d4 commit 03d692c

25 files changed

Lines changed: 1151 additions & 196 deletions

src/NuGet.Server.Core/DataServices/PackageExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ namespace NuGet.Server.Core.DataServices
1111
{
1212
public static class PackageExtensions
1313
{
14-
public static ODataPackage AsODataPackage(this IServerPackage package)
14+
public static ODataPackage AsODataPackage(this IServerPackage package, ClientCompatibility compatibility)
1515
{
1616
return new ODataPackage
1717
{
1818
Id = package.Id,
19-
Version = package.Version.ToString(),
19+
Version = package.Version.ToOriginalString(),
2020
NormalizedVersion = package.Version.ToNormalizedString(),
2121
IsPrerelease = !package.IsReleaseVersion(),
2222
Title = package.Title,
@@ -39,8 +39,8 @@ public static ODataPackage AsODataPackage(this IServerPackage package)
3939
PackageSize = package.PackageSize,
4040
Copyright = package.Copyright,
4141
Tags = package.Tags,
42-
IsAbsoluteLatestVersion = package.IsAbsoluteLatestVersion,
43-
IsLatestVersion = package.IsLatestVersion,
42+
IsAbsoluteLatestVersion = compatibility.AllowSemVer2 ? package.SemVer2IsAbsoluteLatest : package.SemVer1IsAbsoluteLatest,
43+
IsLatestVersion = compatibility.AllowSemVer2 ? package.SemVer2IsLatest : package.SemVer1IsLatest,
4444
Listed = package.Listed,
4545
VersionDownloadCount = package.DownloadCount,
4646
MinClientVersion = package.MinClientVersion == null ? null : package.MinClientVersion.ToString(),
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
6+
namespace NuGet.Server.Core.Infrastructure
7+
{
8+
public class ClientCompatibility
9+
{
10+
/// <summary>
11+
/// A set of client compatibilities with yielding the maximum set of packages.
12+
/// </summary>
13+
public static readonly ClientCompatibility Max = new ClientCompatibility(
14+
semVerLevel: new SemanticVersion("2.0.0"));
15+
16+
/// <summary>
17+
/// A set of client compatibilities with yielding the minimum set of packages.
18+
/// </summary>
19+
public static readonly ClientCompatibility Default = new ClientCompatibility(
20+
semVerLevel: new SemanticVersion("1.0.0"));
21+
22+
public ClientCompatibility(SemanticVersion semVerLevel)
23+
{
24+
if (semVerLevel == null)
25+
{
26+
throw new ArgumentNullException(nameof(semVerLevel));
27+
}
28+
29+
SemVerLevel = semVerLevel;
30+
AllowSemVer2 = semVerLevel.Version.Major >= 2;
31+
}
32+
33+
public SemanticVersion SemVerLevel { get; }
34+
35+
public bool AllowSemVer2 { get; }
36+
}
37+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
namespace NuGet.Server.Core.Infrastructure
5+
{
6+
public static class ClientCompatibilityFactory
7+
{
8+
public static ClientCompatibility FromProperties(string unparsedSemVerLevel)
9+
{
10+
SemanticVersion semVerLevel;
11+
if (string.IsNullOrWhiteSpace(unparsedSemVerLevel) ||
12+
!SemanticVersion.TryParse(unparsedSemVerLevel, out semVerLevel))
13+
{
14+
semVerLevel = ClientCompatibility.Default.SemVerLevel;
15+
}
16+
17+
if (semVerLevel == ClientCompatibility.Default.SemVerLevel)
18+
{
19+
return ClientCompatibility.Default;
20+
}
21+
else if (semVerLevel == ClientCompatibility.Max.SemVerLevel)
22+
{
23+
return ClientCompatibility.Max;
24+
}
25+
26+
return new ClientCompatibility(semVerLevel);
27+
}
28+
}
29+
}

src/NuGet.Server.Core/Infrastructure/IServerPackage.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ public interface IServerPackage
2626
IEnumerable<PackageDependencySet> DependencySets { get; }
2727
string Copyright { get; }
2828
string Tags { get; }
29-
bool IsAbsoluteLatestVersion { get; }
30-
bool IsLatestVersion { get; }
29+
bool SemVer1IsAbsoluteLatest { get; }
30+
bool SemVer1IsLatest { get; }
31+
bool SemVer2IsAbsoluteLatest { get; }
32+
bool SemVer2IsLatest { get; }
3133
bool Listed { get; }
3234
Version MinClientVersion { get; }
3335
string Language { get; }

src/NuGet.Server.Core/Infrastructure/IServerPackageRepository.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ public interface IServerPackageRepository
1313

1414
Task AddPackageAsync(IPackage package, CancellationToken token);
1515

16-
Task<IEnumerable<IServerPackage>> GetPackagesAsync(CancellationToken token);
16+
Task<IEnumerable<IServerPackage>> GetPackagesAsync(ClientCompatibility compatibility, CancellationToken token);
1717

1818
Task<IEnumerable<IServerPackage>> SearchAsync(
1919
string searchTerm,
2020
IEnumerable<string> targetFrameworks,
2121
bool allowPrereleaseVersions,
22+
ClientCompatibility compatibility,
2223
CancellationToken token);
2324

2425
Task ClearCacheAsync(CancellationToken token);

src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
34
using System.Collections.Generic;
45
using System.IO;
56
using System.Linq;
7+
using System.Runtime.Serialization;
68
using System.Text;
79
using Newtonsoft.Json;
810

@@ -11,24 +13,42 @@ namespace NuGet.Server.Core.Infrastructure
1113
public class JsonNetPackagesSerializer
1214
: IPackagesSerializer
1315
{
16+
private static readonly SemanticVersion CurrentSchemaVersion = new SemanticVersion("2.0.0");
17+
1418
private readonly JsonSerializer _serializer = new JsonSerializer
1519
{
16-
Formatting = Formatting.None
20+
Formatting = Formatting.None,
21+
NullValueHandling = NullValueHandling.Ignore
1722
};
1823

1924
public void Serialize(IEnumerable<ServerPackage> packages, Stream stream)
2025
{
2126
using (var writer = new JsonTextWriter(new StreamWriter(stream, Encoding.UTF8, 1024, true)))
2227
{
23-
_serializer.Serialize(writer, packages.ToList());
28+
_serializer.Serialize(
29+
writer,
30+
new SerializedServerPackages
31+
{
32+
SchemaVersion = CurrentSchemaVersion,
33+
Packages = packages.ToList()
34+
});
2435
}
2536
}
2637

2738
public IEnumerable<ServerPackage> Deserialize(Stream stream)
2839
{
2940
using (var reader = new JsonTextReader(new StreamReader(stream, Encoding.UTF8, false, 1024, true)))
3041
{
31-
return _serializer.Deserialize<List<ServerPackage>>(reader);
42+
var packages = _serializer.Deserialize<SerializedServerPackages>(reader);
43+
44+
if (packages == null || packages.SchemaVersion != CurrentSchemaVersion)
45+
{
46+
throw new SerializationException(
47+
$"The expected schema version of the packages file is '{CurrentSchemaVersion}', not " +
48+
$"'{packages?.SchemaVersion}'.");
49+
}
50+
51+
return packages.Packages;
3252
}
3353
}
3454
}

src/NuGet.Server.Core/Infrastructure/SemanticVersionJsonConverter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
3838

3939
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
4040
{
41-
_serializer.Serialize(writer, value.ToString());
41+
var semanticVersion = value as SemanticVersion;
42+
if (semanticVersion != null)
43+
{
44+
_serializer.Serialize(writer, semanticVersion.ToOriginalString());
45+
}
46+
else
47+
{
48+
_serializer.Serialize(writer, value.ToString());
49+
}
4250
}
4351
}
4452
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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 Newtonsoft.Json;
6+
7+
namespace NuGet.Server.Core.Infrastructure
8+
{
9+
public class SerializedServerPackages
10+
{
11+
[JsonRequired, JsonConverter(typeof(SemanticVersionJsonConverter))]
12+
public SemanticVersion SchemaVersion { get; set; }
13+
14+
[JsonRequired]
15+
public IEnumerable<ServerPackage> Packages { get; set; }
16+
}
17+
}

src/NuGet.Server.Core/Infrastructure/ServerPackage.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ public ServerPackage(
4040
MinClientVersion = package.MinClientVersion;
4141
ReportAbuseUrl = package.ReportAbuseUrl;
4242
DownloadCount = package.DownloadCount;
43-
IsAbsoluteLatestVersion = package.IsAbsoluteLatestVersion;
44-
IsLatestVersion = package.IsLatestVersion;
4543
Listed = package.Listed;
4644

4745
_dependencySets = package.DependencySets.ToList();
@@ -56,6 +54,11 @@ public ServerPackage(
5654
LastUpdated = packageDerivedData.LastUpdated;
5755
Created = packageDerivedData.Created;
5856
FullPath = packageDerivedData.FullPath;
57+
58+
SemVer1IsAbsoluteLatest = false;
59+
SemVer1IsLatest = false;
60+
SemVer2IsAbsoluteLatest = false;
61+
SemVer2IsLatest = false;
5962
}
6063

6164
[JsonRequired]
@@ -144,9 +147,13 @@ public IEnumerable<FrameworkName> GetSupportedFrameworks()
144147
return _supportedFrameworks;
145148
}
146149

147-
public bool IsAbsoluteLatestVersion { get; set; }
150+
public bool SemVer1IsAbsoluteLatest { get; set; }
151+
152+
public bool SemVer1IsLatest { get; set; }
153+
154+
public bool SemVer2IsAbsoluteLatest { get; set; }
148155

149-
public bool IsLatestVersion { get; set; }
156+
public bool SemVer2IsLatest { get; set; }
150157

151158
public bool Listed { get; set; }
152159

src/NuGet.Server.Core/Infrastructure/ServerPackageCache.cs

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,43 +168,77 @@ public void AddRange(IEnumerable<ServerPackage> entities)
168168

169169
private static void UpdateLatestVersions(IEnumerable<ServerPackage> packages)
170170
{
171-
var absoluteLatest = new ConcurrentDictionary<string, ServerPackage>();
172-
var latest = new ConcurrentDictionary<string, ServerPackage>();
171+
var semVer1AbsoluteLatest = InitializePackageDictionary();
172+
var semVer1Latest = InitializePackageDictionary();
173+
var semVer2AbsoluteLatest = InitializePackageDictionary();
174+
var semVer2Latest = InitializePackageDictionary();
173175

174176
// Visit packages
175177
Parallel.ForEach(packages, package =>
176178
{
177-
// Update package
178-
package.IsAbsoluteLatestVersion = false;
179-
package.IsLatestVersion = false;
179+
// Reset the package.
180+
package.SemVer1IsAbsoluteLatest = false;
181+
package.SemVer1IsLatest = false;
182+
package.SemVer2IsAbsoluteLatest = false;
183+
package.SemVer2IsLatest = false;
184+
185+
// Update the SemVer1 views.
186+
if (!package.Version.IsSemVer2())
187+
{
188+
UpdateLatestDictionary(semVer1AbsoluteLatest, package);
180189

181-
// Find the latest versions
182-
var id = package.Id.ToLowerInvariant();
190+
if (package.IsReleaseVersion())
191+
{
192+
UpdateLatestDictionary(semVer1Latest, package);
193+
}
194+
}
183195

184-
// Update with the highest version
185-
absoluteLatest.AddOrUpdate(id, package,
186-
(oldId, oldEntry) => oldEntry.Version < package.Version ? package : oldEntry);
196+
// Update the SemVer1 + SemVer2 views.
197+
UpdateLatestDictionary(semVer2AbsoluteLatest, package);
187198

188-
// Update latest for release versions
189199
if (package.IsReleaseVersion())
190200
{
191-
latest.AddOrUpdate(id, package,
192-
(oldId, oldEntry) => oldEntry.Version < package.Version ? package : oldEntry);
201+
UpdateLatestDictionary(semVer2Latest, package);
193202
}
194203
});
195204

196205
// Set version properties
197-
foreach (var entry in absoluteLatest.Values)
206+
foreach (var entry in semVer1AbsoluteLatest.Values)
207+
{
208+
entry.SemVer1IsAbsoluteLatest = true;
209+
}
210+
211+
foreach (var entry in semVer1Latest.Values)
212+
{
213+
entry.SemVer1IsLatest = true;
214+
}
215+
216+
foreach (var entry in semVer2AbsoluteLatest.Values)
198217
{
199-
entry.IsAbsoluteLatestVersion = true;
218+
entry.SemVer2IsAbsoluteLatest = true;
200219
}
201220

202-
foreach (var entry in latest.Values)
221+
foreach (var entry in semVer2Latest.Values)
203222
{
204-
entry.IsLatestVersion = true;
223+
entry.SemVer2IsLatest = true;
205224
}
206225
}
207226

227+
private static ConcurrentDictionary<string, ServerPackage> InitializePackageDictionary()
228+
{
229+
return new ConcurrentDictionary<string, ServerPackage>(StringComparer.OrdinalIgnoreCase);
230+
}
231+
232+
private static void UpdateLatestDictionary(
233+
ConcurrentDictionary<string, ServerPackage> dictionary,
234+
ServerPackage package)
235+
{
236+
dictionary.AddOrUpdate(
237+
package.Id,
238+
package,
239+
(oldId, oldEntry) => oldEntry.Version < package.Version ? package : oldEntry);
240+
}
241+
208242
public void Persist()
209243
{
210244
_syncLock.EnterWriteLock();

0 commit comments

Comments
 (0)