Skip to content

Commit b48f02e

Browse files
author
Bart Koelman
authored
Resource inheritance (#1142)
* Removed existing resource inheritance tests * Resource inheritance: return derived types * Resource inheritance: sparse fieldsets * Changed the expression tokenizer to recognize dots in field chains * Resource inheritance: derived includes * Resource inheritance: derived filters * Added ResourceFieldAttribute.Type and use it from QueryExpression.ToFullString() to provide improved debug info * Resource inheritance: sorting on derived fields * Added missing tests for GET abstract/concrete base primary types at secondary/relationship endpoints * Resource graph validation: fail if field from base type does not exist on derived type * Clarified error message on type mismatch in request body * Rename inheritance tests * Added extension method to obtain ClrType of a resource instance (we'll need this later) * Resource inheritance: write endpoints * Updated documentation * Added rewriter unit tests to improve code coverage * Exclude example project from code coverage * Review feedback * Update ROADMAP.md
1 parent 388f94f commit b48f02e

File tree

190 files changed

+10178
-1391
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+10178
-1391
lines changed

Diff for: JsonApiDotNetCore.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ $left$ = $right$;</s:String>
629629
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/ReplacePattern/@EntryValue">$collection$.IsNullOrEmpty()</s:String>
630630
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/SearchPattern/@EntryValue">$collection$ == null || !$collection$.Any()</s:String>
631631
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/Severity/@EntryValue">WARNING</s:String>
632+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Accurize/@EntryIndexedValue">True</s:Boolean>
632633
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
633634
<s:Boolean x:Key="/Default/UserDictionary/Words/=Assignee/@EntryIndexedValue">True</s:Boolean>
634635
<s:Boolean x:Key="/Default/UserDictionary/Words/=Contoso/@EntryIndexedValue">True</s:Boolean>

Diff for: ROADMAP.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
2727
- [x] Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
2828
- [x] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
2929
- [x] Extract annotations into separate package [#730](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/730)
30+
- [x] Resource inheritance [#844](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844)
3031

3132
Aside from the list above, we have interest in the following topics. It's too soon yet to decide whether they'll make it into v5.x or in a later major version.
3233

3334
- Optimistic concurrency [#1004](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1004)
3435
- OpenAPI (Swagger) [#1046](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1046)
3536
- Fluent API [#776](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/776)
36-
- Resource inheritance [#844](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844)
3737
- Idempotency
3838

3939
## Feedback

Diff for: benchmarks/Serialization/ResourceSerializationBenchmarks.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,19 @@ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceG
127127
RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
128128
RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));
129129

130-
ImmutableArray<ResourceFieldAttribute> chain = ImmutableArray.Create<ResourceFieldAttribute>(single2, single3, multi4, multi5);
131-
IEnumerable<ResourceFieldChainExpression> chains = new ResourceFieldChainExpression(chain).AsEnumerable();
132-
133-
var converter = new IncludeChainConverter();
134-
IncludeExpression include = converter.FromRelationshipChains(chains);
130+
var include = new IncludeExpression(new HashSet<IncludeElementExpression>
131+
{
132+
new(single2, new HashSet<IncludeElementExpression>
133+
{
134+
new(single3, new HashSet<IncludeElementExpression>
135+
{
136+
new(multi4, new HashSet<IncludeElementExpression>
137+
{
138+
new(multi5)
139+
}.ToImmutableHashSet())
140+
}.ToImmutableHashSet())
141+
}.ToImmutableHashSet())
142+
}.ToImmutableHashSet());
135143

136144
var cache = new EvaluatedIncludeCache();
137145
cache.Set(include);

Diff for: benchmarks/Serialization/SerializationBenchmarkBase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ public Task OnSetToManyRelationshipAsync<TResource>(TResource leftResource, HasM
180180
return Task.CompletedTask;
181181
}
182182

183-
public Task OnAddToRelationshipAsync<TResource, TId>(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet<IIdentifiable> rightResourceIds,
183+
public Task OnAddToRelationshipAsync<TResource>(TResource leftResource, HasManyAttribute hasManyRelationship, ISet<IIdentifiable> rightResourceIds,
184184
CancellationToken cancellationToken)
185-
where TResource : class, IIdentifiable<TId>
185+
where TResource : class, IIdentifiable
186186
{
187187
return Task.CompletedTask;
188188
}

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

+27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Expressions are composed using the following functions:
2424
| Ends with text | `endsWith` | `?filter=endsWith(description,'End')` |
2525
| Equals one value from set | `any` | `?filter=any(chapter,'Intro','Summary','Conclusion')` |
2626
| Collection contains items | `has` | `?filter=has(articles)` |
27+
| Type-check derived type (v5) | `isType` | `?filter=isType(,men)` |
2728
| Negation | `not` | `?filter=not(equals(lastName,null))` |
2829
| Conditional logical OR | `or` | `?filter=or(has(orders),has(invoices))` |
2930
| Conditional logical AND | `and` | `?filter=and(has(orders),has(invoices))` |
@@ -86,6 +87,32 @@ GET /customers?filter=has(orders,not(equals(status,'Paid'))) HTTP/1.1
8687

8788
Which returns only customers that have at least one unpaid order.
8889

90+
_since v5.0_
91+
92+
Use the `isType` filter function to perform a type check on a derived type. You can pass a nested filter, where the derived fields are accessible.
93+
94+
Only return men:
95+
```http
96+
GET /humans?filter=isType(,men) HTTP/1.1
97+
```
98+
99+
Only return men with beards:
100+
```http
101+
GET /humans?filter=isType(,men,equals(hasBeard,'true')) HTTP/1.1
102+
```
103+
104+
The first parameter of `isType` can be used to perform the type check on a to-one relationship path.
105+
106+
Only return people whose best friend is a man with children:
107+
```http
108+
GET /humans?filter=isType(bestFriend,men,has(children)) HTTP/1.1
109+
```
110+
111+
Only return people who have at least one female married child:
112+
```http
113+
GET /humans?filter=has(children,isType(,woman,not(equals(husband,null)))) HTTP/1.1
114+
```
115+
89116
# Legacy filters
90117

91118
The next section describes how filtering worked in versions prior to v4.0. They are always applied on the set of resources being requested (no nesting).

0 commit comments

Comments
 (0)