Skip to content

Commit 450c609

Browse files
authored
[Fusion] Added source schema validation rule "IsInvalidSyntaxRule" (#8211)
1 parent 7457d3b commit 450c609

File tree

9 files changed

+186
-1
lines changed

9 files changed

+186
-1
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Events/SchemaEvents.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ internal record InterfaceTypeEvent(
3434
MutableInterfaceTypeDefinition InterfaceType,
3535
MutableSchemaDefinition Schema) : IEvent;
3636

37+
internal record IsFieldInvalidSyntaxEvent(
38+
Directive IsDirective,
39+
MutableInputFieldDefinition Argument,
40+
MutableOutputFieldDefinition Field,
41+
MutableComplexTypeDefinition Type,
42+
MutableSchemaDefinition Schema) : IEvent;
43+
3744
internal record IsFieldInvalidTypeEvent(
3845
Directive IsDirective,
3946
MutableInputFieldDefinition Argument,

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static class LogEntryCodes
2323
public const string InvalidGraphQL = "INVALID_GRAPHQL";
2424
public const string InvalidShareableUsage = "INVALID_SHAREABLE_USAGE";
2525
public const string IsInvalidFieldType = "IS_INVALID_FIELD_TYPE";
26+
public const string IsInvalidSyntax = "IS_INVALID_SYNTAX";
2627
public const string KeyDirectiveInFieldsArg = "KEY_DIRECTIVE_IN_FIELDS_ARG";
2728
public const string KeyFieldsHasArgs = "KEY_FIELDS_HAS_ARGS";
2829
public const string KeyFieldsSelectInvalidType = "KEY_FIELDS_SELECT_INVALID_TYPE";

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Logging/LogEntryHelper.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,24 @@ public static LogEntry IsInvalidFieldType(
440440
schema);
441441
}
442442

443+
public static LogEntry IsInvalidSyntax(
444+
Directive isDirective,
445+
string argumentName,
446+
string fieldName,
447+
string typeName,
448+
MutableSchemaDefinition schema)
449+
{
450+
var coordinate = new SchemaCoordinate(typeName, fieldName, argumentName);
451+
452+
return new LogEntry(
453+
string.Format(LogEntryHelper_IsInvalidSyntax, coordinate, schema.Name),
454+
LogEntryCodes.IsInvalidSyntax,
455+
LogSeverity.Error,
456+
coordinate,
457+
isDirective,
458+
schema);
459+
}
460+
443461
public static LogEntry KeyDirectiveInFieldsArgument(
444462
string typeName,
445463
Directive keyDirective,

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/Properties/CompositionResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@
210210
<data name="LogEntryHelper_IsInvalidFieldType" xml:space="preserve">
211211
<value>The @is directive on argument '{0}' in schema '{1}' must specify a string value for the 'field' argument.</value>
212212
</data>
213+
<data name="LogEntryHelper_IsInvalidSyntax" xml:space="preserve">
214+
<value>The @is directive on argument '{0}' in schema '{1}' contains invalid syntax in the 'field' argument.</value>
215+
</data>
213216
<data name="LogEntryHelper_KeyDirectiveInFieldsArgument" xml:space="preserve">
214217
<value>A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications.</value>
215218
</data>

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SchemaComposer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public CompositionResult<MutableSchemaDefinition> Compose()
8181
new ExternalUnusedRule(),
8282
new InvalidShareableUsageRule(),
8383
new IsInvalidFieldTypeRule(),
84+
new IsInvalidSyntaxRule(),
8485
new KeyDirectiveInFieldsArgumentRule(),
8586
new KeyFieldsHasArgumentsRule(),
8687
new KeyFieldsSelectInvalidTypeRule(),
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using HotChocolate.Fusion.Events;
2+
using HotChocolate.Fusion.Events.Contracts;
3+
using static HotChocolate.Fusion.Logging.LogEntryHelper;
4+
5+
namespace HotChocolate.Fusion.SourceSchemaValidationRules;
6+
7+
/// <summary>
8+
/// The <c>@is</c> directive’s <c>field</c> argument must be syntactically valid GraphQL. If
9+
/// the selection map string is malformed (e.g., missing closing braces, unbalanced quotes, invalid
10+
/// tokens), then the schema cannot be composed correctly. In such cases, the error
11+
/// <c>IS_INVALID_SYNTAX</c> is raised.
12+
/// </summary>
13+
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Is-Invalid-Syntax">
14+
/// Specification
15+
/// </seealso>
16+
internal sealed class IsInvalidSyntaxRule : IEventHandler<IsFieldInvalidSyntaxEvent>
17+
{
18+
public void Handle(IsFieldInvalidSyntaxEvent @event, CompositionContext context)
19+
{
20+
var (isDirective, argument, field, type, schema) = @event;
21+
22+
context.Log.Write(
23+
IsInvalidSyntax(
24+
isDirective,
25+
argument.Name,
26+
field.Name,
27+
type.Name,
28+
schema));
29+
}
30+
}

src/HotChocolate/Fusion-vnext/src/Fusion.Composition/SourceSchemaValidator.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,29 @@ private void PublishIsEvents(
263263
argument.Directives.AsEnumerable().First(d => d.Name == DirectiveNames.Is);
264264

265265
if (!isDirective.Arguments.TryGetValue(ArgumentNames.Field, out var f)
266-
|| f is not StringValueNode)
266+
|| f is not StringValueNode fieldArgument)
267267
{
268268
PublishEvent(
269269
new IsFieldInvalidTypeEvent(isDirective, argument, field, type, schema),
270270
context);
271+
272+
return;
273+
}
274+
275+
try
276+
{
277+
new FieldSelectionMapParser(fieldArgument.Value).Parse();
278+
}
279+
catch (FieldSelectionMapSyntaxException)
280+
{
281+
PublishEvent(
282+
new IsFieldInvalidSyntaxEvent(
283+
isDirective,
284+
argument,
285+
field,
286+
type,
287+
schema),
288+
context);
271289
}
272290
}
273291

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System.Collections.Immutable;
2+
using HotChocolate.Fusion.Logging;
3+
using static HotChocolate.Fusion.CompositionTestHelper;
4+
5+
namespace HotChocolate.Fusion.SourceSchemaValidationRules;
6+
7+
public sealed class IsInvalidSyntaxRuleTests
8+
{
9+
private static readonly object s_rule = new IsInvalidSyntaxRule();
10+
private static readonly ImmutableArray<object> s_rules = [s_rule];
11+
private readonly CompositionLog _log = new();
12+
13+
[Theory]
14+
[MemberData(nameof(ValidExamplesData))]
15+
public void Examples_Valid(string[] sdl)
16+
{
17+
// arrange
18+
var schemas = CreateSchemaDefinitions(sdl);
19+
var validator = new SourceSchemaValidator(schemas, s_rules, _log);
20+
21+
// act
22+
var result = validator.Validate();
23+
24+
// assert
25+
Assert.True(result.IsSuccess);
26+
Assert.True(_log.IsEmpty);
27+
}
28+
29+
[Theory]
30+
[MemberData(nameof(InvalidExamplesData))]
31+
public void Examples_Invalid(string[] sdl, string[] errorMessages)
32+
{
33+
// arrange
34+
var schemas = CreateSchemaDefinitions(sdl);
35+
var validator = new SourceSchemaValidator(schemas, s_rules, _log);
36+
37+
// act
38+
var result = validator.Validate();
39+
40+
// assert
41+
Assert.True(result.IsFailure);
42+
Assert.Equal(errorMessages, _log.Select(e => e.Message).ToArray());
43+
Assert.True(_log.All(e => e.Code == "IS_INVALID_SYNTAX"));
44+
Assert.True(_log.All(e => e.Severity == LogSeverity.Error));
45+
}
46+
47+
public static TheoryData<string[]> ValidExamplesData()
48+
{
49+
return new TheoryData<string[]>
50+
{
51+
// In the following example, the @is directive’s "field" argument is a valid selection
52+
// map and satisfies the rule.
53+
{
54+
[
55+
"""
56+
type User @key(fields: "id") {
57+
id: ID!
58+
profile(name: String! @is(field: "name")): Profile
59+
}
60+
61+
type Profile {
62+
id: ID!
63+
name: String
64+
}
65+
"""
66+
]
67+
}
68+
};
69+
}
70+
71+
public static TheoryData<string[], string[]> InvalidExamplesData()
72+
{
73+
return new TheoryData<string[], string[]>
74+
{
75+
// In the following example, the @is directive’s "field" argument has invalid syntax
76+
// because it is missing a closing brace.
77+
{
78+
[
79+
"""
80+
type User @key(fields: "id") {
81+
id: ID!
82+
profile(name: String! @is(field: "{ name ")): Profile
83+
}
84+
85+
type Profile {
86+
id: ID!
87+
name: String
88+
}
89+
"""
90+
],
91+
[
92+
"The @is directive on argument 'User.profile(name:)' in schema 'A' contains " +
93+
"invalid syntax in the 'field' argument."
94+
]
95+
}
96+
};
97+
}
98+
}

0 commit comments

Comments
 (0)