Skip to content

Commit a6ce7ff

Browse files
author
Bart Koelman
committed
Fixed: use intersection on serialize sparse fieldset from ResourceDefinition. This hides forced included fields from output, but still fetches them.
1 parent 705721a commit a6ce7ff

File tree

6 files changed

+38
-16
lines changed

6 files changed

+38
-16
lines changed

src/JsonApiDotNetCore/Models/ResourceDefinition.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,17 @@ public virtual PaginationExpression OnApplyPagination(PaginationExpression exist
142142
/// Tip: Use <see cref="SparseFieldSetExtensions.Including{TResource}"/> and <see cref="SparseFieldSetExtensions.Excluding{TResource}"/>
143143
/// to safely change the fieldset without worrying about nulls.
144144
/// </summary>
145-
/// <param name="existingSparseFieldSet">
146-
/// An optional existing sparse fieldset, coming from query string. Can be <c>null</c>.
145+
/// <remarks>
146+
/// This method executes twice for a single request: first to select which fields to retrieve from the data store and then to
147+
/// select which fields to serialize. Including extra fields from this method will retrieve them, but not include them in the json output.
148+
/// This enables you to expose calculated properties whose value depends on a field that is not in the sparse fieldset.
149+
/// </remarks>
150+
/// <param name="existingSparseFieldSet">The incoming sparse fieldset from query string.
151+
/// At query execution time, this is <c>null</c> if the query string contains no sparse fieldset.
152+
/// At serialization time, this contains all viewable fields if the query string contains no sparse fieldset.
147153
/// </param>
148154
/// <returns>
149-
/// The new sparse fieldset, or <c>null</c> to disable the existing sparse fieldset and select all fields.
155+
/// The new sparse fieldset, or <c>null</c> to discard the existing sparse fieldset and select all viewable fields.
150156
/// </returns>
151157
public virtual SparseFieldSetExpression OnApplySparseFieldSet(SparseFieldSetExpression existingSparseFieldSet)
152158
{

src/JsonApiDotNetCore/Serialization/Server/Contracts/IFieldsToSerialize.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ namespace JsonApiDotNetCore.Serialization.Server
1515
public interface IFieldsToSerialize
1616
{
1717
/// <summary>
18-
/// Gets the list of attributes that are to be serialized for resource of type <paramref name="type"/>.
18+
/// Gets the list of attributes that are to be serialized for resource of type <paramref name="resourceType"/>.
1919
/// If <paramref name="relationship"/> is non-null, it will consider the allowed list of attributes
2020
/// as an included relationship.
2121
/// </summary>
22-
IReadOnlyCollection<AttrAttribute> GetAttributes(Type type, RelationshipAttribute relationship = null);
22+
IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceType, RelationshipAttribute relationship = null);
2323

2424
/// <summary>
2525
/// Gets the list of relationships that are to be serialized for resource of type <paramref name="type"/>.

src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public FieldsToSerialize(
2828
}
2929

3030
/// <inheritdoc/>
31-
public IReadOnlyCollection<AttrAttribute> GetAttributes(Type type, RelationshipAttribute relationship = null)
31+
public IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceType, RelationshipAttribute relationship = null)
3232
{
3333
var sparseFieldSetAttributes = _constraintProviders
3434
.SelectMany(p => p.GetConstraints())
@@ -42,23 +42,35 @@ public IReadOnlyCollection<AttrAttribute> GetAttributes(Type type, RelationshipA
4242

4343
if (!sparseFieldSetAttributes.Any())
4444
{
45-
sparseFieldSetAttributes = _resourceGraph.GetAttributes(type).ToHashSet();
45+
sparseFieldSetAttributes = GetViewableAttributes(resourceType);
4646
}
4747

48-
sparseFieldSetAttributes.RemoveWhere(attr => !attr.Capabilities.HasFlag(AttrCapabilities.AllowView));
49-
50-
var resourceDefinition = _resourceDefinitionProvider.Get(type);
48+
var resourceDefinition = _resourceDefinitionProvider.Get(resourceType);
5149
if (resourceDefinition != null)
5250
{
53-
var tempExpression = sparseFieldSetAttributes.Any() ? new SparseFieldSetExpression(sparseFieldSetAttributes) : null;
54-
tempExpression = resourceDefinition.OnApplySparseFieldSet(tempExpression);
51+
var inputExpression = sparseFieldSetAttributes.Any() ? new SparseFieldSetExpression(sparseFieldSetAttributes) : null;
52+
var outputExpression = resourceDefinition.OnApplySparseFieldSet(inputExpression);
5553

56-
sparseFieldSetAttributes = tempExpression == null ? new HashSet<AttrAttribute>() : tempExpression.Attributes.ToHashSet();
54+
if (outputExpression == null)
55+
{
56+
sparseFieldSetAttributes = GetViewableAttributes(resourceType);
57+
}
58+
else
59+
{
60+
sparseFieldSetAttributes.IntersectWith(outputExpression.Attributes);
61+
}
5762
}
5863

5964
return sparseFieldSetAttributes;
6065
}
6166

67+
private HashSet<AttrAttribute> GetViewableAttributes(Type resourceType)
68+
{
69+
return _resourceGraph.GetAttributes(resourceType)
70+
.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView))
71+
.ToHashSet();
72+
}
73+
6274
/// <inheritdoc/>
6375
/// <remarks>
6476
/// Note: this method does NOT check if a relationship is included to determine

test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResource.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public sealed class CallableResource : Identifiable
1313
[Attr]
1414
public int PercentageComplete { get; set; }
1515

16+
[Attr]
17+
public string Status => $"{PercentageComplete}% completed.";
18+
1619
[Attr]
1720
public int RiskLevel { get; set; }
1821

test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/CallableResourceDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public override PaginationExpression OnApplyPagination(PaginationExpression exis
6868

6969
public override SparseFieldSetExpression OnApplySparseFieldSet(SparseFieldSetExpression existingSparseFieldSet)
7070
{
71-
// Use case: always include percentageComplete and never include riskLevel in responses.
71+
// Use case: always retrieve percentageComplete and never include riskLevel in responses.
7272

7373
return existingSparseFieldSet
7474
.Including<CallableResource>(resource => resource.PercentageComplete, ResourceGraph)

test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceDefinitions/ResourceDefinitionQueryCallbackTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
295295
await dbContext.SaveChangesAsync();
296296
});
297297

298-
var route = $"/callableResources/{resource.StringId}?fields=label";
298+
var route = $"/callableResources/{resource.StringId}?fields=label,status";
299299

300300
// Act
301301
var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
@@ -306,7 +306,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
306306
responseDocument.SingleData.Should().NotBeNull();
307307
responseDocument.SingleData.Id.Should().Be(resource.StringId);
308308
responseDocument.SingleData.Attributes["label"].Should().Be(resource.Label);
309-
responseDocument.SingleData.Attributes["percentageComplete"].Should().Be(resource.PercentageComplete);
309+
responseDocument.SingleData.Attributes.Should().NotContainKey("percentageComplete");
310+
responseDocument.SingleData.Attributes["status"].Should().Be("5% completed.");
310311
}
311312

312313
[Fact]

0 commit comments

Comments
 (0)