diff --git a/src/CookieCrumble/src/CookieCrumble/CookieCrumble.csproj b/src/CookieCrumble/src/CookieCrumble/CookieCrumble.csproj index e81c6cbf372..5bebb73f26a 100644 --- a/src/CookieCrumble/src/CookieCrumble/CookieCrumble.csproj +++ b/src/CookieCrumble/src/CookieCrumble/CookieCrumble.csproj @@ -5,7 +5,6 @@ enable enable true - false CookieCrumble CookieCrumble diff --git a/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorSetup.cs b/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorSetup.cs index b7b90dfeb17..58685aecda2 100644 --- a/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorSetup.cs +++ b/src/HotChocolate/Core/src/Execution/Configuration/RequestExecutorSetup.cs @@ -5,7 +5,7 @@ namespace HotChocolate.Execution.Configuration; -public class RequestExecutorSetup +public sealed class RequestExecutorSetup { private readonly List _schemaBuilderActions = new(); private readonly List _requestExecutorOptionsActions = new(); diff --git a/src/HotChocolate/Core/src/Execution/Serialization/JsonResultFormatter.cs b/src/HotChocolate/Core/src/Execution/Serialization/JsonResultFormatter.cs index c36cae9f5bc..b31ff9302ce 100644 --- a/src/HotChocolate/Core/src/Execution/Serialization/JsonResultFormatter.cs +++ b/src/HotChocolate/Core/src/Execution/Serialization/JsonResultFormatter.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using HotChocolate.Execution.Processing; @@ -644,6 +645,10 @@ private void WriteIncremental(Utf8JsonWriter writer, IReadOnlyList break; #if NET6_0_OR_GREATER + case JsonDocument doc: + WriteJsonElement(writer, doc.RootElement); + break; + case JsonElement element: WriteJsonElement(writer, element); break; diff --git a/src/HotChocolate/Core/src/Execution/Serialization/RawJsonValue.cs b/src/HotChocolate/Core/src/Execution/Serialization/RawJsonValue.cs index 5062c96761b..564cccb4d08 100644 --- a/src/HotChocolate/Core/src/Execution/Serialization/RawJsonValue.cs +++ b/src/HotChocolate/Core/src/Execution/Serialization/RawJsonValue.cs @@ -9,11 +9,12 @@ namespace HotChocolate.Execution.Serialization; /// and writes it without validation to the JSON response object. /// /// +/// /// The downside of this helper is that we bind it explicitly to JSON. /// If there were alternative query formatter that use different formats we would get /// into trouble with this. -/// -/// This is also the reason for keeping this internal. +/// +/// This is also the reason for keeping this internal. /// internal readonly struct RawJsonValue { diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/DirectiveTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/DirectiveTypeAttribute.cs index c40f914af70..d53d64fb87a 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/DirectiveTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/DirectiveTypeAttribute.cs @@ -45,5 +45,6 @@ public DirectiveTypeAttribute(string name, DirectiveLocation location) } descriptor.Location(Location); + descriptor.Extend().Definition.Arguments.BindingBehavior = BindingBehavior.Implicit; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeAttribute.cs index c87b1499984..15643f40ceb 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeAttribute.cs @@ -42,5 +42,7 @@ public EnumTypeAttribute(string? name = null) { descriptor.Name(Name); } + + descriptor.BindValuesImplicitly(); } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeDescriptorAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeDescriptorAttribute.cs index 573f33943e7..6a37b3b96fb 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeDescriptorAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumTypeDescriptorAttribute.cs @@ -10,8 +10,7 @@ namespace HotChocolate.Types; AttributeTargets.Enum | AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = true)] -public abstract class EnumTypeDescriptorAttribute - : DescriptorAttribute +public abstract class EnumTypeDescriptorAttribute : DescriptorAttribute { protected internal sealed override void TryConfigure( IDescriptorContext context, diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumValueDescriptorAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumValueDescriptorAttribute.cs index 980c5c8cbdc..00e37c070cd 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/EnumValueDescriptorAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/EnumValueDescriptorAttribute.cs @@ -10,8 +10,7 @@ namespace HotChocolate.Types; AttributeTargets.Field, Inherited = true, AllowMultiple = true)] -public abstract class EnumValueDescriptorAttribute - : DescriptorAttribute +public abstract class EnumValueDescriptorAttribute : DescriptorAttribute { protected internal sealed override void TryConfigure( IDescriptorContext context, diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/ExtendObjectTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/ExtendObjectTypeAttribute.cs index 087065b09f5..72003e3fc07 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/ExtendObjectTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/ExtendObjectTypeAttribute.cs @@ -102,9 +102,12 @@ public ExtendObjectTypeAttribute(Type extendsType) descriptor.Name(Name); } + var definition = descriptor.Extend().Definition; + definition.Fields.BindingBehavior = BindingBehavior.Implicit; + if (IncludeStaticMembers) { - descriptor.Extend().Definition.FieldBindingFlags = Instance | Static; + definition.FieldBindingFlags = Instance | Static; } if (IgnoreFields is not null) diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/InputObjectTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/InputObjectTypeAttribute.cs index 2c27cc225a0..9d5bab71162 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/InputObjectTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/InputObjectTypeAttribute.cs @@ -41,5 +41,7 @@ public InputObjectTypeAttribute(string? name = null) { descriptor.Name(Name); } + + descriptor.Extend().Definition.Fields.BindingBehavior = BindingBehavior.Implicit; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/InterfaceTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/InterfaceTypeAttribute.cs index 52642532629..b8706ce6998 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/InterfaceTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/InterfaceTypeAttribute.cs @@ -41,5 +41,7 @@ public InterfaceTypeAttribute(string? name = null) { descriptor.Name(Name); } + + descriptor.Extend().Definition.Fields.BindingBehavior = BindingBehavior.Implicit; } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/MutationTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/MutationTypeAttribute.cs index 1a808a7a06a..c874dddb03f 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/MutationTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/MutationTypeAttribute.cs @@ -16,6 +16,11 @@ public sealed class MutationTypeAttribute /// public bool Inherited { get; set; } + /// + /// Defines that static members are included. + /// + public bool IncludeStaticMembers { get; set; } + TypeKind ITypeAttribute.Kind => TypeKind.Object; bool ITypeAttribute.IsTypeExtension => true; @@ -24,5 +29,15 @@ public sealed class MutationTypeAttribute IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) - => descriptor.Name(OperationTypeNames.Mutation); + { + descriptor.Name(OperationTypeNames.Mutation); + + var definition = descriptor.Extend().Definition; + definition.Fields.BindingBehavior = BindingBehavior.Implicit; + + if (IncludeStaticMembers) + { + definition.FieldBindingFlags = FieldBindingFlags.Instance | FieldBindingFlags.Static; + } + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/ObjectTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/ObjectTypeAttribute.cs index b7443c348cd..a9a6ad3ad09 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/ObjectTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/ObjectTypeAttribute.cs @@ -53,9 +53,12 @@ public ObjectTypeAttribute(string? name = null) descriptor.Name(Name); } + var definition = descriptor.Extend().Definition; + definition.Fields.BindingBehavior = BindingBehavior.Implicit; + if (IncludeStaticMembers) { - descriptor.Extend().Definition.FieldBindingFlags = Instance | Static; + definition.FieldBindingFlags = Instance | Static; } } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/QueryTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/QueryTypeAttribute.cs index ba850d793a6..5df7d435dce 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/QueryTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/QueryTypeAttribute.cs @@ -16,6 +16,11 @@ public sealed class QueryTypeAttribute /// public bool Inherited { get; set; } + /// + /// Defines that static members are included. + /// + public bool IncludeStaticMembers { get; set; } + TypeKind ITypeAttribute.Kind => TypeKind.Object; bool ITypeAttribute.IsTypeExtension => true; @@ -24,5 +29,15 @@ public sealed class QueryTypeAttribute IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) - => descriptor.Name(OperationTypeNames.Query); + { + descriptor.Name(OperationTypeNames.Query); + + var definition = descriptor.Extend().Definition; + definition.Fields.BindingBehavior = BindingBehavior.Implicit; + + if (IncludeStaticMembers) + { + definition.FieldBindingFlags = FieldBindingFlags.Instance | FieldBindingFlags.Static; + } + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/StreamResultAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/StreamResultAttribute.cs index 4c83d31e22c..06be527a764 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/StreamResultAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/StreamResultAttribute.cs @@ -16,7 +16,5 @@ public sealed class StreamResultAttribute : ObjectFieldDescriptorAttribute IDescriptorContext context, IObjectFieldDescriptor descriptor, MemberInfo member) - { - descriptor.StreamResult(); - } + => descriptor.StreamResult(); } diff --git a/src/HotChocolate/Core/src/Types/Types/Attributes/SubscriptionTypeAttribute.cs b/src/HotChocolate/Core/src/Types/Types/Attributes/SubscriptionTypeAttribute.cs index 8282e879217..4f910fa5532 100644 --- a/src/HotChocolate/Core/src/Types/Types/Attributes/SubscriptionTypeAttribute.cs +++ b/src/HotChocolate/Core/src/Types/Types/Attributes/SubscriptionTypeAttribute.cs @@ -16,6 +16,11 @@ public sealed class SubscriptionTypeAttribute /// public bool Inherited { get; set; } + /// + /// Defines that static members are included. + /// + public bool IncludeStaticMembers { get; set; } + TypeKind ITypeAttribute.Kind => TypeKind.Object; bool ITypeAttribute.IsTypeExtension => true; @@ -24,5 +29,15 @@ public sealed class SubscriptionTypeAttribute IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) - => descriptor.Name(OperationTypeNames.Subscription); + { + descriptor.Name(OperationTypeNames.Subscription); + + var definition = descriptor.Extend().Definition; + definition.Fields.BindingBehavior = BindingBehavior.Implicit; + + if (IncludeStaticMembers) + { + definition.FieldBindingFlags = FieldBindingFlags.Instance | FieldBindingFlags.Static; + } + } } diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultNamingConventions.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultNamingConventions.cs index 989cfff15fb..91bf2735bda 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultNamingConventions.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DefaultNamingConventions.cs @@ -10,7 +10,7 @@ namespace HotChocolate.Types.Descriptors; public class DefaultNamingConventions : Convention - , INamingConventions + , INamingConventions { private const string _inputPostfix = "Input"; private const string _inputTypePostfix = "InputType"; diff --git a/src/HotChocolate/Core/src/Types/Types/InputObjectType~1.cs b/src/HotChocolate/Core/src/Types/Types/InputObjectType~1.cs index 8d215e24e2d..d8eb187cf87 100644 --- a/src/HotChocolate/Core/src/Types/Types/InputObjectType~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/InputObjectType~1.cs @@ -2,6 +2,7 @@ using HotChocolate.Configuration; using HotChocolate.Types.Descriptors; using HotChocolate.Types.Descriptors.Definitions; +using HotChocolate.Types.Helpers; #nullable enable @@ -29,14 +30,6 @@ public InputObjectType(Action> configure) _configure!(descriptor); _configure = null; - // if the object type is inferred from a runtime time we will bind fields implicitly - // even if the schema building option are set to bind explicitly by default; - // otherwise we would end up with types that have no fields. - if (context.IsInferred) - { - descriptor.BindFieldsImplicitly(); - } - return descriptor.CreateDefinition(); } diff --git a/src/HotChocolate/Core/src/Types/Types/InterfaceType~1.cs b/src/HotChocolate/Core/src/Types/Types/InterfaceType~1.cs index 67f7b06eac1..53e98eab093 100644 --- a/src/HotChocolate/Core/src/Types/Types/InterfaceType~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/InterfaceType~1.cs @@ -24,14 +24,6 @@ protected override InterfaceTypeDefinition CreateDefinition(ITypeDiscoveryContex _configure!(descriptor); _configure = null; - // if the object type is inferred from a runtime time we will bind fields implicitly - // even if the schema building option are set to bind explicitly by default; - // otherwise we would end up with types that have no fields. - if (context.IsInferred) - { - descriptor.BindFieldsImplicitly(); - } - return descriptor.CreateDefinition(); } diff --git a/src/HotChocolate/Core/src/Types/Types/ObjectTypeExtension~1.cs b/src/HotChocolate/Core/src/Types/Types/ObjectTypeExtension~1.cs index ef91b8a29d6..e296fb19fc3 100644 --- a/src/HotChocolate/Core/src/Types/Types/ObjectTypeExtension~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/ObjectTypeExtension~1.cs @@ -46,14 +46,6 @@ public ObjectTypeExtension(Action> configure) _configure!(descriptor); _configure = null; - // if the object type is inferred from a runtime time we will bind fields implicitly - // even if the schema building option are set to bind explicitly by default; - // otherwise we would end up with types that have no fields. - if (context.IsInferred) - { - descriptor.BindFieldsImplicitly(); - } - return descriptor.CreateDefinition(); } diff --git a/src/HotChocolate/Core/src/Types/Types/ObjectType~1.cs b/src/HotChocolate/Core/src/Types/Types/ObjectType~1.cs index b1a31ff971a..442204f3d42 100644 --- a/src/HotChocolate/Core/src/Types/Types/ObjectType~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/ObjectType~1.cs @@ -2,24 +2,29 @@ using HotChocolate.Configuration; using HotChocolate.Types.Descriptors; using HotChocolate.Types.Descriptors.Definitions; +using HotChocolate.Types.Helpers; #nullable enable namespace HotChocolate.Types; /// +/// /// GraphQL operations are hierarchical and composed, describing a tree of information. /// While Scalar types describe the leaf values of these hierarchical operations, /// Objects describe the intermediate levels. -/// +/// +/// /// GraphQL Objects represent a list of named fields, each of which yield a value of a /// specific type. Object values should be serialized as ordered maps, where the selected /// field names (or aliases) are the keys and the result of evaluating the field is the value, /// ordered by the order in which they appear in the selection set. -/// +/// +/// /// All fields defined within an Object type must not have a name which begins /// with "__" (two underscores), as this is used exclusively by /// GraphQL’s introspection system. +/// /// public class ObjectType : ObjectType { @@ -45,14 +50,6 @@ public ObjectType(Action> configure) _configure!(descriptor); _configure = null; - // if the object type is inferred from a runtime time we will bind fields implicitly - // even if the schema building option are set to bind explicitly by default; - // otherwise we would end up with types that have no fields. - if (context.IsInferred) - { - descriptor.BindFieldsImplicitly(); - } - return descriptor.CreateDefinition(); } diff --git a/src/HotChocolate/Core/src/Types/Types/UnionType~1.cs b/src/HotChocolate/Core/src/Types/Types/UnionType~1.cs index 27dc9f52619..0c852953a22 100644 --- a/src/HotChocolate/Core/src/Types/Types/UnionType~1.cs +++ b/src/HotChocolate/Core/src/Types/Types/UnionType~1.cs @@ -8,8 +8,7 @@ namespace HotChocolate.Types; -public class UnionType - : UnionType +public class UnionType : UnionType { private Action? _configure; diff --git a/src/HotChocolate/Core/test/Types.Tests/Types/BindingBehaviorTests.cs b/src/HotChocolate/Core/test/Types.Tests/Types/BindingBehaviorTests.cs new file mode 100644 index 00000000000..b39bfd6a1a3 --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/Types/BindingBehaviorTests.cs @@ -0,0 +1,155 @@ +using System.Threading.Tasks; +using CookieCrumble; +using HotChocolate.Execution; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate.Types; + +public class BindingBehaviorTests +{ + [Fact] + public async Task BindingBehavior_Explicit_All_Attributes() + { + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddType() + .ModifyOptions(o => o.DefaultBindingBehavior = BindingBehavior.Explicit) + .BuildSchemaAsync(); + + schema.MatchInlineSnapshot( + """ + schema { + query: Query + } + + type Book1 { + title: String + category: BookCategory1! + } + + type Query { + books: Book1 + } + + enum BookCategory1 { + A + B + C + } + """); + } + + [QueryType] + public class Query1 + { + public Book1 GetBooks() => new("Abc", BookCategory1.B); + } + + [ObjectType] + public record Book1(string Title, BookCategory1 Category); + + [EnumType] + public enum BookCategory1 + { + A, + B, + C + } + + [Fact] + public async Task BindingBehavior_Explicit_Enum_Missing_Attribute() + { + async Task Error() => + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddType() + .ModifyOptions(o => o.DefaultBindingBehavior = BindingBehavior.Explicit) + .BuildSchemaAsync(); + + var error = await Assert.ThrowsAsync(Error); + + error.Message.MatchInlineSnapshot( + """ + For more details look at the `Errors` property. + + 1. The enum type `BookCategory2` has no values. (HotChocolate.Types.EnumType) + + """); + } + + [QueryType] + public class Query2 + { + public Book2 GetBooks() => new("Abc", BookCategory2.B); + } + + [ObjectType] + public record Book2(string Title, BookCategory2 Category); + + public enum BookCategory2 + { + A, + B, + C + } + + [Fact] + public async Task BindingBehavior_Explicit_Enum_Bound_With_Fluent() + { + var schema = + await new ServiceCollection() + .AddGraphQL() + .AddQueryType() + .AddType() + .AddType() + .ModifyOptions(o => o.DefaultBindingBehavior = BindingBehavior.Explicit) + .BuildSchemaAsync(); + + schema.MatchInlineSnapshot( + """ + schema { + query: Query + } + + type Book3 { + title: String + category: BookCategory3! + } + + type Query { + books: Book3 + } + + enum BookCategory3 { + A + } + """); + } + + [QueryType] + public class Query3 + { + public Book3 GetBooks() => new("Abc", BookCategory3.B); + } + + [ObjectType] + public record Book3(string Title, BookCategory3 Category); + + public enum BookCategory3 + { + A, + B, + C + } + + public class BookCategory3Type : EnumType + { + protected override void Configure(IEnumTypeDescriptor descriptor) + { + descriptor.Value(BookCategory3.A); + } + } +}