Skip to content

Commit

Permalink
Resource inheritance (#1142)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Bart Koelman authored Apr 4, 2022
1 parent 388f94f commit b48f02e
Show file tree
Hide file tree
Showing 190 changed files with 10,178 additions and 1,391 deletions.
1 change: 1 addition & 0 deletions JsonApiDotNetCore.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ $left$ = $right$;</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/ReplacePattern/@EntryValue">$collection$.IsNullOrEmpty()</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/SearchPattern/@EntryValue">$collection$ == null || !$collection$.Any()</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/StructuralSearch/Pattern/=B3D9EE6B4EC62A4F961EB15F9ADEC2C6/Severity/@EntryValue">WARNING</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Accurize/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Assignee/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Contoso/@EntryIndexedValue">True</s:Boolean>
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ The need for breaking changes has blocked several efforts in the v4.x release, s
- [x] Auto-generated controllers [#732](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/732) [#365](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/365)
- [x] Support .NET 6 with EF Core 6 [#1109](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1109)
- [x] Extract annotations into separate package [#730](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/730)
- [x] Resource inheritance [#844](https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844)

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.

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

## Feedback
Expand Down
18 changes: 13 additions & 5 deletions benchmarks/Serialization/ResourceSerializationBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,19 @@ protected override IEvaluatedIncludeCache CreateEvaluatedIncludeCache(IResourceG
RelationshipAttribute multi4 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi4));
RelationshipAttribute multi5 = resourceAType.GetRelationshipByPropertyName(nameof(OutgoingResource.Multi5));

ImmutableArray<ResourceFieldAttribute> chain = ImmutableArray.Create<ResourceFieldAttribute>(single2, single3, multi4, multi5);
IEnumerable<ResourceFieldChainExpression> chains = new ResourceFieldChainExpression(chain).AsEnumerable();

var converter = new IncludeChainConverter();
IncludeExpression include = converter.FromRelationshipChains(chains);
var include = new IncludeExpression(new HashSet<IncludeElementExpression>
{
new(single2, new HashSet<IncludeElementExpression>
{
new(single3, new HashSet<IncludeElementExpression>
{
new(multi4, new HashSet<IncludeElementExpression>
{
new(multi5)
}.ToImmutableHashSet())
}.ToImmutableHashSet())
}.ToImmutableHashSet())
}.ToImmutableHashSet());

var cache = new EvaluatedIncludeCache();
cache.Set(include);
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/Serialization/SerializationBenchmarkBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ public Task OnSetToManyRelationshipAsync<TResource>(TResource leftResource, HasM
return Task.CompletedTask;
}

public Task OnAddToRelationshipAsync<TResource, TId>(TId leftResourceId, HasManyAttribute hasManyRelationship, ISet<IIdentifiable> rightResourceIds,
public Task OnAddToRelationshipAsync<TResource>(TResource leftResource, HasManyAttribute hasManyRelationship, ISet<IIdentifiable> rightResourceIds,
CancellationToken cancellationToken)
where TResource : class, IIdentifiable<TId>
where TResource : class, IIdentifiable
{
return Task.CompletedTask;
}
Expand Down
27 changes: 27 additions & 0 deletions docs/usage/reading/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Expressions are composed using the following functions:
| Ends with text | `endsWith` | `?filter=endsWith(description,'End')` |
| Equals one value from set | `any` | `?filter=any(chapter,'Intro','Summary','Conclusion')` |
| Collection contains items | `has` | `?filter=has(articles)` |
| Type-check derived type (v5) | `isType` | `?filter=isType(,men)` |
| Negation | `not` | `?filter=not(equals(lastName,null))` |
| Conditional logical OR | `or` | `?filter=or(has(orders),has(invoices))` |
| Conditional logical AND | `and` | `?filter=and(has(orders),has(invoices))` |
Expand Down Expand Up @@ -86,6 +87,32 @@ GET /customers?filter=has(orders,not(equals(status,'Paid'))) HTTP/1.1

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

_since v5.0_

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.

Only return men:
```http
GET /humans?filter=isType(,men) HTTP/1.1
```

Only return men with beards:
```http
GET /humans?filter=isType(,men,equals(hasBeard,'true')) HTTP/1.1
```

The first parameter of `isType` can be used to perform the type check on a to-one relationship path.

Only return people whose best friend is a man with children:
```http
GET /humans?filter=isType(bestFriend,men,has(children)) HTTP/1.1
```

Only return people who have at least one female married child:
```http
GET /humans?filter=has(children,isType(,woman,not(equals(husband,null)))) HTTP/1.1
```

# Legacy filters

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).
Expand Down
Loading

0 comments on commit b48f02e

Please sign in to comment.