diff --git a/src/Catalog/NuGet.Services.Metadata.Catalog.csproj b/src/Catalog/NuGet.Services.Metadata.Catalog.csproj
index a54df06e2..78170008a 100644
--- a/src/Catalog/NuGet.Services.Metadata.Catalog.csproj
+++ b/src/Catalog/NuGet.Services.Metadata.Catalog.csproj
@@ -108,6 +108,7 @@
+
diff --git a/src/Catalog/PackageCatalogItem.cs b/src/Catalog/PackageCatalogItem.cs
index 2385c37a1..101b9cc4e 100644
--- a/src/Catalog/PackageCatalogItem.cs
+++ b/src/Catalog/PackageCatalogItem.cs
@@ -21,13 +21,22 @@ public class PackageCatalogItem : AppendOnlyCatalogItem
public DateTime? CreatedDate { get; }
public DateTime? LastEditedDate { get; }
public DateTime? PublishedDate { get; }
-
- public PackageCatalogItem(NupkgMetadata nupkgMetadata, DateTime? createdDate = null, DateTime? lastEditedDate = null, DateTime? publishedDate = null, string licenseNames = null, string licenseReportUrl = null)
+ public PackageDeprecationItem Deprecation { get; }
+
+ public PackageCatalogItem(
+ NupkgMetadata nupkgMetadata,
+ DateTime? createdDate = null,
+ DateTime? lastEditedDate = null,
+ DateTime? publishedDate = null,
+ string licenseNames = null,
+ string licenseReportUrl = null,
+ PackageDeprecationItem deprecation = null)
{
NupkgMetadata = nupkgMetadata;
CreatedDate = createdDate;
LastEditedDate = lastEditedDate;
PublishedDate = publishedDate;
+ Deprecation = deprecation;
}
public override IGraph CreateContentGraph(CatalogContext context)
@@ -94,6 +103,52 @@ public override IGraph CreateContentGraph(CatalogContext context)
// identity and version
SetIdVersionFromGraph(graph);
+ // deprecation
+ if (Deprecation != null)
+ {
+ // assert deprecation root node to subject
+ var deprecationPredicate = graph.CreateUriNode(Schema.Predicates.Deprecation);
+ var deprecationRootNode = graph.CreateUriNode(new Uri(resource.Subject.ToString() + "#deprecation"));
+ graph.Assert(resource.Subject, deprecationPredicate, deprecationRootNode);
+
+ // assert reasons to deprecation root node
+ var deprecationReasonRootNode = graph.CreateUriNode(Schema.Predicates.Reasons);
+ foreach (var reason in Deprecation.Reasons)
+ {
+ var reasonNode = graph.CreateLiteralNode(reason);
+ graph.Assert(deprecationRootNode, deprecationReasonRootNode, reasonNode);
+ }
+
+ // assert message to deprecation root node
+ if (Deprecation.Message != null)
+ {
+ graph.Assert(
+ deprecationRootNode,
+ graph.CreateUriNode(Schema.Predicates.Message),
+ graph.CreateLiteralNode(Deprecation.Message));
+ }
+
+ if (Deprecation.AlternatePackageId != null)
+ {
+ // assert alternate package root node to deprecation root node
+ var deprecationAlternatePackagePredicate = graph.CreateUriNode(Schema.Predicates.AlternatePackage);
+ var deprecationAlternatePackageRootNode = graph.CreateUriNode(new Uri(resource.Subject.ToString() + "#deprecation/alternatePackage"));
+ graph.Assert(deprecationRootNode, deprecationAlternatePackagePredicate, deprecationAlternatePackageRootNode);
+
+ // assert id to alternate package root node
+ graph.Assert(
+ deprecationAlternatePackageRootNode,
+ graph.CreateUriNode(Schema.Predicates.Id),
+ graph.CreateLiteralNode(Deprecation.AlternatePackageId));
+
+ // assert version range to alternate package root node
+ graph.Assert(
+ deprecationAlternatePackageRootNode,
+ graph.CreateUriNode(Schema.Predicates.Range),
+ graph.CreateLiteralNode(Deprecation.AlternatePackageRange));
+ }
+ }
+
return graph;
}
@@ -143,6 +198,11 @@ public override StorageContent CreateContent(CatalogContext context)
graph.Assert(resource.Subject, timeStampPredicate, graph.CreateLiteralNode(TimeStamp.ToString("O"), Schema.DataTypes.DateTime));
graph.Assert(resource.Subject, commitIdPredicate, graph.CreateLiteralNode(CommitId.ToString()));
+ if (graph.GetTriples(Schema.Predicates.Deprecation).Count() > 1)
+ {
+ throw new ArgumentException("Package catalog items can only have a single deprecation.");
+ }
+
// create JSON content
JObject frame = context.GetJsonLdContext("context.PackageDetails.json", GetItemType());
diff --git a/src/Catalog/PackageDeprecationItem.cs b/src/Catalog/PackageDeprecationItem.cs
new file mode 100644
index 000000000..f3d611d27
--- /dev/null
+++ b/src/Catalog/PackageDeprecationItem.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace NuGet.Services.Metadata.Catalog
+{
+ public class PackageDeprecationItem
+ {
+ /// The list of reasons a package was deprecated.
+ /// An additional message associated with a package, if one exists.
+ ///
+ /// The ID of a package that can be used alternatively. Must be specified if is specified.
+ ///
+ ///
+ /// A string representing the version range of a package that can be used alternatively. Must be specified if is specified.
+ ///
+ public PackageDeprecationItem(
+ IReadOnlyList reasons,
+ string message,
+ string alternatePackageId,
+ string alternatePackageRange)
+ {
+ if (reasons == null)
+ {
+ throw new ArgumentNullException(nameof(reasons));
+ }
+
+ if (!reasons.Any())
+ {
+ throw new ArgumentException(nameof(reasons));
+ }
+
+ Reasons = reasons;
+ Message = message;
+ AlternatePackageId = alternatePackageId;
+ AlternatePackageRange = alternatePackageRange;
+
+ if (AlternatePackageId == null && AlternatePackageRange != null)
+ {
+ throw new ArgumentException(
+ "Cannot specify an alternate package version range if an alternate package ID is not provided.",
+ nameof(AlternatePackageRange));
+ }
+
+ if (AlternatePackageId != null && AlternatePackageRange == null)
+ {
+ throw new ArgumentException(
+ "Cannot specify an alternate package ID if an alternate package version range is not provided.",
+ nameof(AlternatePackageId));
+ }
+ }
+
+ public IReadOnlyList Reasons { get; }
+ public string Message { get; }
+ public string AlternatePackageId { get; }
+ public string AlternatePackageRange { get; }
+ }
+}
diff --git a/src/Catalog/Schema.cs b/src/Catalog/Schema.cs
index 7c63925cb..0a090f1f2 100644
--- a/src/Catalog/Schema.cs
+++ b/src/Catalog/Schema.cs
@@ -153,6 +153,12 @@ public static class Predicates
public static readonly Uri LicenseFile = new Uri(Prefixes.NuGet + "licenseFile");
public static readonly Uri IconFile = new Uri(Prefixes.NuGet + "iconFile");
+
+ public static readonly Uri Deprecation = new Uri(Prefixes.NuGet + "deprecation");
+
+ public static readonly Uri Reasons = new Uri(Prefixes.NuGet + "reasons");
+ public static readonly Uri Message = new Uri(Prefixes.NuGet + "message");
+ public static readonly Uri AlternatePackage = new Uri(Prefixes.NuGet + "alternatePackage");
}
}
}
diff --git a/src/Catalog/context/PackageDetails.json b/src/Catalog/context/PackageDetails.json
index 21a141303..921f17e59 100644
--- a/src/Catalog/context/PackageDetails.json
+++ b/src/Catalog/context/PackageDetails.json
@@ -11,6 +11,7 @@
"published": { "@type": "xsd:dateTime" },
"created": { "@type": "xsd:dateTime" },
"lastEdited" : { "@type" : "xsd:dateTime" },
- "catalog:commitTimeStamp" : { "@type" : "xsd:dateTime" }
+ "catalog:commitTimeStamp" : { "@type" : "xsd:dateTime" },
+ "reasons" : { "@container" : "@set" }
}
}
diff --git a/tests/CatalogTests/CatalogTests.csproj b/tests/CatalogTests/CatalogTests.csproj
index ea6579b88..7dc828779 100644
--- a/tests/CatalogTests/CatalogTests.csproj
+++ b/tests/CatalogTests/CatalogTests.csproj
@@ -104,6 +104,7 @@
+
diff --git a/tests/CatalogTests/Helpers/FeedHelpersTests.cs b/tests/CatalogTests/Helpers/FeedHelpersTests.cs
index cd62e64ef..1c7030ce9 100644
--- a/tests/CatalogTests/Helpers/FeedHelpersTests.cs
+++ b/tests/CatalogTests/Helpers/FeedHelpersTests.cs
@@ -303,13 +303,36 @@ public async Task DownloadMetadata2CatalogAsync_WhenCreatedPackagesIsTrue_WithNo
}
}
+ public enum PackageDeprecationItemState
+ {
+ NotDeprecated,
+ DeprecatedWithSingleReason,
+ DeprecatedWithMessage,
+ DeprecatedWithAlternate
+ }
+
+ public static IEnumerable