diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs
index 07244979d22..971bc8e86c9 100644
--- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs
@@ -19,6 +19,7 @@ public static class LogEntryCodes
public const string ProvidesOnNonCompositeField = "PROVIDES_ON_NON_COMPOSITE_FIELD";
public const string QueryRootTypeInaccessible = "QUERY_ROOT_TYPE_INACCESSIBLE";
public const string RequireDirectiveInFieldsArg = "REQUIRE_DIRECTIVE_IN_FIELDS_ARG";
+ public const string RequireInvalidFieldsType = "REQUIRE_INVALID_FIELDS_TYPE";
public const string RootMutationUsed = "ROOT_MUTATION_USED";
public const string RootQueryUsed = "ROOT_QUERY_USED";
public const string RootSubscriptionUsed = "ROOT_SUBSCRIPTION_USED";
diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs
index 6799f125eec..b7932cdccae 100644
--- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs
@@ -401,6 +401,24 @@ public static LogEntry RequireDirectiveInFieldsArgument(
schema);
}
+ public static LogEntry RequireInvalidFieldsType(
+ Directive requireDirective,
+ string argumentName,
+ string fieldName,
+ string typeName,
+ SchemaDefinition schema)
+ {
+ var coordinate = new SchemaCoordinate(typeName, fieldName, argumentName);
+
+ return new LogEntry(
+ string.Format(LogEntryHelper_RequireInvalidFieldsType, coordinate, schema.Name),
+ LogEntryCodes.RequireInvalidFieldsType,
+ LogSeverity.Error,
+ coordinate,
+ requireDirective,
+ schema);
+ }
+
public static LogEntry RootMutationUsed(SchemaDefinition schema)
{
return new LogEntry(
diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/RequireInvalidFieldsTypeRule.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/RequireInvalidFieldsTypeRule.cs
new file mode 100644
index 00000000000..a4ddeec347e
--- /dev/null
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/PreMergeValidation/Rules/RequireInvalidFieldsTypeRule.cs
@@ -0,0 +1,40 @@
+using HotChocolate.Fusion.Events;
+using HotChocolate.Language;
+using static HotChocolate.Fusion.Logging.LogEntryHelper;
+using static HotChocolate.Fusion.WellKnownArgumentNames;
+using static HotChocolate.Fusion.WellKnownDirectiveNames;
+
+namespace HotChocolate.Fusion.PreMergeValidation.Rules;
+
+///
+/// When using the @require directive, the fields argument must always be a string
+/// that defines a (potentially nested) selection set of fields from the same type. If the
+/// fields argument is provided as a type other than a string (such as an integer, boolean,
+/// or enum), the directive usage is invalid and will cause schema composition to fail.
+///
+///
+/// Specification
+///
+internal sealed class RequireInvalidFieldsTypeRule : IEventHandler
+{
+ public void Handle(FieldArgumentEvent @event, CompositionContext context)
+ {
+ var (argument, field, type, schema) = @event;
+
+ var requireDirective = argument.Directives.FirstOrDefault(Require);
+
+ if (
+ requireDirective is not null
+ && requireDirective.Arguments.TryGetValue(Fields, out var fields)
+ && fields is not StringValueNode)
+ {
+ context.Log.Write(
+ RequireInvalidFieldsType(
+ requireDirective,
+ argument.Name,
+ field.Name,
+ type.Name,
+ schema));
+ }
+ }
+}
diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs
index 835085fbcc1..58845c208a3 100644
--- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs
@@ -257,6 +257,15 @@ internal static string LogEntryHelper_RequireDirectiveInFieldsArgument {
}
}
+ ///
+ /// Looks up a localized string similar to The @require directive on argument '{0}' in schema '{1}' must specify a string value for the 'fields' argument..
+ ///
+ internal static string LogEntryHelper_RequireInvalidFieldsType {
+ get {
+ return ResourceManager.GetString("LogEntryHelper_RequireInvalidFieldsType", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The root mutation type in schema '{0}' must be named 'Mutation'..
///
diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx
index 42920b9a109..99a34c4181f 100644
--- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx
@@ -84,6 +84,9 @@
The @require directive on argument '{0}' in schema '{1}' references field '{2}', which must not include directive applications.
+
+ The @require directive on argument '{0}' in schema '{1}' must specify a string value for the 'fields' argument.
+
The root mutation type in schema '{0}' must be named 'Mutation'.
diff --git a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs
index 0dbd89781d2..5011415d397 100644
--- a/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs
+++ b/src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaMerger.cs
@@ -63,6 +63,7 @@ private CompositionResult MergeSchemaDefinitions(CompositionCo
new ProvidesOnNonCompositeFieldRule(),
new QueryRootTypeInaccessibleRule(),
new RequireDirectiveInFieldsArgumentRule(),
+ new RequireInvalidFieldsTypeRule(),
new RootMutationUsedRule(),
new RootQueryUsedRule(),
new RootSubscriptionUsedRule()
diff --git a/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/RequireInvalidFieldsTypeRuleTests.cs b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/RequireInvalidFieldsTypeRuleTests.cs
new file mode 100644
index 00000000000..ef1bcf71970
--- /dev/null
+++ b/src/HotChocolate/Fusion-vnext/test/Fusion.Composition.Tests/PreMergeValidation/Rules/RequireInvalidFieldsTypeRuleTests.cs
@@ -0,0 +1,95 @@
+using HotChocolate.Fusion.Logging;
+using HotChocolate.Fusion.PreMergeValidation;
+using HotChocolate.Fusion.PreMergeValidation.Rules;
+
+namespace HotChocolate.Composition.PreMergeValidation.Rules;
+
+public sealed class RequireInvalidFieldsTypeRuleTests : CompositionTestBase
+{
+ private readonly PreMergeValidator _preMergeValidator =
+ new([new RequireInvalidFieldsTypeRule()]);
+
+ [Theory]
+ [MemberData(nameof(ValidExamplesData))]
+ public void Examples_Valid(string[] sdl)
+ {
+ // arrange
+ var context = CreateCompositionContext(sdl);
+
+ // act
+ var result = _preMergeValidator.Validate(context);
+
+ // assert
+ Assert.True(result.IsSuccess);
+ Assert.True(context.Log.IsEmpty);
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidExamplesData))]
+ public void Examples_Invalid(string[] sdl, string[] errorMessages)
+ {
+ // arrange
+ var context = CreateCompositionContext(sdl);
+
+ // act
+ var result = _preMergeValidator.Validate(context);
+
+ // assert
+ Assert.True(result.IsFailure);
+ Assert.Equal(errorMessages, context.Log.Select(e => e.Message).ToArray());
+ Assert.True(context.Log.All(e => e.Code == "REQUIRE_INVALID_FIELDS_TYPE"));
+ Assert.True(context.Log.All(e => e.Severity == LogSeverity.Error));
+ }
+
+ public static TheoryData ValidExamplesData()
+ {
+ return new TheoryData
+ {
+ // In the following example, the @require directive’s "fields" argument is a valid
+ // string and satisfies the rule.
+ {
+ [
+ """
+ type User @key(fields: "id") {
+ id: ID!
+ profile(name: String! @require(fields: "name")): Profile
+ }
+
+ type Profile {
+ id: ID!
+ name: String
+ }
+ """
+ ]
+ }
+ };
+ }
+
+ public static TheoryData InvalidExamplesData()
+ {
+ return new TheoryData
+ {
+ // Since "fields" is set to 123 (an integer) instead of a string, this violates the rule
+ // and triggers a REQUIRE_INVALID_FIELDS_TYPE error.
+ {
+ [
+ """
+ type User @key(fields: "id") {
+ id: ID!
+ profile(name: String! @require(fields: 123)): Profile
+ }
+
+ type Profile {
+ id: ID!
+ name: String
+ }
+ """
+ ],
+ [
+ "The @require directive on argument 'User.profile(name:)' in schema 'A' must " +
+ "specify a string value for the 'fields' argument."
+ ]
+ }
+ };
+ }
+}