Skip to content

Commit 1c2726b

Browse files
author
Bart Koelman
committed
Changed IResourceContextProvider.GetResourceContexts() to return a set instead of a list. Made ResourceContext sealed and immutable and implemented Equals/GetHashCode so it works with sets.
1 parent 56cf588 commit 1c2726b

File tree

7 files changed

+134
-84
lines changed

7 files changed

+134
-84
lines changed

src/JsonApiDotNetCore/Configuration/IResourceContextProvider.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ namespace JsonApiDotNetCore.Configuration
1010
public interface IResourceContextProvider
1111
{
1212
/// <summary>
13-
/// Gets all registered resource contexts.
13+
/// Gets the metadata for all registered resources.
1414
/// </summary>
15-
IReadOnlyCollection<ResourceContext> GetResourceContexts();
15+
IReadOnlySet<ResourceContext> GetResourceContexts();
1616

1717
/// <summary>
18-
/// Gets the resource metadata for the specified exposed resource name.
18+
/// Gets the resource metadata for the resource that is publicly exposed by the specified name.
1919
/// </summary>
20-
ResourceContext GetResourceContext(string resourceName);
20+
ResourceContext GetResourceContext(string publicName);
2121

2222
/// <summary>
2323
/// Gets the resource metadata for the specified resource type.

src/JsonApiDotNetCore/Configuration/ResourceContext.cs

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,39 @@ namespace JsonApiDotNetCore.Configuration
1010
/// Provides metadata for a resource, such as its attributes and relationships.
1111
/// </summary>
1212
[PublicAPI]
13-
public class ResourceContext
13+
public sealed class ResourceContext
1414
{
1515
private IReadOnlyCollection<ResourceFieldAttribute> _fields;
1616

1717
/// <summary>
1818
/// The publicly exposed resource name.
1919
/// </summary>
20-
public string PublicName { get; set; }
20+
public string PublicName { get; }
2121

2222
/// <summary>
2323
/// The CLR type of the resource.
2424
/// </summary>
25-
public Type ResourceType { get; set; }
25+
public Type ResourceType { get; }
2626

2727
/// <summary>
2828
/// The identity type of the resource.
2929
/// </summary>
30-
public Type IdentityType { get; set; }
30+
public Type IdentityType { get; }
3131

3232
/// <summary>
3333
/// Exposed resource attributes. See https://jsonapi.org/format/#document-resource-object-attributes.
3434
/// </summary>
35-
public IReadOnlyCollection<AttrAttribute> Attributes { get; set; }
35+
public IReadOnlyCollection<AttrAttribute> Attributes { get; }
3636

3737
/// <summary>
3838
/// Exposed resource relationships. See https://jsonapi.org/format/#document-resource-object-relationships.
3939
/// </summary>
40-
public IReadOnlyCollection<RelationshipAttribute> Relationships { get; set; }
40+
public IReadOnlyCollection<RelationshipAttribute> Relationships { get; }
4141

4242
/// <summary>
4343
/// Related entities that are not exposed as resource relationships.
4444
/// </summary>
45-
public IReadOnlyCollection<EagerLoadAttribute> EagerLoads { get; set; }
45+
public IReadOnlyCollection<EagerLoadAttribute> EagerLoads { get; }
4646

4747
/// <summary>
4848
/// Exposed resource attributes and relationships. See https://jsonapi.org/format/#document-resource-object-fields.
@@ -56,7 +56,7 @@ public class ResourceContext
5656
/// <remarks>
5757
/// In the process of building the resource graph, this value is set based on <see cref="ResourceLinksAttribute.TopLevelLinks" /> usage.
5858
/// </remarks>
59-
public LinkTypes TopLevelLinks { get; internal set; } = LinkTypes.NotConfigured;
59+
public LinkTypes TopLevelLinks { get; }
6060

6161
/// <summary>
6262
/// Configures which links to show in the <see cref="Serialization.Objects.ResourceLinks" /> object for this resource type. Defaults to
@@ -65,7 +65,7 @@ public class ResourceContext
6565
/// <remarks>
6666
/// In the process of building the resource graph, this value is set based on <see cref="ResourceLinksAttribute.ResourceLinks" /> usage.
6767
/// </remarks>
68-
public LinkTypes ResourceLinks { get; internal set; } = LinkTypes.NotConfigured;
68+
public LinkTypes ResourceLinks { get; }
6969

7070
/// <summary>
7171
/// Configures which links to show in the <see cref="Serialization.Objects.RelationshipLinks" /> object for all relationships of this resource type.
@@ -75,11 +75,83 @@ public class ResourceContext
7575
/// <remarks>
7676
/// In the process of building the resource graph, this value is set based on <see cref="ResourceLinksAttribute.RelationshipLinks" /> usage.
7777
/// </remarks>
78-
public LinkTypes RelationshipLinks { get; internal set; } = LinkTypes.NotConfigured;
78+
public LinkTypes RelationshipLinks { get; }
79+
80+
public ResourceContext(string publicName, Type resourceType, Type identityType, IReadOnlyCollection<AttrAttribute> attributes,
81+
IReadOnlyCollection<RelationshipAttribute> relationships, IReadOnlyCollection<EagerLoadAttribute> eagerLoads,
82+
LinkTypes topLevelLinks = LinkTypes.NotConfigured, LinkTypes resourceLinks = LinkTypes.NotConfigured,
83+
LinkTypes relationshipLinks = LinkTypes.NotConfigured)
84+
{
85+
ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName));
86+
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
87+
ArgumentGuard.NotNull(identityType, nameof(identityType));
88+
ArgumentGuard.NotNull(attributes, nameof(attributes));
89+
ArgumentGuard.NotNull(relationships, nameof(relationships));
90+
ArgumentGuard.NotNull(eagerLoads, nameof(eagerLoads));
91+
92+
PublicName = publicName;
93+
ResourceType = resourceType;
94+
IdentityType = identityType;
95+
Attributes = attributes;
96+
Relationships = relationships;
97+
EagerLoads = eagerLoads;
98+
TopLevelLinks = topLevelLinks;
99+
ResourceLinks = resourceLinks;
100+
RelationshipLinks = relationshipLinks;
101+
}
79102

80103
public override string ToString()
81104
{
82105
return PublicName;
83106
}
107+
108+
public override bool Equals(object obj)
109+
{
110+
if (ReferenceEquals(this, obj))
111+
{
112+
return true;
113+
}
114+
115+
if (obj is null || GetType() != obj.GetType())
116+
{
117+
return false;
118+
}
119+
120+
var other = (ResourceContext)obj;
121+
122+
return PublicName == other.PublicName && ResourceType == other.ResourceType && IdentityType == other.IdentityType &&
123+
Attributes.SequenceEqual(other.Attributes) && Relationships.SequenceEqual(other.Relationships) && EagerLoads.SequenceEqual(other.EagerLoads) &&
124+
TopLevelLinks == other.TopLevelLinks && ResourceLinks == other.ResourceLinks && RelationshipLinks == other.RelationshipLinks;
125+
}
126+
127+
public override int GetHashCode()
128+
{
129+
var hashCode = new HashCode();
130+
131+
hashCode.Add(PublicName);
132+
hashCode.Add(ResourceType);
133+
hashCode.Add(IdentityType);
134+
135+
foreach (AttrAttribute attribute in Attributes)
136+
{
137+
hashCode.Add(attribute);
138+
}
139+
140+
foreach (RelationshipAttribute relationship in Relationships)
141+
{
142+
hashCode.Add(relationship);
143+
}
144+
145+
foreach (EagerLoadAttribute eagerLoad in EagerLoads)
146+
{
147+
hashCode.Add(eagerLoad);
148+
}
149+
150+
hashCode.Add(TopLevelLinks);
151+
hashCode.Add(ResourceLinks);
152+
hashCode.Add(RelationshipLinks);
153+
154+
return hashCode.ToHashCode();
155+
}
84156
}
85157
}

src/JsonApiDotNetCore/Configuration/ResourceGraph.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,27 @@ namespace JsonApiDotNetCore.Configuration
1414
public sealed class ResourceGraph : IResourceGraph
1515
{
1616
private static readonly Type ProxyTargetAccessorType = Type.GetType("Castle.DynamicProxy.IProxyTargetAccessor, Castle.Core");
17-
private readonly IReadOnlyCollection<ResourceContext> _resources;
17+
private readonly IReadOnlySet<ResourceContext> _resourceContexts;
1818

19-
public ResourceGraph(IReadOnlyCollection<ResourceContext> resources)
19+
public ResourceGraph(IReadOnlySet<ResourceContext> resourceContexts)
2020
{
21-
ArgumentGuard.NotNull(resources, nameof(resources));
21+
ArgumentGuard.NotNull(resourceContexts, nameof(resourceContexts));
2222

23-
_resources = resources;
23+
_resourceContexts = resourceContexts;
2424
}
2525

2626
/// <inheritdoc />
27-
public IReadOnlyCollection<ResourceContext> GetResourceContexts()
27+
public IReadOnlySet<ResourceContext> GetResourceContexts()
2828
{
29-
return _resources;
29+
return _resourceContexts;
3030
}
3131

3232
/// <inheritdoc />
33-
public ResourceContext GetResourceContext(string resourceName)
33+
public ResourceContext GetResourceContext(string publicName)
3434
{
35-
ArgumentGuard.NotNullNorEmpty(resourceName, nameof(resourceName));
35+
ArgumentGuard.NotNullNorEmpty(publicName, nameof(publicName));
3636

37-
return _resources.SingleOrDefault(resourceContext => resourceContext.PublicName == resourceName);
37+
return _resourceContexts.SingleOrDefault(resourceContext => resourceContext.PublicName == publicName);
3838
}
3939

4040
/// <inheritdoc />
@@ -43,8 +43,8 @@ public ResourceContext GetResourceContext(Type resourceType)
4343
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
4444

4545
return IsLazyLoadingProxyForResourceType(resourceType)
46-
? _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType)
47-
: _resources.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType);
46+
? _resourceContexts.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType.BaseType)
47+
: _resourceContexts.SingleOrDefault(resourceContext => resourceContext.ResourceType == resourceType);
4848
}
4949

5050
private bool IsLazyLoadingProxyForResourceType(Type resourceType)

src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class ResourceGraphBuilder
1717
{
1818
private readonly IJsonApiOptions _options;
1919
private readonly ILogger<ResourceGraphBuilder> _logger;
20-
private readonly List<ResourceContext> _resources = new();
20+
private readonly HashSet<ResourceContext> _resourceContexts = new();
2121
private readonly TypeLocator _typeLocator = new();
2222

2323
public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactory)
@@ -34,20 +34,7 @@ public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactor
3434
/// </summary>
3535
public IResourceGraph Build()
3636
{
37-
_resources.ForEach(SetResourceLinksOptions);
38-
return new ResourceGraph(_resources);
39-
}
40-
41-
private void SetResourceLinksOptions(ResourceContext resourceContext)
42-
{
43-
var attribute = (ResourceLinksAttribute)resourceContext.ResourceType.GetCustomAttribute(typeof(ResourceLinksAttribute));
44-
45-
if (attribute != null)
46-
{
47-
resourceContext.RelationshipLinks = attribute.RelationshipLinks;
48-
resourceContext.ResourceLinks = attribute.ResourceLinks;
49-
resourceContext.TopLevelLinks = attribute.TopLevelLinks;
50-
}
37+
return new ResourceGraph(_resourceContexts);
5138
}
5239

5340
/// <summary>
@@ -102,7 +89,7 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu
10289
{
10390
ArgumentGuard.NotNull(resourceType, nameof(resourceType));
10491

105-
if (_resources.Any(resourceContext => resourceContext.ResourceType == resourceType))
92+
if (_resourceContexts.Any(resourceContext => resourceContext.ResourceType == resourceType))
10693
{
10794
return this;
10895
}
@@ -113,7 +100,7 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu
113100
Type effectiveIdType = idType ?? _typeLocator.TryGetIdType(resourceType);
114101

115102
ResourceContext resourceContext = CreateResourceContext(effectivePublicName, resourceType, effectiveIdType);
116-
_resources.Add(resourceContext);
103+
_resourceContexts.Add(resourceContext);
117104
}
118105
else
119106
{
@@ -125,15 +112,16 @@ public ResourceGraphBuilder Add(Type resourceType, Type idType = null, string pu
125112

126113
private ResourceContext CreateResourceContext(string publicName, Type resourceType, Type idType)
127114
{
128-
return new()
129-
{
130-
PublicName = publicName,
131-
ResourceType = resourceType,
132-
IdentityType = idType,
133-
Attributes = GetAttributes(resourceType),
134-
Relationships = GetRelationships(resourceType),
135-
EagerLoads = GetEagerLoads(resourceType)
136-
};
115+
IReadOnlyCollection<AttrAttribute> attributes = GetAttributes(resourceType);
116+
IReadOnlyCollection<RelationshipAttribute> relationships = GetRelationships(resourceType);
117+
IReadOnlyCollection<EagerLoadAttribute> eagerLoads = GetEagerLoads(resourceType);
118+
119+
var linksAttribute = (ResourceLinksAttribute)resourceType.GetCustomAttribute(typeof(ResourceLinksAttribute));
120+
121+
return linksAttribute == null
122+
? new ResourceContext(publicName, resourceType, idType, attributes, relationships, eagerLoads)
123+
: new ResourceContext(publicName, resourceType, idType, attributes, relationships, eagerLoads, linksAttribute.TopLevelLinks,
124+
linksAttribute.ResourceLinks, linksAttribute.RelationshipLinks);
137125
}
138126

139127
private IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceType)

src/JsonApiDotNetCore/ObjectExtensions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ public static T[] AsArray<T>(this T element)
2121

2222
public static List<T> AsList<T>(this T element)
2323
{
24-
return new()
24+
return new List<T>
25+
{
26+
element
27+
};
28+
}
29+
30+
public static HashSet<T> AsHashSet<T>(this T element)
31+
{
32+
return new HashSet<T>
2533
{
2634
element
2735
};

test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,8 @@ public sealed class LinkInclusionTests
5656
public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected)
5757
{
5858
// Arrange
59-
var exampleResourceContext = new ResourceContext
60-
{
61-
PublicName = nameof(ExampleResource),
62-
ResourceType = typeof(ExampleResource),
63-
TopLevelLinks = linksInResourceContext
64-
};
59+
var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty<AttrAttribute>(),
60+
Array.Empty<RelationshipAttribute>(), Array.Empty<EagerLoadAttribute>(), linksInResourceContext);
6561

6662
var options = new JsonApiOptions
6763
{
@@ -84,7 +80,7 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso
8480
TotalResourceCount = 10
8581
};
8682

87-
var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray());
83+
var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet());
8884
var httpContextAccessor = new FakeHttpContextAccessor();
8985
var linkGenerator = new FakeLinkGenerator();
9086
var controllerResourceMapping = new FakeControllerResourceMapping();
@@ -157,12 +153,8 @@ public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInReso
157153
public void Applies_cascading_settings_for_resource_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected)
158154
{
159155
// Arrange
160-
var exampleResourceContext = new ResourceContext
161-
{
162-
PublicName = nameof(ExampleResource),
163-
ResourceType = typeof(ExampleResource),
164-
ResourceLinks = linksInResourceContext
165-
};
156+
var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty<AttrAttribute>(),
157+
Array.Empty<RelationshipAttribute>(), Array.Empty<EagerLoadAttribute>(), resourceLinks: linksInResourceContext);
166158

167159
var options = new JsonApiOptions
168160
{
@@ -171,7 +163,7 @@ public void Applies_cascading_settings_for_resource_links(LinkTypes linksInResou
171163

172164
var request = new JsonApiRequest();
173165
var paginationContext = new PaginationContext();
174-
var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray());
166+
var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet());
175167
var httpContextAccessor = new FakeHttpContextAccessor();
176168
var linkGenerator = new FakeLinkGenerator();
177169
var controllerResourceMapping = new FakeControllerResourceMapping();
@@ -323,12 +315,8 @@ public void Applies_cascading_settings_for_relationship_links(LinkTypes linksInR
323315
LinkTypes linksInOptions, LinkTypes expected)
324316
{
325317
// Arrange
326-
var exampleResourceContext = new ResourceContext
327-
{
328-
PublicName = nameof(ExampleResource),
329-
ResourceType = typeof(ExampleResource),
330-
RelationshipLinks = linksInResourceContext
331-
};
318+
var exampleResourceContext = new ResourceContext(nameof(ExampleResource), typeof(ExampleResource), typeof(int), Array.Empty<AttrAttribute>(),
319+
Array.Empty<RelationshipAttribute>(), Array.Empty<EagerLoadAttribute>(), relationshipLinks: linksInResourceContext);
332320

333321
var options = new JsonApiOptions
334322
{
@@ -337,7 +325,7 @@ public void Applies_cascading_settings_for_relationship_links(LinkTypes linksInR
337325

338326
var request = new JsonApiRequest();
339327
var paginationContext = new PaginationContext();
340-
var resourceGraph = new ResourceGraph(exampleResourceContext.AsArray());
328+
var resourceGraph = new ResourceGraph(exampleResourceContext.AsHashSet());
341329
var httpContextAccessor = new FakeHttpContextAccessor();
342330
var linkGenerator = new FakeLinkGenerator();
343331
var controllerResourceMapping = new FakeControllerResourceMapping();

0 commit comments

Comments
 (0)