Skip to content

Commit

Permalink
[Fusion] Added pre-merge validation rule "RequireInvalidSyntaxRule" (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
glen-84 authored Jan 3, 2025
1 parent a8c9ac6 commit 7ecc977
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static class LogEntryCodes
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 RequireInvalidSyntax = "REQUIRE_INVALID_SYNTAX";
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 @@ -483,6 +483,24 @@ public static LogEntry RequireInvalidFieldsType(
schema);
}

public static LogEntry RequireInvalidSyntax(
Directive requireDirective,
string argumentName,
string fieldName,
string typeName,
SchemaDefinition schema)
{
var coordinate = new SchemaCoordinate(typeName, fieldName, argumentName);

return new LogEntry(
string.Format(LogEntryHelper_RequireInvalidSyntax, coordinate, schema.Name),
LogEntryCodes.RequireInvalidSyntax,
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
Expand Up @@ -93,6 +93,13 @@ internal record RequireFieldNodeEvent(
ComplexTypeDefinition Type,
SchemaDefinition Schema) : IEvent;

internal record RequireFieldsInvalidSyntaxEvent(
Directive RequireDirective,
InputFieldDefinition Argument,
OutputFieldDefinition Field,
ComplexTypeDefinition Type,
SchemaDefinition Schema) : IEvent;

internal record SchemaEvent(SchemaDefinition Schema) : IEvent;

internal record TypeEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,14 @@ private void PublishRequireEvents(
}
catch (SyntaxException)
{
// Ignore.
PublishEvent(
new RequireFieldsInvalidSyntaxEvent(
requireDirective,
argument,
field,
type,
schema),
context);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using HotChocolate.Fusion.Events;
using static HotChocolate.Fusion.Logging.LogEntryHelper;

namespace HotChocolate.Fusion.PreMergeValidation.Rules;

/// <summary>
/// The <c>@require</c> directive’s <c>fields</c> argument must be syntactically valid GraphQL. If
/// the selection map string is malformed (e.g., missing closing braces, unbalanced quotes, invalid
/// tokens), then the schema cannot be composed correctly. In such cases, the error
/// <c>REQUIRE_INVALID_SYNTAX</c> is raised.
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Require-Invalid-Syntax">
/// Specification
/// </seealso>
internal sealed class RequireInvalidSyntaxRule : IEventHandler<RequireFieldsInvalidSyntaxEvent>
{
public void Handle(RequireFieldsInvalidSyntaxEvent @event, CompositionContext context)
{
var (requireDirective, argument, field, type, schema) = @event;

context.Log.Write(
RequireInvalidSyntax(
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 @@ -96,6 +96,9 @@
<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_RequireInvalidSyntax" xml:space="preserve">
<value>The @require directive on argument '{0}' in schema '{1}' contains invalid syntax in 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 @@ -67,6 +67,7 @@ private CompositionResult<SchemaDefinition> MergeSchemaDefinitions(CompositionCo
new QueryRootTypeInaccessibleRule(),
new RequireDirectiveInFieldsArgumentRule(),
new RequireInvalidFieldsTypeRule(),
new RequireInvalidSyntaxRule(),
new RootMutationUsedRule(),
new RootQueryUsedRule(),
new RootSubscriptionUsedRule()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using HotChocolate.Fusion.Logging;
using HotChocolate.Fusion.PreMergeValidation;
using HotChocolate.Fusion.PreMergeValidation.Rules;

namespace HotChocolate.Composition.PreMergeValidation.Rules;

public sealed class RequireInvalidSyntaxRuleTests : CompositionTestBase
{
private readonly PreMergeValidator _preMergeValidator = new([new RequireInvalidSyntaxRule()]);

[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_SYNTAX"));
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
// selection map 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[]>
{
// In the following example, the @require directive’s "fields" argument has invalid
// syntax because it is missing a closing brace.
{
[
"""
type Book {
id: ID!
title(lang: String! @require(fields: "author { name ")): String
}
type Author {
name: String
}
"""
],
[
"The @require directive on argument 'Book.title(lang:)' in schema 'A' " +
"contains invalid syntax in the 'fields' argument."
]
}
};
}
}

0 comments on commit 7ecc977

Please sign in to comment.