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

Bug: Fixed self-referencing input object lists #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions 6 src/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case




[*.{cs,vb}]
dotnet_style_prefer_compound_assignment=true:suggestion
dotnet_style_prefer_compound_assignment=true:suggestion

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 *************************************************************
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public InputValueResolverMethodGenerator(ISchema schema)
public IInputValueResolver CreateResolver(GraphTypeExpression typeExpression)
{
// used for variable definitions
return this.CreateResolver(typeExpression, null);
return this.CreateResolverInternal(typeExpression, null);
}

/// <summary>
Expand All @@ -57,7 +57,7 @@ public IInputValueResolver CreateResolver(GraphTypeExpression typeExpression)
/// <returns>IQueryInputValueResolver.</returns>
public IInputValueResolver CreateResolver(IInputGraphField field)
{
return this.CreateResolver(field.TypeExpression, field);
return this.CreateResolverInternal(field.TypeExpression, field);
}

/// <summary>
Expand All @@ -68,22 +68,28 @@ public IInputValueResolver CreateResolver(IInputGraphField field)
/// <returns>IQueryInputValueResolver.</returns>
public IInputValueResolver CreateResolver(IGraphArgument argument)
{
return this.CreateResolver(argument.TypeExpression, argument);
return this.CreateResolverInternal(argument.TypeExpression, argument);
}

private IInputValueResolver CreateResolver(GraphTypeExpression typeExpression, IDefaultValueSchemaItem defaultValueProvider)
private IInputValueResolver CreateResolverInternal(GraphTypeExpression typeExpression, IDefaultValueSchemaItem defaultValueProvider)
{
Validation.ThrowIfNull(typeExpression, nameof(typeExpression));

var graphType = _schema.KnownTypes.FindGraphType(typeExpression.TypeName);
if (graphType == null)
return null;

return this.CreateResolver(graphType, typeExpression, defaultValueProvider);
return this.CreateResolverInternal(graphType, typeExpression, defaultValueProvider);
}

private IInputValueResolver CreateResolver(IGraphType graphType, GraphTypeExpression expression, IDefaultValueSchemaItem defaultValueProvider)
private IInputValueResolver CreateResolverInternal(IGraphType graphType, GraphTypeExpression expression, IDefaultValueSchemaItem defaultValueProvider, Dictionary<IInputObjectGraphType, IInputValueResolver> trackedComplexResolvers = null)
{
// keep a list of complex value resolvers that were generated in this run
// to prevent infinite loops for self referencing objects
// all instnaces where a complex object needs to be resolved will use
// the same referenced resolver, scoped to this single argument that is being generated
trackedComplexResolvers = trackedComplexResolvers ?? new Dictionary<IInputObjectGraphType, IInputValueResolver>();

// extract the core resolver for the input type being processed
IInputValueResolver coreResolver = null;
Type coreType = null;
Expand All @@ -101,7 +107,7 @@ private IInputValueResolver CreateResolver(IGraphType graphType, GraphTypeExpres
else if (graphType is IInputObjectGraphType inputType)
{
coreType = _schema.KnownTypes.FindConcreteType(inputType);
coreResolver = this.CreateInputObjectResolver(inputType, coreType, defaultValueProvider);
coreResolver = this.CreateInputObjectResolver(inputType, coreType, defaultValueProvider, trackedComplexResolvers);
}

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

private IInputValueResolver CreateInputObjectResolver(IInputObjectGraphType inputType, Type type, IDefaultValueSchemaItem defaultValueProvider)
private IInputValueResolver CreateInputObjectResolver(
IInputObjectGraphType inputType,
Type type,
IDefaultValueSchemaItem defaultValueProvider,
Dictionary<IInputObjectGraphType, IInputValueResolver> trackedComplexResolvers)
{
if (trackedComplexResolvers.TryGetValue(inputType, out var alreadyBuiltResolver))
return alreadyBuiltResolver;

var inputObjectResolver = new InputObjectValueResolver(inputType, type, _schema, defaultValueProvider);
trackedComplexResolvers.Add(inputType, inputObjectResolver);

foreach (var field in inputType.Fields)
{
IInputValueResolver childResolver;
if (field.TypeExpression.TypeName == inputType.Name)
{
childResolver = inputObjectResolver;
}
else
{
var graphType = _schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName);
childResolver = this.CreateResolver(graphType, field.TypeExpression, field);
}

var graphType = _schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName);
var childResolver = this.CreateResolverInternal(graphType, field.TypeExpression, field, trackedComplexResolvers);
inputObjectResolver.AddFieldResolver(field.Name, childResolver);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,50 @@ public async Task Record_asInputObject_RendersObjectCorrectly()
var result = await server.RenderResult(builder);
CommonAssertions.AreEqualJsonStrings(expectedOutput, result);
}

[Test]
public async Task Self_Referencing_Nested_List_Input()
{
var server = new TestServerBuilder()
.AddGraphQL(o =>
{
o.AddType<SelfReferencingInputObjectController>();
o.ResponseOptions.ExposeExceptions = true;
})
.Build();

// totalPeople exists on base controller
// totalEmployees exists on the added EmployeeController
var builder = server.CreateQueryContextBuilder()
.AddQueryText(@"query {
countNestings(item:
{
name: ""root"",
children: [
{
name: ""child1"",
children: [
{ name: ""child1.1""},
{name: ""child1.2""}
]
},
{name: ""child2""}
]
})
}");

var expectedOutput =
@"{
""data"": {
""countNestings"" : 5
}
}";

var result = await server.ExecuteQuery(builder);
Assert.AreEqual(0, result.Messages.Count);

var renderedResult = await server.RenderResult(builder);
CommonAssertions.AreEqualJsonStrings(expectedOutput, renderedResult);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// *************************************************************
// project: graphql-aspnet
// --
// repo: https://github.com/graphql-aspnet
// docs: https://graphql-aspnet.github.io
// --
// License: MIT
// *************************************************************

namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData
{
using System.Collections.Generic;

public class SelfReferencingInputObject
{
public string Name { get; set; }

public IEnumerable<SelfReferencingInputObject> Children { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// *************************************************************
// project: graphql-aspnet
// --
// repo: https://github.com/graphql-aspnet
// docs: https://graphql-aspnet.github.io
// --
// License: MIT
// *************************************************************

namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData
{
using System.Collections.Generic;
using GraphQL.AspNet.Attributes;
using GraphQL.AspNet.Controllers;

public class SelfReferencingInputObjectController : GraphController
{
[QueryRoot]
public int CountNestings(SelfReferencingInputObject item)
{
var i = 0;
var stack = new Stack<SelfReferencingInputObject>();
stack.Push(item);
while (stack.Count > 0)
{
var curItem = stack.Pop();
i++;
if (curItem.Children != null)
{
foreach (var child in curItem.Children)
stack.Push(child);
}
}

return i;
}
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.