Skip to content

Commit b408bad

Browse files
author
Bart Koelman
committed
Changed SparseFieldSetExpression.Fields type from IReadOnlyCollection to IImmutableSet
1 parent 5e54e4e commit b408bad

File tree

10 files changed

+72
-66
lines changed

10 files changed

+72
-66
lines changed

src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpression.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
2+
using System.Collections.Immutable;
33
using System.Linq;
44
using JetBrains.Annotations;
55
using JsonApiDotNetCore.Resources.Annotations;
@@ -12,9 +12,9 @@ namespace JsonApiDotNetCore.Queries.Expressions
1212
[PublicAPI]
1313
public class SparseFieldSetExpression : QueryExpression
1414
{
15-
public IReadOnlyCollection<ResourceFieldAttribute> Fields { get; }
15+
public IImmutableSet<ResourceFieldAttribute> Fields { get; }
1616

17-
public SparseFieldSetExpression(IReadOnlyCollection<ResourceFieldAttribute> fields)
17+
public SparseFieldSetExpression(IImmutableSet<ResourceFieldAttribute> fields)
1818
{
1919
ArgumentGuard.NotNullNorEmpty(fields, nameof(fields));
2020

@@ -28,7 +28,7 @@ public override TResult Accept<TArgument, TResult>(QueryExpressionVisitor<TArgum
2828

2929
public override string ToString()
3030
{
31-
return string.Join(",", Fields.Select(child => child.PublicName));
31+
return string.Join(",", Fields.Select(child => child.PublicName).OrderBy(name => name));
3232
}
3333

3434
public override bool Equals(object obj)
@@ -45,7 +45,7 @@ public override bool Equals(object obj)
4545

4646
var other = (SparseFieldSetExpression)obj;
4747

48-
return Fields.SequenceEqual(other.Fields);
48+
return Fields.SetEquals(other.Fields);
4949
}
5050

5151
public override int GetHashCode()

src/JsonApiDotNetCore/Queries/Expressions/SparseFieldSetExpressionExtensions.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
2+
using System.Collections.Immutable;
43
using System.Linq.Expressions;
54
using JetBrains.Annotations;
65
using JsonApiDotNetCore.Configuration;
@@ -36,9 +35,8 @@ private static SparseFieldSetExpression IncludeField(SparseFieldSetExpression sp
3635
return sparseFieldSet;
3736
}
3837

39-
HashSet<ResourceFieldAttribute> fieldSet = sparseFieldSet.Fields.ToHashSet();
40-
fieldSet.Add(fieldToInclude);
41-
return new SparseFieldSetExpression(fieldSet);
38+
IImmutableSet<ResourceFieldAttribute> newSparseFieldSet = sparseFieldSet.Fields.Add(fieldToInclude);
39+
return new SparseFieldSetExpression(newSparseFieldSet);
4240
}
4341

4442
public static SparseFieldSetExpression Excluding<TResource>(this SparseFieldSetExpression sparseFieldSet,
@@ -70,9 +68,8 @@ private static SparseFieldSetExpression ExcludeField(SparseFieldSetExpression sp
7068
return sparseFieldSet;
7169
}
7270

73-
HashSet<ResourceFieldAttribute> fieldSet = sparseFieldSet.Fields.ToHashSet();
74-
fieldSet.Remove(fieldToExclude);
75-
return new SparseFieldSetExpression(fieldSet);
71+
IImmutableSet<ResourceFieldAttribute> newSparseFieldSet = sparseFieldSet.Fields.Remove(fieldToExclude);
72+
return new SparseFieldSetExpression(newSparseFieldSet);
7673
}
7774
}
7875
}

src/JsonApiDotNetCore/Queries/Internal/Parsing/SparseFieldSetParser.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using JetBrains.Annotations;
56
using JsonApiDotNetCore.Configuration;
@@ -38,21 +39,21 @@ public SparseFieldSetExpression Parse(string source, ResourceContext resourceCon
3839

3940
protected SparseFieldSetExpression ParseSparseFieldSet()
4041
{
41-
var fields = new Dictionary<string, ResourceFieldAttribute>();
42+
ImmutableHashSet<ResourceFieldAttribute>.Builder fieldSetBuilder = ImmutableHashSet.CreateBuilder<ResourceFieldAttribute>();
4243

4344
while (TokenStack.Any())
4445
{
45-
if (fields.Count > 0)
46+
if (fieldSetBuilder.Count > 0)
4647
{
4748
EatSingleCharacterToken(TokenKind.Comma);
4849
}
4950

5051
ResourceFieldChainExpression nextChain = ParseFieldChain(FieldChainRequirements.EndsInAttribute, "Field name expected.");
5152
ResourceFieldAttribute nextField = nextChain.Fields.Single();
52-
fields[nextField.PublicName] = nextField;
53+
fieldSetBuilder.Add(nextField);
5354
}
5455

55-
return fields.Any() ? new SparseFieldSetExpression(fields.Values) : null;
56+
return fieldSetBuilder.Any() ? new SparseFieldSetExpression(fieldSetBuilder.ToImmutable()) : null;
5657
}
5758

5859
protected override IReadOnlyCollection<ResourceFieldAttribute> OnResolveFieldChain(string path, FieldChainRequirements chainRequirements)

src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using JetBrains.Annotations;
56
using JsonApiDotNetCore.Configuration;
@@ -253,7 +254,7 @@ public QueryLayer ComposeSecondaryLayerForRelationship(ResourceContext secondary
253254

254255
private IDictionary<ResourceFieldAttribute, QueryLayer> GetProjectionForRelationship(ResourceContext secondaryResourceContext)
255256
{
256-
IReadOnlyCollection<AttrAttribute> secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext);
257+
IImmutableSet<AttrAttribute> secondaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(secondaryResourceContext);
257258

258259
return secondaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null);
259260
}
@@ -269,7 +270,7 @@ public QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer,
269270
IncludeExpression innerInclude = secondaryLayer.Include;
270271
secondaryLayer.Include = null;
271272

272-
IReadOnlyCollection<AttrAttribute> primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext);
273+
IImmutableSet<AttrAttribute> primaryAttributeSet = _sparseFieldSetCache.GetIdAttributeSetForRelationshipQuery(primaryResourceContext);
273274

274275
Dictionary<ResourceFieldAttribute, QueryLayer> primaryProjection =
275276
primaryAttributeSet.ToDictionary(key => (ResourceFieldAttribute)key, _ => (QueryLayer)null);
@@ -484,7 +485,7 @@ protected virtual IDictionary<ResourceFieldAttribute, QueryLayer> GetProjectionF
484485
{
485486
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
486487

487-
IReadOnlyCollection<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext);
488+
IImmutableSet<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForQuery(resourceContext);
488489

489490
if (!fieldSet.Any())
490491
{

src/JsonApiDotNetCore/Queries/Internal/SparseFieldSetCache.cs

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using JetBrains.Annotations;
56
using JsonApiDotNetCore.Configuration;
@@ -17,20 +18,21 @@ namespace JsonApiDotNetCore.Queries.Internal
1718
public sealed class SparseFieldSetCache
1819
{
1920
private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor;
20-
private readonly Lazy<IDictionary<ResourceContext, HashSet<ResourceFieldAttribute>>> _lazySourceTable;
21-
private readonly IDictionary<ResourceContext, HashSet<ResourceFieldAttribute>> _visitedTable;
21+
private readonly Lazy<IDictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>>> _lazySourceTable;
22+
private readonly IDictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>> _visitedTable;
2223

2324
public SparseFieldSetCache(IEnumerable<IQueryConstraintProvider> constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor)
2425
{
2526
ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders));
2627
ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor));
2728

2829
_resourceDefinitionAccessor = resourceDefinitionAccessor;
29-
_lazySourceTable = new Lazy<IDictionary<ResourceContext, HashSet<ResourceFieldAttribute>>>(() => BuildSourceTable(constraintProviders));
30-
_visitedTable = new Dictionary<ResourceContext, HashSet<ResourceFieldAttribute>>();
30+
_lazySourceTable = new Lazy<IDictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>>>(() => BuildSourceTable(constraintProviders));
31+
_visitedTable = new Dictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>>();
3132
}
3233

33-
private static IDictionary<ResourceContext, HashSet<ResourceFieldAttribute>> BuildSourceTable(IEnumerable<IQueryConstraintProvider> constraintProviders)
34+
private static IDictionary<ResourceContext, IImmutableSet<ResourceFieldAttribute>> BuildSourceTable(
35+
IEnumerable<IQueryConstraintProvider> constraintProviders)
3436
{
3537
// @formatter:wrap_chained_method_calls chop_always
3638
// @formatter:keep_existing_linebreaks true
@@ -47,30 +49,31 @@ private static IDictionary<ResourceContext, HashSet<ResourceFieldAttribute>> Bui
4749
// @formatter:keep_existing_linebreaks restore
4850
// @formatter:wrap_chained_method_calls restore
4951

50-
var mergedTable = new Dictionary<ResourceContext, HashSet<ResourceFieldAttribute>>();
52+
var mergedTable = new Dictionary<ResourceContext, ImmutableHashSet<ResourceFieldAttribute>.Builder>();
5153

5254
foreach ((ResourceContext resourceContext, SparseFieldSetExpression sparseFieldSet) in sparseFieldTables)
5355
{
5456
if (!mergedTable.ContainsKey(resourceContext))
5557
{
56-
mergedTable[resourceContext] = new HashSet<ResourceFieldAttribute>();
58+
mergedTable[resourceContext] = ImmutableHashSet.CreateBuilder<ResourceFieldAttribute>();
5759
}
5860

5961
AddSparseFieldsToSet(sparseFieldSet.Fields, mergedTable[resourceContext]);
6062
}
6163

62-
return mergedTable;
64+
return mergedTable.ToDictionary(pair => pair.Key, pair => (IImmutableSet<ResourceFieldAttribute>)pair.Value.ToImmutable());
6365
}
6466

65-
private static void AddSparseFieldsToSet(IReadOnlyCollection<ResourceFieldAttribute> sparseFieldsToAdd, HashSet<ResourceFieldAttribute> sparseFieldSet)
67+
private static void AddSparseFieldsToSet(IImmutableSet<ResourceFieldAttribute> sparseFieldsToAdd,
68+
ImmutableHashSet<ResourceFieldAttribute>.Builder sparseFieldSetBuilder)
6669
{
6770
foreach (ResourceFieldAttribute field in sparseFieldsToAdd)
6871
{
69-
sparseFieldSet.Add(field);
72+
sparseFieldSetBuilder.Add(field);
7073
}
7174
}
7275

73-
public IReadOnlyCollection<ResourceFieldAttribute> GetSparseFieldSetForQuery(ResourceContext resourceContext)
76+
public IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForQuery(ResourceContext resourceContext)
7477
{
7578
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
7679

@@ -82,84 +85,73 @@ public IReadOnlyCollection<ResourceFieldAttribute> GetSparseFieldSetForQuery(Res
8285

8386
SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression);
8487

85-
HashSet<ResourceFieldAttribute> outputFields = outputExpression == null
86-
? new HashSet<ResourceFieldAttribute>()
87-
: outputExpression.Fields.ToHashSet();
88+
IImmutableSet<ResourceFieldAttribute> outputFields = outputExpression == null
89+
? ImmutableHashSet<ResourceFieldAttribute>.Empty
90+
: outputExpression.Fields;
8891

8992
_visitedTable[resourceContext] = outputFields;
9093
}
9194

9295
return _visitedTable[resourceContext];
9396
}
9497

95-
public IReadOnlyCollection<AttrAttribute> GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext)
98+
public IImmutableSet<AttrAttribute> GetIdAttributeSetForRelationshipQuery(ResourceContext resourceContext)
9699
{
97100
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
98101

99102
AttrAttribute idAttribute = resourceContext.Attributes.Single(attr => attr.Property.Name == nameof(Identifiable.Id));
100-
var inputExpression = new SparseFieldSetExpression(idAttribute.AsArray());
103+
var inputExpression = new SparseFieldSetExpression(ImmutableHashSet.Create<ResourceFieldAttribute>(idAttribute));
101104

102105
// Intentionally not cached, as we are fetching ID only (ignoring any sparse fieldset that came from query string).
103106
SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression);
104107

105-
HashSet<AttrAttribute> outputAttributes = outputExpression == null
106-
? new HashSet<AttrAttribute>()
107-
: outputExpression.Fields.OfType<AttrAttribute>().ToHashSet();
108+
ImmutableHashSet<AttrAttribute> outputAttributes = outputExpression == null
109+
? ImmutableHashSet<AttrAttribute>.Empty
110+
: outputExpression.Fields.OfType<AttrAttribute>().ToImmutableHashSet();
108111

109-
outputAttributes.Add(idAttribute);
112+
outputAttributes = outputAttributes.Add(idAttribute);
110113
return outputAttributes;
111114
}
112115

113-
public IReadOnlyCollection<ResourceFieldAttribute> GetSparseFieldSetForSerializer(ResourceContext resourceContext)
116+
public IImmutableSet<ResourceFieldAttribute> GetSparseFieldSetForSerializer(ResourceContext resourceContext)
114117
{
115118
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
116119

117120
if (!_visitedTable.ContainsKey(resourceContext))
118121
{
119-
HashSet<ResourceFieldAttribute> inputFields = _lazySourceTable.Value.ContainsKey(resourceContext)
122+
IImmutableSet<ResourceFieldAttribute> inputFields = _lazySourceTable.Value.ContainsKey(resourceContext)
120123
? _lazySourceTable.Value[resourceContext]
121124
: GetResourceFields(resourceContext);
122125

123126
var inputExpression = new SparseFieldSetExpression(inputFields);
124127
SparseFieldSetExpression outputExpression = _resourceDefinitionAccessor.OnApplySparseFieldSet(resourceContext.ResourceType, inputExpression);
125128

126-
HashSet<ResourceFieldAttribute> outputFields;
127-
128-
if (outputExpression == null)
129-
{
130-
outputFields = GetResourceFields(resourceContext);
131-
}
132-
else
133-
{
134-
outputFields = new HashSet<ResourceFieldAttribute>(inputFields);
135-
outputFields.IntersectWith(outputExpression.Fields);
136-
}
129+
IImmutableSet<ResourceFieldAttribute> outputFields =
130+
outputExpression == null ? GetResourceFields(resourceContext) : inputFields.Intersect(outputExpression.Fields);
137131

138132
_visitedTable[resourceContext] = outputFields;
139133
}
140134

141135
return _visitedTable[resourceContext];
142136
}
143137

144-
#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type
145-
private HashSet<ResourceFieldAttribute> GetResourceFields(ResourceContext resourceContext)
146-
#pragma warning restore AV1130 // Return type in method signature should be a collection interface instead of a concrete type
138+
private IImmutableSet<ResourceFieldAttribute> GetResourceFields(ResourceContext resourceContext)
147139
{
148140
ArgumentGuard.NotNull(resourceContext, nameof(resourceContext));
149141

150-
var fieldSet = new HashSet<ResourceFieldAttribute>();
142+
ImmutableHashSet<ResourceFieldAttribute>.Builder fieldSetBuilder = ImmutableHashSet.CreateBuilder<ResourceFieldAttribute>();
151143

152144
foreach (AttrAttribute attribute in resourceContext.Attributes.Where(attr => attr.Capabilities.HasFlag(AttrCapabilities.AllowView)))
153145
{
154-
fieldSet.Add(attribute);
146+
fieldSetBuilder.Add(attribute);
155147
}
156148

157149
foreach (RelationshipAttribute relationship in resourceContext.Relationships)
158150
{
159-
fieldSet.Add(relationship);
151+
fieldSetBuilder.Add(relationship);
160152
}
161153

162-
return fieldSet;
154+
return fieldSetBuilder.ToImmutable();
163155
}
164156

165157
public void Reset()

src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using JetBrains.Annotations;
56
using JsonApiDotNetCore.Configuration;
@@ -89,7 +90,7 @@ private SparseFieldSetExpression GetSparseFieldSet(string parameterValue, Resour
8990
{
9091
// We add ID on an incoming empty fieldset, so that callers can distinguish between no fieldset and an empty one.
9192
AttrAttribute idAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Identifiable.Id));
92-
return new SparseFieldSetExpression(ArrayFactory.Create(idAttribute));
93+
return new SparseFieldSetExpression(ImmutableHashSet.Create<ResourceFieldAttribute>(idAttribute));
9394
}
9495

9596
return sparseFieldSet;

src/JsonApiDotNetCore/Serialization/Building/IncludedResourceObjectBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Collections.Immutable;
45
using System.Linq;
56
using JetBrains.Annotations;
67
using JsonApiDotNetCore.Configuration;
@@ -92,7 +93,7 @@ private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship)
9293
{
9394
ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType);
9495

95-
IReadOnlyCollection<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
96+
IImmutableSet<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
9697
return fieldSet.Contains(relationship);
9798
}
9899

src/JsonApiDotNetCore/Serialization/Building/ResponseResourceObjectBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Collections.Immutable;
23
using System.Linq;
34
using JetBrains.Annotations;
45
using JsonApiDotNetCore.Configuration;
@@ -113,7 +114,7 @@ private bool IsRelationshipInSparseFieldSet(RelationshipAttribute relationship)
113114
{
114115
ResourceContext resourceContext = ResourceContextProvider.GetResourceContext(relationship.LeftType);
115116

116-
IReadOnlyCollection<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
117+
IImmutableSet<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
117118
return fieldSet.Contains(relationship);
118119
}
119120

src/JsonApiDotNetCore/Serialization/FieldsToSerialize.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
45
using JetBrains.Annotations;
56
using JsonApiDotNetCore.Configuration;
@@ -44,9 +45,20 @@ public IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceType)
4445
}
4546

4647
ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(resourceType);
47-
IReadOnlyCollection<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
48+
IImmutableSet<ResourceFieldAttribute> fieldSet = _sparseFieldSetCache.GetSparseFieldSetForSerializer(resourceContext);
4849

49-
return fieldSet.OfType<AttrAttribute>().ToArray();
50+
return SortAttributesInDeclarationOrder(fieldSet, resourceContext).ToArray();
51+
}
52+
53+
private IEnumerable<AttrAttribute> SortAttributesInDeclarationOrder(IImmutableSet<ResourceFieldAttribute> fieldSet, ResourceContext resourceContext)
54+
{
55+
foreach (AttrAttribute attribute in resourceContext.Attributes)
56+
{
57+
if (fieldSet.Contains(attribute))
58+
{
59+
yield return attribute;
60+
}
61+
}
5062
}
5163

5264
/// <inheritdoc />

0 commit comments

Comments
 (0)