diff --git a/src/.editorconfig b/src/.editorconfig index 55608d4f..c2d30edf 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -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 \ No newline at end of file +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 ************************************************************* \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs b/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs index a84bc1a3..2bb8e839 100644 --- a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs +++ b/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs @@ -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); } /// @@ -57,7 +57,7 @@ public IInputValueResolver CreateResolver(GraphTypeExpression typeExpression) /// IQueryInputValueResolver. public IInputValueResolver CreateResolver(IInputGraphField field) { - return this.CreateResolver(field.TypeExpression, field); + return this.CreateResolverInternal(field.TypeExpression, field); } /// @@ -68,10 +68,10 @@ public IInputValueResolver CreateResolver(IInputGraphField field) /// IQueryInputValueResolver. 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)); @@ -79,11 +79,17 @@ private IInputValueResolver CreateResolver(GraphTypeExpression typeExpression, I 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 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(); + // extract the core resolver for the input type being processed IInputValueResolver coreResolver = null; Type coreType = null; @@ -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 @@ -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 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); } diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs b/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs index 10c13c24..58703e44 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs @@ -155,7 +155,7 @@ private bool CanBeInputField(PropertyInfo propInfo) if (propInfo == null) return false; - if (propInfo.Attributes.SingleAttributeOrDefault() != null) + if (propInfo.HasAttribute()) return false; if (Constants.IgnoredFieldNames.Contains(propInfo.Name)) diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs index 8a5b18c0..e14f2a47 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs @@ -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(); + 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); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs new file mode 100644 index 00000000..0e832bf8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs @@ -0,0 +1,45 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution +{ + using GraphQL.AspNet.Tests.Execution.TestData.GraphSkipSequencingTestData; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class GraphSkipSequencingTests + { + [Test] + public void SkipFieldWithSameNameAsAnotherField_OBJECT() + { + using var restore = new GraphQLGlobalRestorePoint(true); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(AspNet.Schemas.TypeSystem.TypeKind.OBJECT); + }) + .Build(); + } + + [Test] + public void SkipFieldWithSameNameAsAnotherField_INPUTOBJECT() + { + using var restore = new GraphQLGlobalRestorePoint(true); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(AspNet.Schemas.TypeSystem.TypeKind.INPUT_OBJECT); + }) + .Build(); + } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs new file mode 100644 index 00000000..13c707eb --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs @@ -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 Children { get; set; } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObjectController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObjectController.cs new file mode 100644 index 00000000..7663a560 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObjectController.cs @@ -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(); + 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; + } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/GraphSkipSequencingTestData/ClassWithRenamedField.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/GraphSkipSequencingTestData/ClassWithRenamedField.cs new file mode 100644 index 00000000..76e05af3 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/GraphSkipSequencingTestData/ClassWithRenamedField.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.GraphSkipSequencingTestData +{ + using GraphQL.AspNet.Attributes; + + public class ClassWithRenamedField + { + [GraphSkip] + public int RootNamedField { get; set; } + + [GraphField("RootNamedField")] + public string ReNamedField { get; set; } + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy