From 44533255f24de82b3ef9b03562ba39ca1e1f3075 Mon Sep 17 00:00:00 2001 From: Kevin Carroll Date: Sun, 10 Mar 2024 10:32:46 -0700 Subject: [PATCH 1/2] Fixed a bug with early out ignoring of input fields (#150) --- .../InputObjectGraphTypeTemplate.cs | 2 +- .../Execution/GraphSkipSequencingTests.cs | 45 +++++++++++++++++++ .../ClassWithRenamedField.cs | 22 +++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs create mode 100644 src/unit-tests/graphql-aspnet-tests/Execution/TestData/GraphSkipSequencingTestData/ClassWithRenamedField.cs diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs b/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs index 10c13c246..58703e44b 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/GraphSkipSequencingTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs new file mode 100644 index 000000000..0e832bf89 --- /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/GraphSkipSequencingTestData/ClassWithRenamedField.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/GraphSkipSequencingTestData/ClassWithRenamedField.cs new file mode 100644 index 000000000..76e05af30 --- /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; } + } +} From 5cd17260dd1242845fd61bf798cb9f219c95f3e2 Mon Sep 17 00:00:00 2001 From: Kevin Carroll Date: Sun, 10 Mar 2024 10:36:33 -0700 Subject: [PATCH 2/2] Fixed self referencing complex input object lists (#152) --- src/.editorconfig | 6 +-- .../InputValueResolverMethodGenerator.cs | 43 ++++++++++-------- .../Execution/GeneralQueryExecutionTests3.cs | 45 +++++++++++++++++++ .../SelfReferencingInputObject.cs | 20 +++++++++ .../SelfReferencingInputObjectController.cs | 38 ++++++++++++++++ 5 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs create mode 100644 src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObjectController.cs diff --git a/src/.editorconfig b/src/.editorconfig index 55608d4fe..c2d30edfd 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 a84bc1a3b..2bb8e8399 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/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs index 8a5b18c00..e14f2a47c 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/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SelfReferencingInputObject.cs new file mode 100644 index 000000000..13c707eb1 --- /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 000000000..7663a5601 --- /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; + } + } +} 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