@@ -21,7 +21,7 @@ public class ResourceGraphBuilder
21
21
{
22
22
private readonly IJsonApiOptions _options ;
23
23
private readonly ILogger < ResourceGraphBuilder > _logger ;
24
- private readonly HashSet < ResourceType > _resourceTypes = new ( ) ;
24
+ private readonly Dictionary < Type , ResourceType > _resourceTypesByClrType = new ( ) ;
25
25
private readonly TypeLocator _typeLocator = new ( ) ;
26
26
27
27
public ResourceGraphBuilder ( IJsonApiOptions options , ILoggerFactory loggerFactory )
@@ -38,12 +38,27 @@ public ResourceGraphBuilder(IJsonApiOptions options, ILoggerFactory loggerFactor
38
38
/// </summary>
39
39
public IResourceGraph Build ( )
40
40
{
41
- var resourceGraph = new ResourceGraph ( _resourceTypes ) ;
41
+ HashSet < ResourceType > resourceTypes = _resourceTypesByClrType . Values . ToHashSet ( ) ;
42
42
43
- foreach ( RelationshipAttribute relationship in _resourceTypes . SelectMany ( resourceType => resourceType . Relationships ) )
43
+ if ( ! resourceTypes . Any ( ) )
44
+ {
45
+ _logger . LogWarning ( "The resource graph is empty." ) ;
46
+ }
47
+
48
+ var resourceGraph = new ResourceGraph ( resourceTypes ) ;
49
+
50
+ foreach ( RelationshipAttribute relationship in resourceTypes . SelectMany ( resourceType => resourceType . Relationships ) )
44
51
{
45
52
relationship . LeftType = resourceGraph . GetResourceType ( relationship . LeftClrType ! ) ;
46
- relationship . RightType = resourceGraph . GetResourceType ( relationship . RightClrType ! ) ;
53
+ ResourceType ? rightType = resourceGraph . FindResourceType ( relationship . RightClrType ! ) ;
54
+
55
+ if ( rightType == null )
56
+ {
57
+ throw new InvalidConfigurationException ( $ "Resource type '{ relationship . LeftClrType } ' depends on " +
58
+ $ "'{ relationship . RightClrType } ', which was not added to the resource graph.") ;
59
+ }
60
+
61
+ relationship . RightType = rightType ;
47
62
}
48
63
49
64
return resourceGraph ;
@@ -123,7 +138,7 @@ public ResourceGraphBuilder Add(Type resourceClrType, Type? idClrType = null, st
123
138
{
124
139
ArgumentGuard . NotNull ( resourceClrType , nameof ( resourceClrType ) ) ;
125
140
126
- if ( _resourceTypes . Any ( resourceType => resourceType . ClrType == resourceClrType ) )
141
+ if ( _resourceTypesByClrType . ContainsKey ( resourceClrType ) )
127
142
{
128
143
return this ;
129
144
}
@@ -139,7 +154,10 @@ public ResourceGraphBuilder Add(Type resourceClrType, Type? idClrType = null, st
139
154
}
140
155
141
156
ResourceType resourceType = CreateResourceType ( effectivePublicName , resourceClrType , effectiveIdType ) ;
142
- _resourceTypes . Add ( resourceType ) ;
157
+
158
+ AssertNoDuplicatePublicName ( resourceType , effectivePublicName ) ;
159
+
160
+ _resourceTypesByClrType . Add ( resourceClrType , resourceType ) ;
143
161
}
144
162
else
145
163
{
@@ -155,6 +173,8 @@ private ResourceType CreateResourceType(string publicName, Type resourceClrType,
155
173
IReadOnlyCollection < RelationshipAttribute > relationships = GetRelationships ( resourceClrType ) ;
156
174
IReadOnlyCollection < EagerLoadAttribute > eagerLoads = GetEagerLoads ( resourceClrType ) ;
157
175
176
+ AssertNoDuplicatePublicName ( attributes , relationships ) ;
177
+
158
178
var linksAttribute = ( ResourceLinksAttribute ? ) resourceClrType . GetCustomAttribute ( typeof ( ResourceLinksAttribute ) ) ;
159
179
160
180
return linksAttribute == null
@@ -165,7 +185,7 @@ private ResourceType CreateResourceType(string publicName, Type resourceClrType,
165
185
166
186
private IReadOnlyCollection < AttrAttribute > GetAttributes ( Type resourceClrType )
167
187
{
168
- var attributes = new List < AttrAttribute > ( ) ;
188
+ var attributesByName = new Dictionary < string , AttrAttribute > ( ) ;
169
189
170
190
foreach ( PropertyInfo property in resourceClrType . GetProperties ( ) )
171
191
{
@@ -181,7 +201,7 @@ private IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceClrType)
181
201
Capabilities = _options . DefaultAttrCapabilities
182
202
} ;
183
203
184
- attributes . Add ( idAttr ) ;
204
+ IncludeField ( attributesByName , idAttr ) ;
185
205
continue ;
186
206
}
187
207
@@ -200,15 +220,20 @@ private IReadOnlyCollection<AttrAttribute> GetAttributes(Type resourceClrType)
200
220
attribute . Capabilities = _options . DefaultAttrCapabilities ;
201
221
}
202
222
203
- attributes . Add ( attribute ) ;
223
+ IncludeField ( attributesByName , attribute ) ;
204
224
}
205
225
206
- return attributes ;
226
+ if ( attributesByName . Count < 2 )
227
+ {
228
+ _logger . LogWarning ( $ "Type '{ resourceClrType } ' does not contain any attributes.") ;
229
+ }
230
+
231
+ return attributesByName . Values ;
207
232
}
208
233
209
234
private IReadOnlyCollection < RelationshipAttribute > GetRelationships ( Type resourceClrType )
210
235
{
211
- var relationships = new List < RelationshipAttribute > ( ) ;
236
+ var relationshipsByName = new Dictionary < string , RelationshipAttribute > ( ) ;
212
237
PropertyInfo [ ] properties = resourceClrType . GetProperties ( ) ;
213
238
214
239
foreach ( PropertyInfo property in properties )
@@ -222,11 +247,11 @@ private IReadOnlyCollection<RelationshipAttribute> GetRelationships(Type resourc
222
247
relationship . LeftClrType = resourceClrType ;
223
248
relationship . RightClrType = GetRelationshipType ( relationship , property ) ;
224
249
225
- relationships . Add ( relationship ) ;
250
+ IncludeField ( relationshipsByName , relationship ) ;
226
251
}
227
252
}
228
253
229
- return relationships ;
254
+ return relationshipsByName . Values ;
230
255
}
231
256
232
257
private void SetPublicName ( ResourceFieldAttribute field , PropertyInfo property )
@@ -269,6 +294,51 @@ private IReadOnlyCollection<EagerLoadAttribute> GetEagerLoads(Type resourceClrTy
269
294
return attributes ;
270
295
}
271
296
297
+ private static void IncludeField < TField > ( Dictionary < string , TField > fieldsByName , TField field )
298
+ where TField : ResourceFieldAttribute
299
+ {
300
+ if ( fieldsByName . TryGetValue ( field . PublicName , out var existingField ) )
301
+ {
302
+ throw CreateExceptionForDuplicatePublicName ( field . Property . DeclaringType ! , existingField , field ) ;
303
+ }
304
+
305
+ fieldsByName . Add ( field . PublicName , field ) ;
306
+ }
307
+
308
+ private void AssertNoDuplicatePublicName ( ResourceType resourceType , string effectivePublicName )
309
+ {
310
+ var ( existingClrType , _) = _resourceTypesByClrType . FirstOrDefault ( type => type . Value . PublicName == resourceType . PublicName ) ;
311
+
312
+ if ( existingClrType != null )
313
+ {
314
+ throw new InvalidConfigurationException (
315
+ $ "Resource '{ existingClrType } ' and '{ resourceType . ClrType } ' both use public name '{ effectivePublicName } '.") ;
316
+ }
317
+ }
318
+
319
+ private void AssertNoDuplicatePublicName ( IReadOnlyCollection < AttrAttribute > attributes , IReadOnlyCollection < RelationshipAttribute > relationships )
320
+ {
321
+ IEnumerable < ( AttrAttribute attribute , RelationshipAttribute relationship ) > query =
322
+ from attribute in attributes
323
+ from relationship in relationships
324
+ where attribute . PublicName == relationship . PublicName
325
+ select ( attribute , relationship ) ;
326
+
327
+ ( AttrAttribute ? duplicateAttribute , RelationshipAttribute ? duplicateRelationship ) = query . FirstOrDefault ( ) ;
328
+
329
+ if ( duplicateAttribute != null && duplicateRelationship != null )
330
+ {
331
+ throw CreateExceptionForDuplicatePublicName ( duplicateAttribute . Property . DeclaringType ! , duplicateAttribute , duplicateRelationship ) ;
332
+ }
333
+ }
334
+
335
+ private static InvalidConfigurationException CreateExceptionForDuplicatePublicName ( Type containingClrType , ResourceFieldAttribute existingField ,
336
+ ResourceFieldAttribute field )
337
+ {
338
+ return new InvalidConfigurationException (
339
+ $ "Properties '{ containingClrType } .{ existingField . Property . Name } ' and '{ containingClrType } .{ field . Property . Name } ' both use public name '{ field . PublicName } '.") ;
340
+ }
341
+
272
342
[ AssertionMethod ]
273
343
private static void AssertNoInfiniteRecursion ( int recursionDepth )
274
344
{
0 commit comments