Skip to content

Commit 6db4cf0

Browse files
author
Bart Koelman
committed
Resource inheritance: sparse fieldsets
1 parent 4a29199 commit 6db4cf0

File tree

10 files changed

+753
-169
lines changed

10 files changed

+753
-169
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Text;
2+
using JetBrains.Annotations;
3+
using JsonApiDotNetCore.Configuration;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCore.Queries;
7+
8+
/// <summary>
9+
/// Provides access to sparse fieldsets, per resource type. There's usually just a single resource type, but there can be multiple in case an endpoint
10+
/// for an abstract resource type returns derived types.
11+
/// </summary>
12+
[PublicAPI]
13+
public sealed class FieldSelection : Dictionary<ResourceType, FieldSelectors>
14+
{
15+
public bool IsEmpty => Values.All(selectors => selectors.IsEmpty);
16+
17+
public ISet<ResourceType> GetResourceTypes()
18+
{
19+
return Keys.ToHashSet();
20+
}
21+
22+
#pragma warning disable AV1130 // Return type in method signature should be a collection interface instead of a concrete type
23+
public FieldSelectors GetOrCreateSelectors(ResourceType resourceType)
24+
#pragma warning restore AV1130 // Return type in method signature should be a collection interface instead of a concrete type
25+
{
26+
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
27+
28+
if (!ContainsKey(resourceType))
29+
{
30+
this[resourceType] = new FieldSelectors();
31+
}
32+
33+
return this[resourceType];
34+
}
35+
36+
public override string ToString()
37+
{
38+
var builder = new StringBuilder();
39+
40+
var writer = new IndentingStringWriter(builder);
41+
WriteSelection(writer);
42+
43+
return builder.ToString();
44+
}
45+
46+
internal void WriteSelection(IndentingStringWriter writer)
47+
{
48+
using (writer.Indent())
49+
{
50+
foreach (ResourceType type in GetResourceTypes())
51+
{
52+
writer.WriteLine($"{nameof(FieldSelectors)}<{type.ClrType.Name}>");
53+
WriterSelectors(writer, type);
54+
}
55+
}
56+
}
57+
58+
private void WriterSelectors(IndentingStringWriter writer, ResourceType type)
59+
{
60+
using (writer.Indent())
61+
{
62+
foreach ((ResourceFieldAttribute field, QueryLayer? nextLayer) in GetOrCreateSelectors(type))
63+
{
64+
if (nextLayer == null)
65+
{
66+
writer.WriteLine(field.ToString());
67+
}
68+
else
69+
{
70+
nextLayer.WriteLayer(writer, $"{field.PublicName}: ");
71+
}
72+
}
73+
}
74+
}
75+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Resources.Annotations;
3+
4+
namespace JsonApiDotNetCore.Queries;
5+
6+
/// <summary>
7+
/// A data structure that contains which fields (attributes and relationships) to retrieve, or empty to retrieve all. In the case of a relationship, it
8+
/// contains the nested query constraints.
9+
/// </summary>
10+
[PublicAPI]
11+
public sealed class FieldSelectors : Dictionary<ResourceFieldAttribute, QueryLayer?>
12+
{
13+
public bool IsEmpty => !this.Any();
14+
15+
public bool ContainsReadOnlyAttribute
16+
{
17+
get
18+
{
19+
return this.Any(selector => selector.Key is AttrAttribute attribute && attribute.Property.SetMethod == null);
20+
}
21+
}
22+
23+
public bool ContainsOnlyRelationships
24+
{
25+
get
26+
{
27+
return this.All(selector => selector.Key is RelationshipAttribute);
28+
}
29+
}
30+
31+
public bool ContainsField(ResourceFieldAttribute field)
32+
{
33+
ArgumentGuard.NotNull(field, nameof(field));
34+
35+
return ContainsKey(field);
36+
}
37+
38+
public void IncludeAttribute(AttrAttribute attribute)
39+
{
40+
ArgumentGuard.NotNull(attribute, nameof(attribute));
41+
42+
this[attribute] = null;
43+
}
44+
45+
public void IncludeAttributes(IEnumerable<AttrAttribute> attributes)
46+
{
47+
ArgumentGuard.NotNull(attributes, nameof(attributes));
48+
49+
foreach (AttrAttribute attribute in attributes)
50+
{
51+
this[attribute] = null;
52+
}
53+
}
54+
55+
public void IncludeRelationship(RelationshipAttribute relationship, QueryLayer? queryLayer)
56+
{
57+
ArgumentGuard.NotNull(relationship, nameof(relationship));
58+
59+
this[relationship] = queryLayer;
60+
}
61+
62+
public void RemoveAttributes()
63+
{
64+
while (this.Any(pair => pair.Key is AttrAttribute))
65+
{
66+
ResourceFieldAttribute field = this.First(pair => pair.Key is AttrAttribute).Key;
67+
Remove(field);
68+
}
69+
}
70+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Text;
2+
3+
namespace JsonApiDotNetCore.Queries;
4+
5+
internal sealed class IndentingStringWriter : IDisposable
6+
{
7+
private readonly StringBuilder _builder;
8+
9+
private int _indentDepth;
10+
11+
public IndentingStringWriter(StringBuilder builder)
12+
{
13+
_builder = builder;
14+
}
15+
16+
public void WriteLine(string? line)
17+
{
18+
if (_indentDepth > 0)
19+
{
20+
_builder.Append(new string(' ', _indentDepth * 2));
21+
}
22+
23+
_builder.AppendLine(line);
24+
}
25+
26+
public IndentingStringWriter Indent()
27+
{
28+
WriteLine("{");
29+
_indentDepth++;
30+
return this;
31+
}
32+
33+
public void Dispose()
34+
{
35+
if (_indentDepth > 0)
36+
{
37+
_indentDepth--;
38+
WriteLine("}");
39+
}
40+
}
41+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ private ResourceType ParseSparseFieldTarget()
3737

3838
EatSingleCharacterToken(TokenKind.OpenBracket);
3939

40-
ResourceType resourceType = ParseResourceName();
40+
ResourceType resourceType = ParseResourceType();
4141

4242
EatSingleCharacterToken(TokenKind.CloseBracket);
4343

4444
return resourceType;
4545
}
4646

47-
private ResourceType ParseResourceName()
47+
private ResourceType ParseResourceType()
4848
{
4949
if (TokenStack.TryPop(out Token? token) && token.Kind == TokenKind.Text)
5050
{

0 commit comments

Comments
 (0)