Skip to content

Commit

Permalink
[Fusion] Added pre-merge validation rule "RequireInvalidFieldsTypeRul…
Browse files Browse the repository at this point in the history
…e" (#7888)
  • Loading branch information
glen-84 authored Jan 2, 2025
1 parent becfc54 commit fecff49
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// When using the <c>@require</c> directive, the <c>fields</c> argument must always be a string
/// that defines a (potentially nested) selection set of fields from the same type. If the
/// <c>fields</c> 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.
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Require-Invalid-Fields-Type">
/// Specification
/// </seealso>
internal sealed class RequireInvalidFieldsTypeRule : IEventHandler<FieldArgumentEvent>
{
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));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
<data name="LogEntryHelper_RequireDirectiveInFieldsArgument" xml:space="preserve">
<value>The @require directive on argument '{0}' in schema '{1}' references field '{2}', which must not include directive applications.</value>
</data>
<data name="LogEntryHelper_RequireInvalidFieldsType" xml:space="preserve">
<value>The @require directive on argument '{0}' in schema '{1}' must specify a string value for the 'fields' argument.</value>
</data>
<data name="LogEntryHelper_RootMutationUsed" xml:space="preserve">
<value>The root mutation type in schema '{0}' must be named 'Mutation'.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private CompositionResult<SchemaDefinition> MergeSchemaDefinitions(CompositionCo
new ProvidesOnNonCompositeFieldRule(),
new QueryRootTypeInaccessibleRule(),
new RequireDirectiveInFieldsArgumentRule(),
new RequireInvalidFieldsTypeRule(),
new RootMutationUsedRule(),
new RootQueryUsedRule(),
new RootSubscriptionUsedRule()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string[]> ValidExamplesData()
{
return new TheoryData<string[]>
{
// 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<string[], string[]> InvalidExamplesData()
{
return new TheoryData<string[], string[]>
{
// 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."
]
}
};
}
}

0 comments on commit fecff49

Please sign in to comment.