Skip to content

Commit cccb011

Browse files
authored
Merge pull request #1714 from json-api-dotnet/merge-master-into-openapi
Merge master into openapi
2 parents 87f8d52 + 7883e69 commit cccb011

21 files changed

+337
-26
lines changed

Diff for: .github/CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Bugs are tracked as [GitHub issues](https://github.com/json-api-dotnet/JsonApiDo
2222
Explain the problem and include additional details to help maintainers reproduce the problem:
2323

2424
- **Use a clear and descriptive title** for the issue to identify the problem.
25-
- **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, don't just say what you did, but explain how you did it.
25+
- **Describe the exact steps which reproduce the problem** in as many details as possible. When listing steps, don't just say what you did, but explain how you did it.
2626
- **Provide specific examples to demonstrate the steps.** Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://docs.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks).
2727
- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. Explain which behavior you expected to see instead and why.
2828
- **If you're reporting a crash**, include the full exception stack trace.

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ To try it out, follow the steps below:
109109
In the command above:
110110
- Replace YOUR-GITHUB-USERNAME with the username you use to login your GitHub account.
111111
- Replace YOUR-PAT-CLASSIC with the token your created above.
112-
112+
113113
:warning: If the above command doesn't give you access in the next step, remove the package source by running:
114114
```bash
115115
dotnet nuget remove source github-json-api

Diff for: docs/ext/openapi/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Here's how an article (i.e. a resource of type "articles") might appear in a doc
7878

7979
### Atomic Operations
8080

81-
In addition to the members allowed by the [Atomic Operations extension](https://jsonapi.org/ext/atomic/),
81+
In addition to the members allowed by the [Atomic Operations extension](https://jsonapi.org/ext/atomic/),
8282
the following member MAY be included in elements of an `atomic:operations` array:
8383

8484
* `openapi:discriminator` - A free-format string to facilitate generation of client code.

Diff for: docs/usage/extensibility/middleware.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
The default middleware validates incoming `Content-Type` and `Accept` HTTP headers.
44
Based on routing configuration, it fills `IJsonApiRequest`, an injectable object that contains JSON:API-related information about the request being processed.
55

6-
It is possible to replace the built-in middleware components by configuring the IoC container and by configuring `MvcOptions`.
6+
It is possible to replace the built-in middleware components by configuring the IoC container and by configuring `MvcOptions`.
77

8-
## Configuring the IoC container
8+
## Configuring the IoC container
99

1010
The following example replaces the internal exception filter with a custom implementation.
1111

Diff for: docs/usage/extensibility/resource-definitions.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ For various reasons (see examples below) you may need to change parts of the que
2929
`JsonApiResourceDefinition<TResource, TId>` (which is an empty implementation of `IResourceDefinition<TResource, TId>`) provides overridable methods that pass you the result of query string parameter parsing.
3030
The value returned by you determines what will be used to execute the query.
3131
32-
An intermediate format (`QueryExpression` and derived types) is used, which enables us to separate JSON:API implementation
32+
An intermediate format (`QueryExpression` and derived types) is used, which enables us to separate JSON:API implementation
3333
from Entity Framework Core `IQueryable` execution.
3434
3535
### Excluding fields
@@ -220,7 +220,7 @@ You can define additional query string parameters with the LINQ expression that
220220
If the key is present in a query string, the supplied LINQ expression will be added to the database query.
221221

222222
> [!NOTE]
223-
> This directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of Entity Framework Core operators.
223+
> This directly influences the Entity Framework Core `IQueryable`. As opposed to using `OnApplyFilter`, this enables the full range of Entity Framework Core operators.
224224
But it only works on primary resource endpoints (for example: /articles, but not on /blogs/1/articles or /blogs?include=articles).
225225

226226
```c#

Diff for: docs/usage/reading/filtering.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ The next request returns all customers that have orders -or- whose last name is
6060
GET /customers?filter=has(orders)&filter=equals(lastName,'Smith') HTTP/1.1
6161
```
6262

63-
Aside from filtering on the resource being requested (which would be blogs in /blogs and articles in /blogs/1/articles),
63+
Aside from filtering on the resource being requested (which would be blogs in /blogs and articles in /blogs/1/articles),
6464
filtering on to-many relationships can be done using bracket notation:
6565

6666
```http

Diff for: docs/usage/resource-graph.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ There are three ways the resource graph can be created:
1414
2. Specifying an entire DbContext
1515
3. Manually specifying each resource
1616

17-
It is also possible to combine the three of them at once. Be aware that some configuration might overlap,
17+
It is also possible to combine the three of them at once. Be aware that some configuration might overlap,
1818
for example one could manually add a resource to the graph which is also auto-discovered. In such a scenario, the configuration
1919
is prioritized by the list above in descending order.
2020

Diff for: docs/usage/resources/relationships.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ public class TodoItem : Identifiable<int>
261261

262262
_since v5.1_
263263

264-
Default JSON:API relationship capabilities are specified in
265-
@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasOneCapabilities and
264+
Default JSON:API relationship capabilities are specified in
265+
@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasOneCapabilities and
266266
@JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_DefaultHasManyCapabilities:
267267

268268
```c#

Diff for: docs/usage/writing/bulk-batch-operations.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public sealed class OperationsController : JsonApiOperationsController
2828
}
2929
```
3030

31-
> [!IMPORTANT]
31+
> [!IMPORTANT]
3232
> Since v5.6.0, the set of exposed operations is based on
3333
> [`GenerateControllerEndpoints` usage](~/usage/extensibility/controllers.md#resource-access-control).
3434
> Earlier versions always exposed all operations for all resource types.

Diff for: src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs

+17-9
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,25 @@ public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProvid
9696
// @formatter:wrap_chained_method_calls restore
9797

9898
FilterExpression? primaryFilter = GetFilter(Array.Empty<QueryExpression>(), hasManyRelationship.LeftType);
99-
FilterExpression? secondaryFilter = GetFilter(filtersInSecondaryScope, hasManyRelationship.RightType);
10099

101-
FilterExpression inverseFilter = GetInverseRelationshipFilter(primaryId, hasManyRelationship, inverseRelationship);
100+
if (primaryFilter != null && inverseRelationship is HasOneAttribute)
101+
{
102+
// We can't lift the field chains in a primary filter, because there's no way for a custom filter expression to express
103+
// the scope of its chains. See https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1671.
104+
return null;
105+
}
106+
107+
FilterExpression? secondaryFilter = GetFilter(filtersInSecondaryScope, hasManyRelationship.RightType);
108+
FilterExpression inverseFilter = GetInverseRelationshipFilter(primaryId, primaryFilter, hasManyRelationship, inverseRelationship);
102109

103-
return LogicalExpression.Compose(LogicalOperator.And, inverseFilter, primaryFilter, secondaryFilter);
110+
return LogicalExpression.Compose(LogicalOperator.And, inverseFilter, secondaryFilter);
104111
}
105112

106-
private static FilterExpression GetInverseRelationshipFilter<TId>([DisallowNull] TId primaryId, HasManyAttribute relationship,
107-
RelationshipAttribute inverseRelationship)
113+
private static FilterExpression GetInverseRelationshipFilter<TId>([DisallowNull] TId primaryId, FilterExpression? primaryFilter,
114+
HasManyAttribute relationship, RelationshipAttribute inverseRelationship)
108115
{
109116
return inverseRelationship is HasManyAttribute hasManyInverseRelationship
110-
? GetInverseHasManyRelationshipFilter(primaryId, relationship, hasManyInverseRelationship)
117+
? GetInverseHasManyRelationshipFilter(primaryId, primaryFilter, relationship, hasManyInverseRelationship)
111118
: GetInverseHasOneRelationshipFilter(primaryId, relationship, (HasOneAttribute)inverseRelationship);
112119
}
113120

@@ -120,14 +127,15 @@ private static ComparisonExpression GetInverseHasOneRelationshipFilter<TId>([Dis
120127
return new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId));
121128
}
122129

123-
private static HasExpression GetInverseHasManyRelationshipFilter<TId>([DisallowNull] TId primaryId, HasManyAttribute relationship,
124-
HasManyAttribute inverseRelationship)
130+
private static HasExpression GetInverseHasManyRelationshipFilter<TId>([DisallowNull] TId primaryId, FilterExpression? primaryFilter,
131+
HasManyAttribute relationship, HasManyAttribute inverseRelationship)
125132
{
126133
AttrAttribute idAttribute = GetIdAttribute(relationship.LeftType);
127134
var idChain = new ResourceFieldChainExpression(ImmutableArray.Create<ResourceFieldAttribute>(idAttribute));
128135
var idComparison = new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId));
129136

130-
return new HasExpression(new ResourceFieldChainExpression(inverseRelationship), idComparison);
137+
FilterExpression filter = LogicalExpression.Compose(LogicalOperator.And, idComparison, primaryFilter)!;
138+
return new HasExpression(new ResourceFieldChainExpression(inverseRelationship), filter);
131139
}
132140

133141
/// <inheritdoc />

Diff for: src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
226226
}
227227
}
228228

229-
attributes.Add(attributeName, attributeValue);
229+
attributes[attributeName] = attributeValue;
230230
}
231231
else
232232
{
233-
attributes.Add(attributeName, null);
233+
attributes[attributeName] = null;
234234
reader.Skip();
235235
}
236236

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using JetBrains.Annotations;
3+
using JsonApiDotNetCore.Resources;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading;
7+
8+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
9+
[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading")]
10+
public sealed class Constellation : Identifiable<int>
11+
{
12+
[Attr]
13+
public string Name { get; set; } = null!;
14+
15+
[Attr]
16+
[Required]
17+
public Season? VisibleDuring { get; set; }
18+
19+
[HasMany]
20+
public ISet<Star> Stars { get; set; } = new HashSet<Star>();
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Queries.Expressions;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading;
7+
8+
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
9+
public sealed class ConstellationDefinition(
10+
IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter)
11+
: HitCountingResourceDefinition<Constellation, int>(resourceGraph, hitCounter)
12+
{
13+
private readonly IClientSettingsProvider _clientSettingsProvider = clientSettingsProvider;
14+
15+
protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading;
16+
17+
public override FilterExpression? OnApplyFilter(FilterExpression? existingFilter)
18+
{
19+
FilterExpression? baseFilter = base.OnApplyFilter(existingFilter);
20+
21+
if (_clientSettingsProvider.AreConstellationsVisibleDuringWinterHidden)
22+
{
23+
AttrAttribute visibleDuringAttribute = ResourceType.GetAttributeByPropertyName(nameof(Constellation.VisibleDuring));
24+
var visibleDuringChain = new ResourceFieldChainExpression(visibleDuringAttribute);
25+
var visibleDuringComparison = new ComparisonExpression(ComparisonOperator.Equals, visibleDuringChain, new LiteralConstantExpression(Season.Winter));
26+
var notVisibleDuringComparison = new NotExpression(visibleDuringComparison);
27+
28+
return LogicalExpression.Compose(LogicalOperator.And, baseFilter, notVisibleDuringComparison);
29+
}
30+
31+
return baseFilter;
32+
}
33+
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading;
22

33
public interface IClientSettingsProvider
44
{
5+
bool AreVeryLargeStarsHidden { get; }
6+
bool AreConstellationsVisibleDuringWinterHidden { get; }
57
bool IsIncludePlanetMoonsBlocked { get; }
68
bool ArePlanetsWithPrivateNameHidden { get; }
79
bool IsStarGivingLightToMoonAutoIncluded { get; }

0 commit comments

Comments
 (0)