Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 63ecf18

Browse filesBrowse files
committed
Merge branch 'master' into release/2.0
2 parents 16aac72 + 5cd1726 commit 63ecf18
Copy full SHA for 63ecf18

20 files changed

+692
-48
lines changed

‎src/.editorconfig

Copy file name to clipboardExpand all lines: src/.editorconfig
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ dotnet_naming_style.begins_with_i.word_separator =
197197
dotnet_naming_style.begins_with_i.capitalization = pascal_case
198198

199199

200-
201-
202200
[*.{cs,vb}]
203-
dotnet_style_prefer_compound_assignment=true:suggestion
201+
dotnet_style_prefer_compound_assignment=true:suggestion
202+
203+
file_header_template = *************************************************************\n project: graphql-aspnet\n --\n repo: https://github.com/graphql-aspnet\n docs: https://graphql-aspnet.github.io\n --\n License: MIT\n *************************************************************

‎src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs

Copy file name to clipboardExpand all lines: src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs
+88-21Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace GraphQL.AspNet.Configuration
1111
{
1212
using System;
13+
using System.Collections.Generic;
1314
using GraphQL.AspNet.Common;
1415
using GraphQL.AspNet.Common.Extensions;
1516
using GraphQL.AspNet.Configuration.Startup;
@@ -26,6 +27,52 @@ namespace GraphQL.AspNet.Configuration
2627
/// </summary>
2728
public static class GraphQLSchemaBuilderExtensions
2829
{
30+
private static readonly Dictionary<IServiceCollection, ISchemaInjectorCollection> SCHEMA_REGISTRATIONS;
31+
32+
/// <summary>
33+
/// Initializes static members of the <see cref="GraphQLSchemaBuilderExtensions"/> class.
34+
/// </summary>
35+
static GraphQLSchemaBuilderExtensions()
36+
{
37+
SCHEMA_REGISTRATIONS = new Dictionary<IServiceCollection, ISchemaInjectorCollection>();
38+
}
39+
40+
/// <summary>
41+
/// Helper method to null out the schema registration references. Useful in testing and after setup is complete there is no
42+
/// need to keep the reference chain in tact.
43+
/// </summary>
44+
/// <param name="serviceCollection">The service collection to clear.</param>
45+
public static void Clear(IServiceCollection serviceCollection)
46+
{
47+
lock (SCHEMA_REGISTRATIONS)
48+
{
49+
if (!SCHEMA_REGISTRATIONS.TryGetValue(serviceCollection, out var value))
50+
{
51+
return;
52+
}
53+
54+
value.Clear();
55+
SCHEMA_REGISTRATIONS.Remove(serviceCollection);
56+
}
57+
}
58+
59+
/// <summary>
60+
/// Helper method to null out all schema injector collections. Useful in testing and after setup is complete there is no
61+
/// need to keep the reference chain in tact.
62+
/// </summary>
63+
public static void Clear()
64+
{
65+
lock (SCHEMA_REGISTRATIONS)
66+
{
67+
foreach (var injectorCollection in SCHEMA_REGISTRATIONS.Values)
68+
{
69+
injectorCollection.Clear();
70+
}
71+
72+
SCHEMA_REGISTRATIONS.Clear();
73+
}
74+
}
75+
2976
/// <summary>
3077
/// Enables the query cache locally, in memory, to retain parsed query plans. When enabled, use the configuration
3178
/// settings for each added schema to determine how each will interact with the cache. Implement your own cache provider
@@ -54,19 +101,18 @@ public static ISchemaBuilder<TSchema> AddGraphQL<TSchema>(
54101
where TSchema : class, ISchema
55102
{
56103
Validation.ThrowIfNull(serviceCollection, nameof(serviceCollection));
57-
58-
var wasFound = GraphQLSchemaInjectorFactory.TryGetOrCreate(
59-
out var injector,
60-
serviceCollection,
61-
options);
62-
63-
if (wasFound)
104+
var injectorCollection = GetOrAddSchemaInjectorCollection(serviceCollection);
105+
if (injectorCollection.ContainsKey(typeof(TSchema)))
64106
{
65107
throw new InvalidOperationException(
66108
$"The schema type {typeof(TSchema).FriendlyName()} has already been registered. " +
67109
"Each schema type may only be registered once with GraphQL.");
68110
}
69111

112+
var schemaOptions = new SchemaOptions<TSchema>(serviceCollection);
113+
var injector = new GraphQLSchemaInjector<TSchema>(schemaOptions, options);
114+
injectorCollection.Add(typeof(TSchema), injector);
115+
70116
injector.ConfigureServices();
71117

72118
return injector.SchemaBuilder;
@@ -93,15 +139,13 @@ public static ISchemaBuilder<GraphSchema> AddGraphQL(
93139
/// <param name="app">The application being constructed.</param>
94140
public static void UseGraphQL(this IApplicationBuilder app)
95141
{
96-
Validation.ThrowIfNull(app, nameof(app));
97-
var allInjectors = app.ApplicationServices.GetServices<ISchemaInjector>();
98-
if (allInjectors != null)
142+
var injectorCollection = app.ApplicationServices.GetRequiredService<ISchemaInjectorCollection>();
143+
foreach (var injector in injectorCollection.Values)
99144
{
100-
foreach (var injector in allInjectors)
101-
{
102-
injector.UseSchema(app);
103-
}
145+
injector.UseSchema(app);
104146
}
147+
148+
Clear(injectorCollection.ServiceCollection);
105149
}
106150

107151
/// <summary>
@@ -118,15 +162,38 @@ public static void UseGraphQL(this IApplicationBuilder app)
118162
/// graphql runtime.</param>
119163
public static void UseGraphQL(this IServiceProvider serviceProvider)
120164
{
121-
Validation.ThrowIfNull(serviceProvider, nameof(serviceProvider));
122-
var allInjectors = serviceProvider.GetServices<ISchemaInjector>();
123-
if (allInjectors != null)
165+
var injectorCollection = serviceProvider.GetRequiredService<ISchemaInjectorCollection>();
166+
foreach (var injector in injectorCollection.Values)
124167
{
125-
foreach (var injector in allInjectors)
126-
{
127-
injector.UseSchema(serviceProvider);
128-
}
168+
injector.UseSchema(serviceProvider);
129169
}
170+
171+
Clear(injectorCollection.ServiceCollection);
172+
}
173+
174+
/// <summary>
175+
/// Get or create a schema injector collection for the service collection being harnessed.
176+
/// </summary>
177+
/// <param name="serviceCollection">The service collection to create schema injector collection for</param>
178+
/// <returns>Existing or new schema injector collection</returns>
179+
private static ISchemaInjectorCollection GetOrAddSchemaInjectorCollection(IServiceCollection serviceCollection)
180+
{
181+
if (SCHEMA_REGISTRATIONS.TryGetValue(serviceCollection, out var value))
182+
return value;
183+
184+
lock (SCHEMA_REGISTRATIONS)
185+
{
186+
if (SCHEMA_REGISTRATIONS.TryGetValue(serviceCollection, out value))
187+
return value;
188+
189+
var injectorCollection = new SchemaInjectorCollection(serviceCollection);
190+
191+
serviceCollection.AddSingleton<ISchemaInjectorCollection>(injectorCollection);
192+
value = injectorCollection;
193+
SCHEMA_REGISTRATIONS.Add(serviceCollection, value);
194+
}
195+
196+
return value;
130197
}
131198
}
132199
}
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// *************************************************************
2+
// project: graphql-aspnet
3+
// --
4+
// repo: https://github.com/graphql-aspnet
5+
// docs: https://graphql-aspnet.github.io
6+
// --
7+
// License: MIT
8+
// *************************************************************
9+
10+
namespace GraphQL.AspNet.Configuration
11+
{
12+
using System;
13+
using System.Collections.Generic;
14+
using GraphQL.AspNet.Common;
15+
using GraphQL.AspNet.Interfaces.Configuration;
16+
using Microsoft.Extensions.DependencyInjection;
17+
18+
/// <summary>
19+
/// A schema injector collection implementation.
20+
/// </summary>
21+
public class SchemaInjectorCollection : Dictionary<Type, ISchemaInjector>, ISchemaInjectorCollection
22+
{
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="SchemaInjectorCollection"/> class.
25+
/// </summary>
26+
/// <param name="serviceCollection">The service collection this set is tracking against.</param>
27+
public SchemaInjectorCollection(IServiceCollection serviceCollection)
28+
{
29+
this.ServiceCollection = Validation.ThrowIfNullOrReturn(serviceCollection, nameof(serviceCollection));
30+
}
31+
32+
/// <inheritdoc />
33+
public IServiceCollection ServiceCollection { get; }
34+
}
35+
}

‎src/graphql-aspnet/Execution/Resolvers/InputValueResolverMethodGenerator.cs

Copy file name to clipboardExpand all lines: src/graphql-aspnet/Execution/Resolvers/InputValueResolverMethodGenerator.cs
+24-19Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public InputValueResolverMethodGenerator(ISchema schema)
4646
public IInputValueResolver CreateResolver(GraphTypeExpression typeExpression)
4747
{
4848
// used for variable definitions
49-
return this.CreateResolver(typeExpression, null);
49+
return this.CreateResolverInternal(typeExpression, null);
5050
}
5151

5252
/// <summary>
@@ -57,7 +57,7 @@ public IInputValueResolver CreateResolver(GraphTypeExpression typeExpression)
5757
/// <returns>IQueryInputValueResolver.</returns>
5858
public IInputValueResolver CreateResolver(IInputGraphField field)
5959
{
60-
return this.CreateResolver(field.TypeExpression, field);
60+
return this.CreateResolverInternal(field.TypeExpression, field);
6161
}
6262

6363
/// <summary>
@@ -68,22 +68,28 @@ public IInputValueResolver CreateResolver(IInputGraphField field)
6868
/// <returns>IQueryInputValueResolver.</returns>
6969
public IInputValueResolver CreateResolver(IGraphArgument argument)
7070
{
71-
return this.CreateResolver(argument.TypeExpression, argument);
71+
return this.CreateResolverInternal(argument.TypeExpression, argument);
7272
}
7373

74-
private IInputValueResolver CreateResolver(GraphTypeExpression typeExpression, IDefaultValueSchemaItem defaultValueProvider)
74+
private IInputValueResolver CreateResolverInternal(GraphTypeExpression typeExpression, IDefaultValueSchemaItem defaultValueProvider)
7575
{
7676
Validation.ThrowIfNull(typeExpression, nameof(typeExpression));
7777

7878
var graphType = _schema.KnownTypes.FindGraphType(typeExpression.TypeName);
7979
if (graphType == null)
8080
return null;
8181

82-
return this.CreateResolver(graphType, typeExpression, defaultValueProvider);
82+
return this.CreateResolverInternal(graphType, typeExpression, defaultValueProvider);
8383
}
8484

85-
private IInputValueResolver CreateResolver(IGraphType graphType, GraphTypeExpression expression, IDefaultValueSchemaItem defaultValueProvider)
85+
private IInputValueResolver CreateResolverInternal(IGraphType graphType, GraphTypeExpression expression, IDefaultValueSchemaItem defaultValueProvider, Dictionary<IInputObjectGraphType, IInputValueResolver> trackedComplexResolvers = null)
8686
{
87+
// keep a list of complex value resolvers that were generated in this run
88+
// to prevent infinite loops for self referencing objects
89+
// all instnaces where a complex object needs to be resolved will use
90+
// the same referenced resolver, scoped to this single argument that is being generated
91+
trackedComplexResolvers = trackedComplexResolvers ?? new Dictionary<IInputObjectGraphType, IInputValueResolver>();
92+
8793
// extract the core resolver for the input type being processed
8894
IInputValueResolver coreResolver = null;
8995
Type coreType = null;
@@ -101,7 +107,7 @@ private IInputValueResolver CreateResolver(IGraphType graphType, GraphTypeExpres
101107
else if (graphType is IInputObjectGraphType inputType)
102108
{
103109
coreType = _schema.KnownTypes.FindConcreteType(inputType);
104-
coreResolver = this.CreateInputObjectResolver(inputType, coreType, defaultValueProvider);
110+
coreResolver = this.CreateInputObjectResolver(inputType, coreType, defaultValueProvider, trackedComplexResolvers);
105111
}
106112

107113
// wrap any list wrappers around core resolver according to the type expression
@@ -117,23 +123,22 @@ private IInputValueResolver CreateResolver(IGraphType graphType, GraphTypeExpres
117123
return coreResolver;
118124
}
119125

120-
private IInputValueResolver CreateInputObjectResolver(IInputObjectGraphType inputType, Type type, IDefaultValueSchemaItem defaultValueProvider)
126+
private IInputValueResolver CreateInputObjectResolver(
127+
IInputObjectGraphType inputType,
128+
Type type,
129+
IDefaultValueSchemaItem defaultValueProvider,
130+
Dictionary<IInputObjectGraphType, IInputValueResolver> trackedComplexResolvers)
121131
{
132+
if (trackedComplexResolvers.TryGetValue(inputType, out var alreadyBuiltResolver))
133+
return alreadyBuiltResolver;
134+
122135
var inputObjectResolver = new InputObjectValueResolver(inputType, type, _schema, defaultValueProvider);
136+
trackedComplexResolvers.Add(inputType, inputObjectResolver);
123137

124138
foreach (var field in inputType.Fields)
125139
{
126-
IInputValueResolver childResolver;
127-
if (field.TypeExpression.TypeName == inputType.Name)
128-
{
129-
childResolver = inputObjectResolver;
130-
}
131-
else
132-
{
133-
var graphType = _schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName);
134-
childResolver = this.CreateResolver(graphType, field.TypeExpression, field);
135-
}
136-
140+
var graphType = _schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName);
141+
var childResolver = this.CreateResolverInternal(graphType, field.TypeExpression, field, trackedComplexResolvers);
137142
inputObjectResolver.AddFieldResolver(field.Name, childResolver);
138143
}
139144

‎src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_3_2_FieldsOfIdenticalOutputMustHaveIdenticalSigs.cs

Copy file name to clipboardExpand all lines: src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_3_2_FieldsOfIdenticalOutputMustHaveIdenticalSigs.cs
+28-1Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,22 @@ private bool CompareAllFields(
7676
if (fields.Count < 2)
7777
return true;
7878

79+
// must compare every field to every other field
80+
// inside the field set O(n^2) time :(
81+
// take a short cut if some fields are not in a state
82+
// that warrants complete validation
7983
for (var i = 0; i < fields.Count - 1; i++)
8084
{
85+
if (!this.ShouldBeValidatedForRule(fields[i]))
86+
continue;
87+
8188
for (var j = i + 1; j < fields.Count; j++)
8289
{
90+
// don't attempt to perform a validation unless both
91+
// fields are correct and validatable
92+
if (!this.ShouldBeValidatedForRule(fields[j]))
93+
continue;
94+
8395
var passedValidation = this.ValidateFieldPair(
8496
context,
8597
ownerField,
@@ -226,6 +238,21 @@ private bool AreSameShape(IFieldDocumentPart leftField, IFieldDocumentPart right
226238
return true;
227239
}
228240

241+
/// <summary>
242+
/// Determines whether the field document part should be validated against this rule. Field
243+
/// document parts may not qualify for validation if other errors may exist within them.
244+
/// </summary>
245+
/// <param name="fieldToCheck">The field to check.</param>
246+
/// <returns><c>true</c> if the document parts can be checked for co-existance; otherwise, <c>false</c>.</returns>
247+
private bool ShouldBeValidatedForRule(IFieldDocumentPart fieldToCheck)
248+
{
249+
IGraphType graphType = null;
250+
if (fieldToCheck.Parent is IFieldSelectionSetDocumentPart fsdl)
251+
graphType = fsdl.GraphType;
252+
253+
return graphType != null;
254+
}
255+
229256
/// <summary>
230257
/// Inspects both fields to see if any target graph type restrctions exist (such as with fragmetn spreads)
231258
/// such that the fields could not be included in the same object resolution. For example if the existing field targets a
@@ -246,7 +273,7 @@ private bool CanCoExist(ISchema targetSchema, IFieldDocumentPart leftField, IFie
246273
if (rightField.Parent is IFieldSelectionSetDocumentPart fsdr)
247274
rightSourceGraphType = fsdr.GraphType;
248275

249-
// neither should be null at this point
276+
// last ditch safety check: neither should be null at this point.
250277
if (leftSourceGraphType == null)
251278
{
252279
throw new GraphExecutionException(

‎src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryFragmentSteps/Rule_5_5_1_2_InlineFragmentGraphTypeMustExistInTheSchema.cs

Copy file name to clipboardExpand all lines: src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryFragmentSteps/Rule_5_5_1_2_InlineFragmentGraphTypeMustExistInTheSchema.cs
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public override bool Execute(DocumentValidationContext context)
2828
{
2929
this.ValidationError(
3030
context,
31-
$"No known graph type was found for the target fragment.");
31+
$"No known graph type was found for the target fragment (Target: '{fragment.TargetGraphTypeName}').");
3232

3333
return false;
3434
}
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// *************************************************************
2+
// project: graphql-aspnet
3+
// --
4+
// repo: https://github.com/graphql-aspnet
5+
// docs: https://graphql-aspnet.github.io
6+
// --
7+
// License: MIT
8+
// *************************************************************
9+
10+
namespace GraphQL.AspNet.Interfaces.Configuration
11+
{
12+
using System;
13+
using System.Collections.Generic;
14+
using Microsoft.Extensions.DependencyInjection;
15+
16+
/// <summary>
17+
/// An interface used by the injector to track multiple schema injectors
18+
/// used for injecting.
19+
/// </summary>
20+
public interface ISchemaInjectorCollection : IDictionary<Type, ISchemaInjector>
21+
{
22+
/// <summary>
23+
/// Gets the service collection to which this collection is attached.
24+
/// </summary>
25+
/// <value>The attached service collection.</value>
26+
public IServiceCollection ServiceCollection { get; }
27+
}
28+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.