Skip to content

Commit

Permalink
[Fusion] Added pre-merge validation rule "InputFieldDefaultMismatchRu…
Browse files Browse the repository at this point in the history
…le" (#7891)

Co-authored-by: Glen <glen.84@gmail.com>
  • Loading branch information
danielreynolds1 and glen-84 authored Jan 3, 2025
1 parent 6ec7760 commit a8c9ac6
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public static class LogEntryCodes
public const string ExternalMissingOnBase = "EXTERNAL_MISSING_ON_BASE";
public const string ExternalOnInterface = "EXTERNAL_ON_INTERFACE";
public const string ExternalUnused = "EXTERNAL_UNUSED";
public const string InputFieldDefaultMismatch = "INPUT_FIELD_DEFAULT_MISMATCH";
public const string KeyDirectiveInFieldsArg = "KEY_DIRECTIVE_IN_FIELDS_ARG";
public const string KeyFieldsHasArgs = "KEY_FIELDS_HAS_ARGS";
public const string KeyFieldsSelectInvalidType = "KEY_FIELDS_SELECT_INVALID_TYPE";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using HotChocolate.Language;
using HotChocolate.Skimmed;
using static HotChocolate.Fusion.Properties.CompositionResources;

Expand Down Expand Up @@ -161,6 +162,31 @@ public static LogEntry ExternalUnused(
schema);
}

public static LogEntry InputFieldDefaultMismatch(
IValueNode defaultValueA,
IValueNode defaultValueB,
InputFieldDefinition field,
string typeName,
SchemaDefinition schemaA,
SchemaDefinition schemaB)
{
var coordinate = new SchemaCoordinate(typeName, field.Name);

return new LogEntry(
string.Format(
LogEntryHelper_InputFieldDefaultMismatch,
defaultValueA,
coordinate,
schemaA.Name,
defaultValueB,
schemaB.Name),
LogEntryCodes.InputFieldDefaultMismatch,
LogSeverity.Error,
coordinate,
field,
schemaA);
}

public static LogEntry KeyDirectiveInFieldsArgument(
string entityTypeName,
Directive keyDirective,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using HotChocolate.Fusion.Events;
using HotChocolate.Fusion.PreMergeValidation.Info;
using HotChocolate.Language;
using HotChocolate.Skimmed;

Expand All @@ -26,6 +27,11 @@ internal record FieldArgumentGroupEvent(
string FieldName,
string TypeName) : IEvent;

internal record InputFieldGroupEvent(
string FieldName,
ImmutableArray<InputFieldInfo> FieldGroup,
string TypeName) : IEvent;

internal record KeyFieldEvent(
ComplexTypeDefinition EntityType,
Directive KeyDirective,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.PreMergeValidation.Info;

internal record FieldArgumentInfo(
InputFieldDefinition Argument,
OutputFieldDefinition Field,
INamedTypeDefinition Type,
SchemaDefinition Schema);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.PreMergeValidation.Info;

internal record InputFieldInfo(
InputFieldDefinition Field,
INamedTypeDefinition Type,
SchemaDefinition Schema);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.PreMergeValidation.Info;

internal record OutputFieldInfo(
OutputFieldDefinition Field,
INamedTypeDefinition Type,
SchemaDefinition Schema);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using HotChocolate.Skimmed;

namespace HotChocolate.Fusion.PreMergeValidation.Info;

internal record TypeInfo(INamedTypeDefinition Type, SchemaDefinition Schema);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using HotChocolate.Fusion.Collections;
using HotChocolate.Fusion.Errors;
using HotChocolate.Fusion.Events;
using HotChocolate.Fusion.PreMergeValidation.Info;
using HotChocolate.Fusion.Results;
using HotChocolate.Language;
using HotChocolate.Skimmed;
Expand Down Expand Up @@ -81,20 +82,42 @@ private void PublishEvents(CompositionContext context)
{
PublishEvent(new TypeGroupEvent(typeName, [.. typeGroup]), context);

MultiValueDictionary<string, OutputFieldInfo> fieldGroupByName = [];
MultiValueDictionary<string, InputFieldInfo> inputFieldGroupByName = [];
MultiValueDictionary<string, OutputFieldInfo> outputFieldGroupByName = [];

foreach (var (type, schema) in typeGroup)
{
if (type is ComplexTypeDefinition complexType)
switch (type)
{
foreach (var field in complexType.Fields)
{
fieldGroupByName.Add(field.Name, new OutputFieldInfo(field, type, schema));
}
case InputObjectTypeDefinition inputType:
foreach (var field in inputType.Fields)
{
inputFieldGroupByName.Add(
field.Name,
new InputFieldInfo(field, type, schema));
}

break;

case ComplexTypeDefinition complexType:
foreach (var field in complexType.Fields)
{
outputFieldGroupByName.Add(
field.Name,
new OutputFieldInfo(field, type, schema));
}

break;
}
}

foreach (var (fieldName, fieldGroup) in fieldGroupByName)
foreach (var (fieldName, fieldGroup) in inputFieldGroupByName)
{
PublishEvent(
new InputFieldGroupEvent(fieldName, [.. fieldGroup], typeName), context);
}

foreach (var (fieldName, fieldGroup) in outputFieldGroupByName)
{
PublishEvent(
new OutputFieldGroupEvent(fieldName, [.. fieldGroup], typeName), context);
Expand Down Expand Up @@ -446,18 +469,3 @@ private void PublishEvent<TEvent>(TEvent @event, CompositionContext context)
}
}
}

internal record TypeInfo(
INamedTypeDefinition Type,
SchemaDefinition Schema);

internal record OutputFieldInfo(
OutputFieldDefinition Field,
INamedTypeDefinition Type,
SchemaDefinition Schema);

internal record FieldArgumentInfo(
InputFieldDefinition Argument,
OutputFieldDefinition Field,
INamedTypeDefinition Type,
SchemaDefinition Schema);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Collections.Immutable;
using HotChocolate.Fusion.Events;
using HotChocolate.Language;
using static HotChocolate.Fusion.Logging.LogEntryHelper;

namespace HotChocolate.Fusion.PreMergeValidation.Rules;

/// <summary>
/// <para>
/// Input fields in different source schemas that have the same name are required to have consistent
/// default values. This ensures that there is no ambiguity or inconsistency when merging input
/// fields from different source schemas.
/// </para>
/// <para>
/// A mismatch in default values for input fields with the same name across different source schemas
/// will result in a schema composition error.
/// </para>
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Input-Field-Default-Mismatch">
/// Specification
/// </seealso>
internal sealed class InputFieldDefaultMismatchRule : IEventHandler<InputFieldGroupEvent>
{
public void Handle(InputFieldGroupEvent @event, CompositionContext context)
{
var (_, fieldGroup, typeName) = @event;

var fieldGroupWithDefaultValues = fieldGroup
.Where(i => i.Field.DefaultValue is not null)
.ToImmutableArray();

var defaultValues = fieldGroupWithDefaultValues
.Select(i => i.Field.DefaultValue!)
.ToImmutableHashSet(SyntaxComparer.BySyntax);

if (defaultValues.Count <= 1)
{
return;
}

for (var i = 0; i < fieldGroupWithDefaultValues.Length - 1; i++)
{
var (fieldA, _, schemaA) = fieldGroupWithDefaultValues[i];
var (fieldB, _, schemaB) = fieldGroupWithDefaultValues[i + 1];
var defaultValueA = fieldA.DefaultValue!;
var defaultValueB = fieldB.DefaultValue!;

if (!SyntaxComparer.BySyntax.Equals(defaultValueA, defaultValueB))
{
context.Log.Write(
InputFieldDefaultMismatch(
defaultValueA,
defaultValueB,
fieldA,
typeName,
schemaA,
schemaB));
}
}
}
}

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 @@ -48,6 +48,9 @@
<data name="LogEntryHelper_ExternalUnused" xml:space="preserve">
<value>External field '{0}' in schema '{1}' is not referenced by a @provides directive in the schema.</value>
</data>
<data name="LogEntryHelper_InputFieldDefaultMismatch" xml:space="preserve">
<value>The default value '{0}' of input field '{1}' in schema '{2}' differs from the default value of '{3}' in schema '{4}'.</value>
</data>
<data name="LogEntryHelper_KeyDirectiveInFieldsArgument" xml:space="preserve">
<value>A @key directive on type '{0}' in schema '{1}' references field '{2}', which must not include directive applications.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private CompositionResult<SchemaDefinition> MergeSchemaDefinitions(CompositionCo
new ExternalMissingOnBaseRule(),
new ExternalOnInterfaceRule(),
new ExternalUnusedRule(),
new InputFieldDefaultMismatchRule(),
new KeyDirectiveInFieldsArgumentRule(),
new KeyFieldsHasArgumentsRule(),
new KeyFieldsSelectInvalidTypeRule(),
Expand Down
Loading

0 comments on commit a8c9ac6

Please sign in to comment.