Skip to content

Commit e5a7084

Browse files
authored
Merge pull request #1630 from json-api-dotnet/merge-master-into-openapi
Merge master into openapi
2 parents 9657947 + 462a6e3 commit e5a7084

31 files changed

+969
-85
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"jetbrains.resharper.globaltools": {
6-
"version": "2024.2.6",
6+
"version": "2024.2.7",
77
"commands": [
88
"jb"
99
],

src/JsonApiDotNetCore/AtomicOperations/DefaultOperationFilter.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,37 @@
77
namespace JsonApiDotNetCore.AtomicOperations;
88

99
/// <inheritdoc />
10-
internal sealed class DefaultOperationFilter : IAtomicOperationFilter
10+
public class DefaultOperationFilter : IAtomicOperationFilter
1111
{
1212
/// <inheritdoc />
13-
public bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation)
13+
public virtual bool IsEnabled(ResourceType resourceType, WriteOperationKind writeOperation)
1414
{
15+
ArgumentGuard.NotNull(resourceType);
16+
17+
// To match the behavior of non-operations controllers:
18+
// If an operation is enabled on a base type, it is implicitly enabled on all derived types.
19+
ResourceType currentResourceType = resourceType;
20+
21+
while (true)
22+
{
23+
JsonApiEndpoints? endpoints = GetJsonApiEndpoints(currentResourceType);
24+
bool isEnabled = endpoints != null && Contains(endpoints.Value, writeOperation);
25+
26+
if (isEnabled || currentResourceType.BaseType == null)
27+
{
28+
return isEnabled;
29+
}
30+
31+
currentResourceType = currentResourceType.BaseType;
32+
}
33+
}
34+
35+
protected virtual JsonApiEndpoints? GetJsonApiEndpoints(ResourceType resourceType)
36+
{
37+
ArgumentGuard.NotNull(resourceType);
38+
1539
var resourceAttribute = resourceType.ClrType.GetCustomAttribute<ResourceAttribute>();
16-
return resourceAttribute != null && Contains(resourceAttribute.GenerateControllerEndpoints, writeOperation);
40+
return resourceAttribute?.GenerateControllerEndpoints;
1741
}
1842

1943
private static bool Contains(JsonApiEndpoints endpoints, WriteOperationKind writeOperation)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using JsonApiDotNetCore.Serialization.JsonConverters;
2+
3+
namespace JsonApiDotNetCore.Configuration;
4+
5+
internal sealed class DefaultJsonApiApplicationBuilderEvents : IJsonApiApplicationBuilderEvents
6+
{
7+
private readonly IJsonApiOptions _options;
8+
9+
public DefaultJsonApiApplicationBuilderEvents(IJsonApiOptions options)
10+
{
11+
ArgumentGuard.NotNull(options);
12+
13+
_options = options;
14+
}
15+
16+
public void ResourceGraphBuilt(IResourceGraph resourceGraph)
17+
{
18+
_options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
19+
}
20+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace JsonApiDotNetCore.Configuration;
2+
3+
internal interface IJsonApiApplicationBuilderEvents
4+
{
5+
void ResourceGraphBuilt(IResourceGraph resourceGraph);
6+
}

src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,15 @@ public interface IJsonApiOptions
176176

177177
/// <summary>
178178
/// Lists the JSON:API extensions that are turned on. Empty by default, but if your project contains a controller that derives from
179-
/// <see cref="BaseJsonApiOperationsController" />, the <see cref="JsonApiExtension.AtomicOperations" /> and
180-
/// <see cref="JsonApiExtension.RelaxedAtomicOperations" /> extensions are automatically added.
179+
/// <see cref="BaseJsonApiOperationsController" />, the <see cref="JsonApiMediaTypeExtension.AtomicOperations" /> and
180+
/// <see cref="JsonApiMediaTypeExtension.RelaxedAtomicOperations" /> extensions are automatically added.
181181
/// </summary>
182182
/// <remarks>
183183
/// To implement a custom JSON:API extension, add it here and override <see cref="JsonApiContentNegotiator.GetPossibleMediaTypes" /> to indicate which
184184
/// combinations of extensions are available, depending on the current endpoint. Use <see cref="IJsonApiRequest.Extensions" /> to obtain the active
185185
/// extensions when implementing extension-specific logic.
186186
/// </remarks>
187-
IReadOnlySet<JsonApiExtension> Extensions { get; }
187+
IReadOnlySet<JsonApiMediaTypeExtension> Extensions { get; }
188188

189189
/// <summary>
190190
/// Enables to customize the settings that are used by the <see cref="JsonSerializer" />.

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using JsonApiDotNetCore.QueryStrings;
88
using JsonApiDotNetCore.Repositories;
99
using JsonApiDotNetCore.Resources;
10-
using JsonApiDotNetCore.Serialization.JsonConverters;
1110
using JsonApiDotNetCore.Serialization.Request;
1211
using JsonApiDotNetCore.Serialization.Request.Adapters;
1312
using JsonApiDotNetCore.Serialization.Response;
@@ -74,6 +73,8 @@ public void ConfigureResourceGraph(ICollection<Type> dbContextTypes, Action<Reso
7473
_services.TryAddSingleton(serviceProvider =>
7574
{
7675
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
76+
var events = serviceProvider.GetRequiredService<IJsonApiApplicationBuilderEvents>();
77+
7778
var resourceGraphBuilder = new ResourceGraphBuilder(_options, loggerFactory);
7879

7980
var scanner = new ResourcesAssemblyScanner(_assemblyCache, resourceGraphBuilder);
@@ -93,8 +94,7 @@ public void ConfigureResourceGraph(ICollection<Type> dbContextTypes, Action<Reso
9394
configureResourceGraph?.Invoke(resourceGraphBuilder);
9495

9596
IResourceGraph resourceGraph = resourceGraphBuilder.Build();
96-
97-
_options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
97+
events.ResourceGraphBuilt(resourceGraph);
9898

9999
return resourceGraph;
100100
});
@@ -169,6 +169,7 @@ public void ConfigureServiceContainer(ICollection<Type> dbContextTypes)
169169
_services.TryAddScoped<IQueryLayerComposer, QueryLayerComposer>();
170170
_services.TryAddScoped<IInverseNavigationResolver, InverseNavigationResolver>();
171171
_services.TryAddSingleton<IDocumentDescriptionLinkProvider, NoDocumentDescriptionLinkProvider>();
172+
_services.TryAddSingleton<IJsonApiApplicationBuilderEvents, DefaultJsonApiApplicationBuilderEvents>();
172173
}
173174

174175
private void AddMiddlewareLayer()

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace JsonApiDotNetCore.Configuration;
1212
[PublicAPI]
1313
public sealed class JsonApiOptions : IJsonApiOptions
1414
{
15-
private static readonly IReadOnlySet<JsonApiExtension> EmptyExtensionSet = new HashSet<JsonApiExtension>().AsReadOnly();
15+
private static readonly IReadOnlySet<JsonApiMediaTypeExtension> EmptyExtensionSet = new HashSet<JsonApiMediaTypeExtension>().AsReadOnly();
1616
private readonly Lazy<JsonSerializerOptions> _lazySerializerWriteOptions;
1717
private readonly Lazy<JsonSerializerOptions> _lazySerializerReadOptions;
1818

@@ -100,7 +100,7 @@ public bool AllowClientGeneratedIds
100100
public IsolationLevel? TransactionIsolationLevel { get; set; }
101101

102102
/// <inheritdoc />
103-
public IReadOnlySet<JsonApiExtension> Extensions { get; set; } = EmptyExtensionSet;
103+
public IReadOnlySet<JsonApiMediaTypeExtension> Extensions { get; set; } = EmptyExtensionSet;
104104

105105
/// <inheritdoc />
106106
public JsonSerializerOptions SerializerOptions { get; } = new()
@@ -142,15 +142,15 @@ public JsonApiOptions()
142142
/// <param name="extensionsToAdd">
143143
/// The JSON:API extensions to add.
144144
/// </param>
145-
public void IncludeExtensions(params JsonApiExtension[] extensionsToAdd)
145+
public void IncludeExtensions(params JsonApiMediaTypeExtension[] extensionsToAdd)
146146
{
147147
ArgumentGuard.NotNull(extensionsToAdd);
148148

149149
if (!Extensions.IsSupersetOf(extensionsToAdd))
150150
{
151-
var extensions = new HashSet<JsonApiExtension>(Extensions);
151+
var extensions = new HashSet<JsonApiMediaTypeExtension>(Extensions);
152152

153-
foreach (JsonApiExtension extension in extensionsToAdd)
153+
foreach (JsonApiMediaTypeExtension extension in extensionsToAdd)
154154
{
155155
extensions.Add(extension);
156156
}

src/JsonApiDotNetCore/Middleware/IJsonApiContentNegotiator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ public interface IJsonApiContentNegotiator
1212
/// Validates the Content-Type and Accept HTTP headers from the incoming request. Throws a <see cref="JsonApiException" /> if unsupported. Otherwise,
1313
/// returns the list of negotiated JSON:API extensions, which should always be a subset of <see cref="IJsonApiOptions.Extensions" />.
1414
/// </summary>
15-
IReadOnlySet<JsonApiExtension> Negotiate();
15+
IReadOnlySet<JsonApiMediaTypeExtension> Negotiate();
1616
}

src/JsonApiDotNetCore/Middleware/IJsonApiRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public interface IJsonApiRequest
6363
/// <summary>
6464
/// The JSON:API extensions enabled for the current request. This is always a subset of <see cref="IJsonApiOptions.Extensions" />.
6565
/// </summary>
66-
IReadOnlySet<JsonApiExtension> Extensions { get; }
66+
IReadOnlySet<JsonApiMediaTypeExtension> Extensions { get; }
6767

6868
/// <summary>
6969
/// Performs a shallow copy.

src/JsonApiDotNetCore/Middleware/JsonApiContentNegotiator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public JsonApiContentNegotiator(IJsonApiOptions options, IHttpContextAccessor ht
3636
}
3737

3838
/// <inheritdoc />
39-
public IReadOnlySet<JsonApiExtension> Negotiate()
39+
public IReadOnlySet<JsonApiMediaTypeExtension> Negotiate()
4040
{
4141
IReadOnlyList<JsonApiMediaType> possibleMediaTypes = GetPossibleMediaTypes();
4242

@@ -66,7 +66,7 @@ public IReadOnlySet<JsonApiExtension> Negotiate()
6666
return mediaType;
6767
}
6868

69-
private IReadOnlySet<JsonApiExtension> ValidateAcceptHeader(IReadOnlyList<JsonApiMediaType> possibleMediaTypes, JsonApiMediaType? requestMediaType)
69+
private IReadOnlySet<JsonApiMediaTypeExtension> ValidateAcceptHeader(IReadOnlyList<JsonApiMediaType> possibleMediaTypes, JsonApiMediaType? requestMediaType)
7070
{
7171
string[] acceptHeaderValues = HttpContext.Request.Headers.GetCommaSeparatedValues("Accept");
7272
JsonApiMediaType? bestMatch = null;
@@ -166,12 +166,12 @@ protected virtual IReadOnlyList<JsonApiMediaType> GetPossibleMediaTypes()
166166

167167
if (IsOperationsEndpoint())
168168
{
169-
if (_options.Extensions.Contains(JsonApiExtension.AtomicOperations))
169+
if (_options.Extensions.Contains(JsonApiMediaTypeExtension.AtomicOperations))
170170
{
171171
mediaTypes.Add(JsonApiMediaType.AtomicOperations);
172172
}
173173

174-
if (_options.Extensions.Contains(JsonApiExtension.RelaxedAtomicOperations))
174+
if (_options.Extensions.Contains(JsonApiMediaTypeExtension.RelaxedAtomicOperations))
175175
{
176176
mediaTypes.Add(JsonApiMediaType.RelaxedAtomicOperations);
177177
}

0 commit comments

Comments
 (0)