From 86ff1966e9d2ab26c7a6b5132e4c4fe8d4d50593 Mon Sep 17 00:00:00 2001
From: Bart Koelman <10324372+bkoelman@users.noreply.github.com>
Date: Sat, 23 Mar 2024 17:25:16 +0100
Subject: [PATCH] Add support for configuring the visibility of the
"describedby" link
---
.../Resources/Annotations/LinkTypes.shared.cs | 13 ++++++-----
.../Annotations/RelationshipAttribute.cs | 2 +-
.../RelationshipAttribute.netstandard.cs | 2 +-
.../ResourceLinksAttribute.shared.cs | 6 ++---
.../Serialization/Response/LinkBuilder.cs | 13 +++++++----
.../UnitTests/Links/LinkInclusionTests.cs | 23 ++++++++++++++++++-
6 files changed, 42 insertions(+), 17 deletions(-)
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs
index 7e996828b9..632b8b9ed3 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/LinkTypes.shared.cs
@@ -3,10 +3,11 @@ namespace JsonApiDotNetCore.Resources.Annotations;
[Flags]
public enum LinkTypes
{
- Self = 1 << 0,
- Related = 1 << 1,
- Pagination = 1 << 2,
- NotConfigured = 1 << 3,
- None = 1 << 4,
- All = Self | Related | Pagination
+ NotConfigured = 0,
+ None = 1 << 0,
+ Self = 1 << 1,
+ Related = 1 << 2,
+ DescribedBy = 1 << 3,
+ Pagination = 1 << 4,
+ All = Self | Related | DescribedBy | Pagination
}
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
index 0b4848ada1..492af08c60 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.cs
@@ -70,7 +70,7 @@ internal set
/// Configures which links to write in the relationship-level links object for this relationship. Defaults to ,
/// which falls back to and then falls back to RelationshipLinks in global options.
///
- public LinkTypes Links { get; set; } = LinkTypes.NotConfigured;
+ public LinkTypes Links { get; set; }
///
/// Whether or not this relationship can be included using the include query string parameter. This is true by default.
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs
index d7af592564..054d7b1af3 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/RelationshipAttribute.netstandard.cs
@@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.Resources.Annotations;
public abstract class RelationshipAttribute : ResourceFieldAttribute
{
///
- public LinkTypes Links { get; set; } = LinkTypes.NotConfigured;
+ public LinkTypes Links { get; set; }
///
[Obsolete("Use AllowInclude in Capabilities instead.")]
diff --git a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs
index 010f87db5e..2c2c353f3a 100644
--- a/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs
+++ b/src/JsonApiDotNetCore.Annotations/Resources/Annotations/ResourceLinksAttribute.shared.cs
@@ -13,18 +13,18 @@ public sealed class ResourceLinksAttribute : Attribute
/// Configures which links to write in the top-level links object for this resource type. Defaults to , which falls
/// back to TopLevelLinks in global options.
///
- public LinkTypes TopLevelLinks { get; set; } = LinkTypes.NotConfigured;
+ public LinkTypes TopLevelLinks { get; set; }
///
/// Configures which links to write in the resource-level links object for this resource type. Defaults to , which
/// falls back to ResourceLinks in global options.
///
- public LinkTypes ResourceLinks { get; set; } = LinkTypes.NotConfigured;
+ public LinkTypes ResourceLinks { get; set; }
///
/// Configures which links to write in the relationship-level links object for all relationships of this resource type. Defaults to
/// , which falls back to RelationshipLinks in global options. This can be overruled per relationship by setting
/// .
///
- public LinkTypes RelationshipLinks { get; set; } = LinkTypes.NotConfigured;
+ public LinkTypes RelationshipLinks { get; set; }
}
diff --git a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs
index 7740141002..ffb8b8e9b7 100644
--- a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs
+++ b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs
@@ -100,12 +100,15 @@ private static string NoAsyncSuffix(string actionName)
SetPaginationInTopLevelLinks(resourceType!, links);
}
- string? documentDescriptionUrl = _documentDescriptionLinkProvider.GetUrl();
-
- if (!string.IsNullOrEmpty(documentDescriptionUrl))
+ if (ShouldIncludeTopLevelLink(LinkTypes.DescribedBy, resourceType))
{
- var requestUri = new Uri(HttpContext.Request.GetEncodedUrl());
- links.DescribedBy = UriNormalizer.Normalize(documentDescriptionUrl, _options.UseRelativeLinks, requestUri);
+ string? documentDescriptionUrl = _documentDescriptionLinkProvider.GetUrl();
+
+ if (!string.IsNullOrEmpty(documentDescriptionUrl))
+ {
+ var requestUri = new Uri(HttpContext.Request.GetEncodedUrl());
+ links.DescribedBy = UriNormalizer.Normalize(documentDescriptionUrl, _options.UseRelativeLinks, requestUri);
+ }
}
return links.HasValue() ? links : null;
diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Links/LinkInclusionTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Links/LinkInclusionTests.cs
index 94b9cb9386..9b8890618a 100644
--- a/test/JsonApiDotNetCoreTests/UnitTests/Links/LinkInclusionTests.cs
+++ b/test/JsonApiDotNetCoreTests/UnitTests/Links/LinkInclusionTests.cs
@@ -22,36 +22,49 @@ public sealed class LinkInclusionTests
[InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None)]
[InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self)]
[InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Related)]
+ [InlineData(LinkTypes.NotConfigured, LinkTypes.DescribedBy, LinkTypes.DescribedBy)]
[InlineData(LinkTypes.NotConfigured, LinkTypes.Pagination, LinkTypes.Pagination)]
[InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.All)]
[InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None)]
[InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.None)]
[InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.None)]
[InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.None)]
+ [InlineData(LinkTypes.None, LinkTypes.DescribedBy, LinkTypes.None)]
[InlineData(LinkTypes.None, LinkTypes.Pagination, LinkTypes.None)]
[InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.None)]
[InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self)]
[InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.Self)]
[InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.Self)]
[InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.Self)]
+ [InlineData(LinkTypes.Self, LinkTypes.DescribedBy, LinkTypes.Self)]
[InlineData(LinkTypes.Self, LinkTypes.Pagination, LinkTypes.Self)]
[InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.Self)]
[InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Related)]
[InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.Related)]
[InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.Related)]
[InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.Related)]
+ [InlineData(LinkTypes.Related, LinkTypes.DescribedBy, LinkTypes.Related)]
[InlineData(LinkTypes.Related, LinkTypes.Pagination, LinkTypes.Related)]
[InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.Related)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.NotConfigured, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.None, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.Self, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.Related, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.DescribedBy, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.Pagination, LinkTypes.DescribedBy)]
+ [InlineData(LinkTypes.DescribedBy, LinkTypes.All, LinkTypes.DescribedBy)]
[InlineData(LinkTypes.Pagination, LinkTypes.NotConfigured, LinkTypes.Pagination)]
[InlineData(LinkTypes.Pagination, LinkTypes.None, LinkTypes.Pagination)]
[InlineData(LinkTypes.Pagination, LinkTypes.Self, LinkTypes.Pagination)]
[InlineData(LinkTypes.Pagination, LinkTypes.Related, LinkTypes.Pagination)]
+ [InlineData(LinkTypes.Pagination, LinkTypes.DescribedBy, LinkTypes.Pagination)]
[InlineData(LinkTypes.Pagination, LinkTypes.Pagination, LinkTypes.Pagination)]
[InlineData(LinkTypes.Pagination, LinkTypes.All, LinkTypes.Pagination)]
[InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All)]
[InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.All)]
[InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.All)]
[InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.All)]
+ [InlineData(LinkTypes.All, LinkTypes.DescribedBy, LinkTypes.All)]
[InlineData(LinkTypes.All, LinkTypes.Pagination, LinkTypes.All)]
[InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.All)]
public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInResourceType, LinkTypes linksInOptions, LinkTypes expected)
@@ -88,7 +101,7 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso
var linkGenerator = new FakeLinkGenerator();
var controllerResourceMapping = new FakeControllerResourceMapping();
var paginationParser = new PaginationParser();
- var documentDescriptionLinkProvider = new NoDocumentDescriptionLinkProvider();
+ var documentDescriptionLinkProvider = new NonEmptyDocumentDescriptionLinkProvider();
var linkBuilder = new LinkBuilder(options, request, paginationContext, httpContextAccessor, linkGenerator, controllerResourceMapping, paginationParser,
documentDescriptionLinkProvider);
@@ -435,4 +448,12 @@ public override string GetUriByAddress(TAddress address, RouteValueDic
throw new NotImplementedException();
}
}
+
+ private sealed class NonEmptyDocumentDescriptionLinkProvider : IDocumentDescriptionLinkProvider
+ {
+ public string GetUrl()
+ {
+ return "openapi.yaml";
+ }
+ }
}