Skip to content

Commit

Permalink
Merge branch 'main' into remove-unused-methods-from-IError
Browse files Browse the repository at this point in the history
  • Loading branch information
JesseXia authored Jan 4, 2025
2 parents eb259e5 + 7ecc977 commit 03844d9
Show file tree
Hide file tree
Showing 34 changed files with 1,368 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ 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";
public const string KeyInvalidFields = "KEY_INVALID_FIELDS";
public const string KeyInvalidSyntax = "KEY_INVALID_SYNTAX";
public const string LookupMustNotReturnList = "LOOKUP_MUST_NOT_RETURN_LIST";
public const string LookupShouldHaveNullableReturnType = "LOOKUP_SHOULD_HAVE_NULLABLE_RETURN_TYPE";
public const string OutputFieldTypesNotMergeable = "OUTPUT_FIELD_TYPES_NOT_MERGEABLE";
public const string ProvidesDirectiveInFieldsArg = "PROVIDES_DIRECTIVE_IN_FIELDS_ARG";
public const string ProvidesFieldsHasArgs = "PROVIDES_FIELDS_HAS_ARGS";
public const string ProvidesFieldsMissingExternal = "PROVIDES_FIELDS_MISSING_EXTERNAL";
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 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
@@ -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 Expand Up @@ -257,6 +283,44 @@ public static LogEntry KeyInvalidSyntax(
schema);
}

public static LogEntry LookupMustNotReturnList(
OutputFieldDefinition field,
INamedTypeDefinition type,
SchemaDefinition schema)
{
var coordinate = new SchemaCoordinate(type.Name, field.Name);

return new LogEntry(
string.Format(
LogEntryHelper_LookupMustNotReturnList,
coordinate,
schema.Name),
LogEntryCodes.LookupMustNotReturnList,
LogSeverity.Error,
coordinate,
field,
schema);
}

public static LogEntry LookupShouldHaveNullableReturnType(
OutputFieldDefinition field,
INamedTypeDefinition type,
SchemaDefinition schema)
{
var coordinate = new SchemaCoordinate(type.Name, field.Name);

return new LogEntry(
string.Format(
LogEntryHelper_LookupShouldHaveNullableReturnType,
coordinate,
schema.Name),
LogEntryCodes.LookupShouldHaveNullableReturnType,
LogSeverity.Warning,
coordinate,
field,
schema);
}

public static LogEntry OutputFieldTypesNotMergeable(
OutputFieldDefinition field,
string typeName,
Expand Down Expand Up @@ -401,6 +465,42 @@ 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 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
@@ -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 Expand Up @@ -87,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
@@ -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 @@ -385,7 +408,14 @@ private void PublishRequireEvents(
}
catch (SyntaxException)
{
// Ignore.
PublishEvent(
new RequireFieldsInvalidSyntaxEvent(
requireDirective,
argument,
field,
type,
schema),
context);
}
}

Expand Down Expand Up @@ -446,18 +476,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));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using HotChocolate.Fusion.Events;
using HotChocolate.Skimmed;
using static HotChocolate.Fusion.Logging.LogEntryHelper;

namespace HotChocolate.Fusion.PreMergeValidation.Rules;

/// <summary>
/// Fields annotated with the <c>@lookup</c> directive are intended to retrieve a single entity
/// based on provided arguments. To avoid ambiguity in entity resolution, such fields must return a
/// single object and not a list. This validation rule enforces that any field annotated with
/// <c>@lookup</c> must have a return type that is <b>NOT</b> a list.
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec--lookup-must-not-return-a-list">
/// Specification
/// </seealso>
internal sealed class LookupMustNotReturnListRule : IEventHandler<OutputFieldEvent>
{
public void Handle(OutputFieldEvent @event, CompositionContext context)
{
var (field, type, schema) = @event;

if (ValidationHelper.IsLookup(field) && field.Type.NullableType() is ListTypeDefinition)
{
context.Log.Write(LookupMustNotReturnList(field, type, schema));
}
}
}
Loading

0 comments on commit 03844d9

Please sign in to comment.