From 7fc4f078411a1e6e599c74c713c2dff514723bee Mon Sep 17 00:00:00 2001 From: Michael Staib Date: Thu, 12 Mar 2026 22:20:40 +0000 Subject: [PATCH] Fix state attribute handling in analyzers --- .../Helpers/CompilationExtensions.cs | 6 +- .../Helpers/SymbolExtensions.cs | 219 ++++++++++++------ .../Types.Analyzers.Tests/ResolverTests.cs | 66 ++++++ ...imaryConstructorBaseKey_MatchesSnapshot.md | 107 +++++++++ ...bute_BaseConstructorKey_MatchesSnapshot.md | 107 +++++++++ .../Data.PostgreSQL.Tests/IntegrationTests.cs | 67 ++++++ .../IntegrationTests.CreateSchema.graphql | 1 + 7 files changed, 503 insertions(+), 70 deletions(-) create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot.md create mode 100644 src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot.md diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CompilationExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CompilationExtensions.cs index 7d9201448b9..57d4bb3e2a3 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CompilationExtensions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/CompilationExtensions.cs @@ -371,21 +371,21 @@ public static ResolverParameterKind GetParameterKind( return ResolverParameterKind.HttpResponse; } - if (parameter.IsGlobalState(out key)) + if (parameter.IsGlobalState(compilation, out key)) { return parameter.IsSetState() ? ResolverParameterKind.SetGlobalState : ResolverParameterKind.GetGlobalState; } - if (parameter.IsScopedState(out key)) + if (parameter.IsScopedState(compilation, out key)) { return parameter.IsSetState() ? ResolverParameterKind.SetScopedState : ResolverParameterKind.GetScopedState; } - if (parameter.IsLocalState(out key)) + if (parameter.IsLocalState(compilation, out key)) { return parameter.IsSetState() ? ResolverParameterKind.SetLocalState diff --git a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs index 8adfd0c8594..0cc8ac01132 100644 --- a/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs +++ b/src/HotChocolate/Core/src/Types.Analyzers/Helpers/SymbolExtensions.cs @@ -789,32 +789,29 @@ public static bool IsSelection(this IParameterSymbol parameter) public static bool IsGlobalState( this IParameterSymbol parameter, + Compilation compilation, [NotNullWhen(true)] out string? key) - { - key = null; + => parameter.TryGetStateKey("HotChocolate.GlobalStateAttribute", compilation, out key); + + public static bool IsScopedState( + this IParameterSymbol parameter, + Compilation compilation, + [NotNullWhen(true)] out string? key) + => parameter.TryGetStateKey("HotChocolate.ScopedStateAttribute", compilation, out key); + public static bool IsLocalState( + this IParameterSymbol parameter, + Compilation compilation, + [NotNullWhen(true)] out string? key) + => parameter.TryGetStateKey("HotChocolate.LocalStateAttribute", compilation, out key); + + public static bool IsEventMessage( + this IParameterSymbol parameter) + { foreach (var attributeData in parameter.GetAttributes()) { - if (IsOrInheritsFrom(attributeData.AttributeClass, "HotChocolate.GlobalStateAttribute")) + if (attributeData.AttributeClass?.ToDisplayString() == WellKnownAttributes.EventMessageAttribute) { - if (attributeData.ConstructorArguments.Length == 1 - && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive - && attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } - - foreach (var namedArg in attributeData.NamedArguments) - { - if (namedArg is { Key: "Key", Value.Value: string namedKeyValue }) - { - key = namedKeyValue; - return true; - } - } - - key = parameter.Name; return true; } } @@ -822,15 +819,15 @@ public static bool IsGlobalState( return false; } - public static bool IsScopedState( + public static bool IsService( this IParameterSymbol parameter, - [NotNullWhen(true)] out string? key) + out string? key) { key = null; foreach (var attributeData in parameter.GetAttributes()) { - if (IsOrInheritsFrom(attributeData.AttributeClass, "HotChocolate.ScopedStateAttribute")) + if (attributeData.AttributeClass?.ToDisplayString() == WellKnownAttributes.ServiceAttribute) { if (attributeData.ConstructorArguments.Length == 1 && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive @@ -849,7 +846,7 @@ public static bool IsScopedState( } } - key = parameter.Name; + key = null; return true; } } @@ -857,48 +854,53 @@ public static bool IsScopedState( return false; } - public static bool IsLocalState( + private static bool TryGetStateKey( this IParameterSymbol parameter, + string stateAttributeType, + Compilation compilation, [NotNullWhen(true)] out string? key) { key = null; foreach (var attributeData in parameter.GetAttributes()) { - if (IsOrInheritsFrom(attributeData.AttributeClass, "HotChocolate.LocalStateAttribute")) + if (!IsOrInheritsFrom(attributeData.AttributeClass, stateAttributeType)) { - if (attributeData.ConstructorArguments.Length == 1 - && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive - && attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } - - foreach (var namedArg in attributeData.NamedArguments) - { - if (namedArg is { Key: "Key", Value.Value: string namedKeyValue }) - { - key = namedKeyValue; - return true; - } - } + continue; + } - key = parameter.Name; + if (TryGetStateKeyFromAttributeUsage(attributeData, out key) + || TryGetStateKeyFromAttributeDeclaration(attributeData, compilation, out key)) + { return true; } + + key = parameter.Name; + return true; } return false; } - public static bool IsEventMessage( - this IParameterSymbol parameter) + private static bool TryGetStateKeyFromAttributeUsage( + AttributeData attributeData, + [NotNullWhen(true)] out string? key) { - foreach (var attributeData in parameter.GetAttributes()) + key = null; + + if (attributeData.ConstructorArguments.Length == 1 + && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive + && attributeData.ConstructorArguments[0].Value is string keyValue) { - if (attributeData.AttributeClass?.ToDisplayString() == WellKnownAttributes.EventMessageAttribute) + key = keyValue; + return true; + } + + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg is { Key: "Key", Value.Value: string namedKeyValue }) { + key = namedKeyValue; return true; } } @@ -906,34 +908,92 @@ public static bool IsEventMessage( return false; } - public static bool IsService( - this IParameterSymbol parameter, - out string? key) + private static bool TryGetStateKeyFromAttributeDeclaration( + AttributeData attributeData, + Compilation compilation, + [NotNullWhen(true)] out string? key) { key = null; - foreach (var attributeData in parameter.GetAttributes()) + var constructor = attributeData.AttributeConstructor; + if (constructor is not null + && TryGetStateKeyFromConstructorDeclaration(constructor, compilation, out key)) { - if (attributeData.AttributeClass?.ToDisplayString() == WellKnownAttributes.ServiceAttribute) + return true; + } + + var attributeType = attributeData.AttributeClass; + if (attributeType is null) + { + return false; + } + + foreach (var syntaxReference in attributeType.DeclaringSyntaxReferences) + { + if (syntaxReference.GetSyntax() is TypeDeclarationSyntax declaration + && TryGetStateKeyFromPrimaryConstructorBaseCall(declaration, compilation, out key)) { - if (attributeData.ConstructorArguments.Length == 1 - && attributeData.ConstructorArguments[0].Kind == TypedConstantKind.Primitive - && attributeData.ConstructorArguments[0].Value is string keyValue) - { - key = keyValue; - return true; - } + return true; + } + } - foreach (var namedArg in attributeData.NamedArguments) + return false; + } + + private static bool TryGetStateKeyFromConstructorDeclaration( + IMethodSymbol constructor, + Compilation compilation, + [NotNullWhen(true)] out string? key) + { + key = null; + + foreach (var syntaxReference in constructor.DeclaringSyntaxReferences) + { + if (syntaxReference.GetSyntax() is ConstructorDeclarationSyntax declaration + && declaration.Initializer is { - if (namedArg is { Key: "Key", Value.Value: string namedKeyValue }) - { - key = namedKeyValue; - return true; - } + RawKind: (int)SyntaxKind.BaseConstructorInitializer, + ArgumentList: { } argumentList } + && TryGetConstantStringFromArgumentList( + argumentList, + declaration.SyntaxTree, + compilation, + out key)) + { + return true; + } + } - key = null; + return false; + } + + private static bool TryGetStateKeyFromPrimaryConstructorBaseCall( + TypeDeclarationSyntax declaration, + Compilation compilation, + [NotNullWhen(true)] out string? key) + { + key = null; + + if (declaration.BaseList is null) + { + return false; + } + + foreach (var baseType in declaration.BaseList.Types) + { + var argumentList = baseType.ChildNodes().OfType().FirstOrDefault(); + if (argumentList is null) + { + continue; + } + + if (TryGetConstantStringFromArgumentList( + argumentList, + declaration.SyntaxTree, + compilation, + out key)) + { return true; } } @@ -941,6 +1001,31 @@ public static bool IsService( return false; } + private static bool TryGetConstantStringFromArgumentList( + ArgumentListSyntax argumentList, + SyntaxTree syntaxTree, + Compilation compilation, + [NotNullWhen(true)] out string? key) + { + key = null; + + if (argumentList.Arguments.Count != 1) + { + return false; + } + + var model = compilation.GetSemanticModel(syntaxTree); + var constantValue = model.GetConstantValue(argumentList.Arguments[0].Expression); + + if (constantValue is { HasValue: true, Value: string keyValue }) + { + key = keyValue; + return true; + } + + return false; + } + public static bool IsArgument( this IParameterSymbol parameter, out string? key) diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ResolverTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ResolverTests.cs index 5fb71e05f93..e916d01d931 100644 --- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/ResolverTests.cs +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/ResolverTests.cs @@ -71,6 +71,72 @@ internal class Test; """).MatchMarkdownAsync(); } + [Fact] + public async Task GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot() + { + await TestHelper.GetGeneratedSourceSnapshot( + """ + using System; + using HotChocolate; + using HotChocolate.Types; + + namespace TestNamespace; + + [ObjectType] + internal static partial class TestType + { + public static string GetTest([ScopeState] string scope) + { + return scope; + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class ScopeStateAttribute() + : LocalStateAttribute(LookupKey) + { + public const string LookupKey = "ScopeState"; + } + + internal class Test; + """).MatchMarkdownAsync(); + } + + [Fact] + public async Task GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot() + { + await TestHelper.GetGeneratedSourceSnapshot( + """ + using System; + using HotChocolate; + using HotChocolate.Types; + + namespace TestNamespace; + + [ObjectType] + internal static partial class TestType + { + public static string GetTest([ScopeState] string scope) + { + return scope; + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class ScopeStateAttribute : ScopedStateAttribute + { + public const string LookupKey = "ScopeState"; + + public ScopeStateAttribute() + : base(LookupKey) + { + } + } + + internal class Test; + """).MatchMarkdownAsync(); + } + [Fact] public async Task GenerateSource_ResolverWithLocalStateSetStateArgument_MatchesSnapshot() { diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot.md new file mode 100644 index 00000000000..dd9725f0f72 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot.md @@ -0,0 +1,107 @@ +# GenerateSource_ResolverWithLocalStateDerivedAttribute_PrimaryConstructorBaseKey_MatchesSnapshot + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.TestType", + () => global::TestNamespace.TestType.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## TestType.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + internal static partial class TestType + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.TestType); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("Test", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string"))); + configuration.ResultType = typeof(string); + + configuration.SetSourceGeneratorFlags(); + + configuration.Resolvers = context.Resolvers.GetTest(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates GetTest() + { + return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetTest); + } + + private global::System.Object? GetTest(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = context.GetLocalStateOrDefault("ScopeState"); + var result = global::TestNamespace.TestType.GetTest(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot.md new file mode 100644 index 00000000000..4ca587a77ed --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/ResolverTests.GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot.md @@ -0,0 +1,107 @@ +# GenerateSource_ResolverWithScopedStateDerivedAttribute_BaseConstructorKey_MatchesSnapshot + +## HotChocolateTypeModule.735550c.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class TestsTypesRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddTestsTypes(this IRequestExecutorBuilder builder) + { + builder.ConfigureDescriptorContext(ctx => ctx.TypeConfiguration.TryAdd( + "Tests::TestNamespace.TestType", + () => global::TestNamespace.TestType.Initialize)); + builder.AddType>(); + return builder; + } + } +} + +``` + +## TestType.WaAdMHmlGJHjtEI4nqY7WA.hc.g.cs + +```csharp +// + +#nullable enable +#pragma warning disable + +using System; +using System.Runtime.CompilerServices; +using HotChocolate; +using HotChocolate.Types; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Internal; + +namespace TestNamespace +{ + internal static partial class TestType + { + internal static void Initialize(global::HotChocolate.Types.IObjectTypeDescriptor descriptor) + { + var extension = descriptor.Extend(); + var configuration = extension.Configuration; + var thisType = typeof(global::TestNamespace.TestType); + var bindingResolver = extension.Context.ParameterBindingResolver; + var resolvers = new __Resolvers(); + + var naming = descriptor.Extend().Context.Naming; + + descriptor + .Field(naming.GetMemberName("Test", global::HotChocolate.Types.MemberKind.ObjectField)) + .ExtendWith(static (field, context) => + { + var configuration = field.Configuration; + var typeInspector = field.Context.TypeInspector; + var bindingResolver = field.Context.ParameterBindingResolver; + var naming = field.Context.Naming; + + configuration.Type = global::HotChocolate.Types.Descriptors.TypeReference.Create( + typeInspector.GetTypeRef(typeof(string), HotChocolate.Types.TypeContext.Output), + new global::HotChocolate.Language.NonNullTypeNode(new global::HotChocolate.Language.NamedTypeNode("string"))); + configuration.ResultType = typeof(string); + + configuration.SetSourceGeneratorFlags(); + + configuration.Resolvers = context.Resolvers.GetTest(); + }, + (Resolvers: resolvers, ThisType: thisType)); + + Configure(descriptor); + } + + static partial void Configure(global::HotChocolate.Types.IObjectTypeDescriptor descriptor); + + private sealed class __Resolvers + { + public HotChocolate.Resolvers.FieldResolverDelegates GetTest() + { + return new global::HotChocolate.Resolvers.FieldResolverDelegates(pureResolver: GetTest); + } + + private global::System.Object? GetTest(global::HotChocolate.Resolvers.IResolverContext context) + { + var args0 = context.GetScopedStateOrDefault("ScopeState"); + var result = global::TestNamespace.TestType.GetTest(args0); + return result; + } + } + } +} + + +``` diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs index d790b61d9c0..618ef5b2cff 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/IntegrationTests.cs @@ -4,11 +4,16 @@ using HotChocolate.Data.Models; using HotChocolate.Data.Services; using HotChocolate.Execution; +using HotChocolate.Resolvers; +using HotChocolate.Types; +using HotChocolate.Types.Descriptors; using HotChocolate.Types.Relay; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Squadron; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; namespace HotChocolate.Data; @@ -645,6 +650,28 @@ public async Task Generated_BrandKey_NodeIdValueSerializer_RoundTrip() Assert.Equal(original, (BrandKey)parsed.InternalId); } + [Fact] + public async Task Query_ScopeState_With_Derived_ScopedState_Attribute() + { + // act + var result = await ExecuteAsync( + """ + { + scopeState + } + """); + + // assert + result.MatchInlineSnapshot( + """ + { + "data": { + "scopeState": "Hello World" + } + } + """); + } + private static ServiceProvider CreateServer(string connectionString) { var services = new ServiceCollection(); @@ -841,3 +868,43 @@ public bool TryAdd(PromiseCacheKey key, Promise promise) [GeneratedRegex(@"'(?\d+)'", RegexOptions.CultureInvariant)] private static partial Regex QuotedNumericIdRegex(); } + +[QueryType] +public static partial class ScopeStateQuery +{ + [UseScopeStateMiddleware] + public static string ScopeState([ScopeState] string scope) + => scope; +} + +[AttributeUsage(AttributeTargets.Parameter)] +public sealed class ScopeStateAttribute() + : ScopedStateAttribute(LookupKey) +{ + public const string LookupKey = "ScopeState"; +} + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] +public sealed class UseScopeStateMiddlewareAttribute : ObjectFieldDescriptorAttribute +{ + public UseScopeStateMiddlewareAttribute([CallerLineNumber] int order = 0) + => Order = order; + + protected override void OnConfigure( + IDescriptorContext context, + IObjectFieldDescriptor descriptor, + MemberInfo? member) => + descriptor.Use(); + + private sealed class ScopeStateMiddleware(FieldDelegate next) + { + public async Task InvokeAsync(IMiddlewareContext context) + { + context.SetScopedState(ScopeStateAttribute.LookupKey, "Hello World"); + + await next(context); + + context.RemoveScopedState(ScopeStateAttribute.LookupKey); + } + } +} diff --git a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql index 8fab1f72f31..8c1779f398b 100644 --- a/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql +++ b/src/HotChocolate/Data/test/Data.PostgreSQL.Tests/__snapshots__/IntegrationTests.CreateSchema.graphql @@ -169,6 +169,7 @@ type Query { "Lookup nodes by a list of IDs." nodes("The list of node IDs." ids: [ID!]!): [Node]! @shareable @cost(weight: "10") hiddenNameProductTypes("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: ProductTypeFilterInput @cost(weight: "10") order: [ProductTypeSortInput!] @cost(weight: "10")): ProductTypeConnection! @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false) @cost(weight: "10") + scopeState: String! brands("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String where: BrandFilterInput @cost(weight: "10")): BrandConnection! @listSize(assumedSize: 50, slicingArguments: ["first", "last"], slicingArgumentDefaultValue: 10, sizedFields: ["edges", "nodes"], requireOneSlicingArgument: false) @cost(weight: "10") brandById(id: ID!): Brand @lookup @cost(weight: "10") brandByIdWithDL(id: ID!): Brand @lookup @cost(weight: "10")