Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Mutable;

Expand Down Expand Up @@ -94,6 +95,8 @@ internal sealed record SatisfiabilityPathItem(
{
public ITypeDefinition FieldType { get; } = Field.Type.AsTypeDefinition();

public SelectionSetNode? ProvidedSelectionSet { get; init; }

private readonly int _hashCode = HashCode.Combine(Field, Type, SchemaName);

public override string ToString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ await archive.GetSourceSchemaNamesAsync(cancellationToken),
.SetSeverity(LogSeverity.Error)
.Build());

ImmutableArray<CompositionError> errors = [new("❌ Composition failed")];
return errors;
return (ImmutableArray<CompositionError>)[new("❌ Composition failed")];
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using HotChocolate.Types;
using HotChocolate.Types.Mutable;
using static HotChocolate.Fusion.WellKnownArgumentNames;
using static HotChocolate.Fusion.WellKnownDirectiveNames;

namespace HotChocolate.Fusion.Definitions;

/// <summary>
/// The <c>@fusion__subscribe</c> directive specifies broker metadata for a composed
/// subscription field.
/// </summary>
internal sealed class FusionSubscribeMutableDirectiveDefinition : MutableDirectiveDefinition
{
public FusionSubscribeMutableDirectiveDefinition(
MutableEnumTypeDefinition schemaMutableEnumType,
MutableScalarTypeDefinition fieldSelectionSetType,
MutableScalarTypeDefinition stringType)
: base(FusionSubscribe)
{
Arguments.Add(new MutableInputFieldDefinition(Schema, new NonNullType(schemaMutableEnumType)));
Arguments.Add(new MutableInputFieldDefinition(Topics, new ListType(new NonNullType(stringType))));
Arguments.Add(new MutableInputFieldDefinition(Broker, stringType));
Arguments.Add(new MutableInputFieldDefinition(Message, new NonNullType(fieldSelectionSetType)));

Locations = DirectiveLocation.FieldDefinition;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using HotChocolate.Types;
using HotChocolate.Types.Mutable;

namespace HotChocolate.Fusion.Definitions;

/// <summary>
/// The <c>@subscribe</c> directive declares event-stream metadata for a subscription field.
/// </summary>
internal sealed class SubscribeMutableDirectiveDefinition : MutableDirectiveDefinition
{
public SubscribeMutableDirectiveDefinition(
MutableScalarTypeDefinition fieldSelectionSetType,
MutableScalarTypeDefinition stringType)
: base(WellKnownDirectiveNames.Subscribe)
{
Arguments.Add(
new MutableInputFieldDefinition(
WellKnownArgumentNames.Topics,
new ListType(new NonNullType(stringType))));
Arguments.Add(
new MutableInputFieldDefinition(
WellKnownArgumentNames.Broker,
stringType));
Arguments.Add(
new MutableInputFieldDefinition(
WellKnownArgumentNames.Message,
new NonNullType(fieldSelectionSetType)));

Locations = DirectiveLocation.FieldDefinition;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Immutable;
using HotChocolate.Fusion.Info;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Mutable;
Expand Down Expand Up @@ -55,6 +57,34 @@ public void AddDirective(Directive directive)
return null;
}

public ImmutableArray<SubscribeDirectiveInfo> GetSubscribeDirectives()
{
ImmutableArray<SubscribeDirectiveInfo>.Builder? builder = null;

foreach (var subscribeDirective in member.Directives.AsEnumerable())
{
if (subscribeDirective.Name != WellKnownDirectiveNames.Subscribe)
{
continue;
}

if (!subscribeDirective.Arguments.TryGetValue(ArgumentNames.Message, out var message)
|| message is not StringValueNode messageArgument)
{
continue;
}

builder ??= ImmutableArray.CreateBuilder<SubscribeDirectiveInfo>();
builder.Add(
new SubscribeDirectiveInfo(
GetOptionalStringListArgument(subscribeDirective, ArgumentNames.Topics),
GetOptionalStringArgument(subscribeDirective, ArgumentNames.Broker),
ParseSelectionSet(messageArgument.Value)));
}

return builder?.ToImmutable() ?? [];
}

public HashSet<string> GetTags()
{
var tags = new HashSet<string>();
Expand Down Expand Up @@ -89,4 +119,44 @@ public bool HasInaccessibleDirective()
return member.Directives.ContainsName(WellKnownDirectiveNames.Inaccessible);
}
}

private static string? GetOptionalStringArgument(IDirective directive, string name)
=> directive.Arguments.TryGetValue(name, out var value) && value is StringValueNode stringValue
? stringValue.Value
: null;

private static ImmutableArray<string> GetOptionalStringListArgument(
IDirective directive,
string name)
{
if (!directive.Arguments.TryGetValue(name, out var value)
|| value is not ListValueNode listValue)
{
return [];
}

var builder = ImmutableArray.CreateBuilder<string>(listValue.Items.Count);

foreach (var item in listValue.Items)
{
if (item is StringValueNode stringValue)
{
builder.Add(stringValue.Value);
}
}
Comment thread
michaelstaib marked this conversation as resolved.
Dismissed

return builder.ToImmutable();
}

private static SelectionSetNode ParseSelectionSet(string value)
{
try
{
return Utf8GraphQLParser.Syntax.ParseSelectionSet(value);
}
catch (SyntaxException)
{
return Utf8GraphQLParser.Syntax.ParseSelectionSet($"{{ {value} }}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ public bool ExistsInSchema(string schemaName)
return null;
}

public ImmutableArray<string> GetFusionSubscribeSchemaNames()
{
ImmutableArray<string>.Builder? builder = null;

foreach (var directive in field.Directives.AsEnumerable())
{
if (directive.Name != DirectiveNames.FusionSubscribe)
{
continue;
}

builder ??= ImmutableArray.CreateBuilder<string>();
builder.Add((string)directive.Arguments[ArgumentNames.Schema].Value!);
}

return builder?.ToImmutable() ?? [];
}

public SelectionSetNode? GetFusionSubscribeMessage(string schemaName)
{
var fusionSubscribeDirective =
field.Directives.AsEnumerable().FirstOrDefault(
d =>
d.Name == DirectiveNames.FusionSubscribe
&& (string)d.Arguments[ArgumentNames.Schema].Value! == schemaName);

if (fusionSubscribeDirective?.Arguments.TryGetValue(ArgumentNames.Message, out var message) == true)
{
return ParseSelectionSet((string)message.Value!);
}

return null;
}

public List<string> GetFusionLookupMap()
{
var items = new List<string>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using HotChocolate.Fusion.Collections;
using HotChocolate.Fusion.Validators;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Mutable;
using static HotChocolate.Language.Utf8GraphQLParser.Syntax;

Expand All @@ -18,12 +19,33 @@ public bool Provides(
MutableObjectTypeDefinition type,
string schemaName,
MutableSchemaDefinition schema)
=> item.TryGetProvidedSelectionSet(field, type, schemaName, schema, out _);

public bool TryGetProvidedSelectionSet(
MutableOutputFieldDefinition field,
MutableObjectTypeDefinition type,
string schemaName,
MutableSchemaDefinition schema,
out SelectionSetNode? providedSelectionSet)
{
providedSelectionSet = null;

if (item.SchemaName != schemaName)
{
return false;
}

if (item.ProvidedSelectionSet is { } itemProvidedSelectionSet)
{
return SelectionSetProvider.TryGetSelectionSet(
schema,
itemProvidedSelectionSet,
item.FieldType,
field,
type,
out providedSelectionSet);
}

var selectionSetText = item.Field.GetFusionFieldProvides(item.SchemaName);

if (selectionSetText is null)
Expand All @@ -32,9 +54,129 @@ public bool Provides(
}

var selectionSet = ParseSelectionSet($"{{ {selectionSetText} }}");
var validator = new FieldInSelectionSetValidator(schema);

return validator.Validate(selectionSet, item.FieldType, field, type);
return SelectionSetProvider.TryGetSelectionSet(
schema,
selectionSet,
item.FieldType,
field,
type,
out providedSelectionSet);
}
}

private static class SelectionSetProvider
{
public static bool TryGetSelectionSet(
ISchemaDefinition schema,
SelectionSetNode selectionSet,
ITypeDefinition type,
IOutputFieldDefinition field,
ITypeDefinition declaringType,
out SelectionSetNode? providedSelectionSet)
{
List<ISelectionNode>? selections = null;
var isSelected = TryCollectSelectionSet(
schema,
selectionSet,
type,
field,
declaringType,
ref selections);

providedSelectionSet = selections is { Count: > 0 }
? new SelectionSetNode(selections)
: null;

return isSelected;
}

private static bool TryCollectSelectionSet(
ISchemaDefinition schema,
SelectionSetNode selectionSet,
ITypeDefinition type,
IOutputFieldDefinition field,
ITypeDefinition declaringType,
ref List<ISelectionNode>? selections)
{
var isSelected = false;

foreach (var selection in selectionSet.Selections)
{
switch (selection)
{
case FieldNode fieldNode:
if (type is not IComplexTypeDefinition complexType
|| !complexType.Fields.TryGetField(fieldNode.Name.Value, out var selectedField)
|| selectedField.Name != field.Name
|| !type.IsAssignableFrom(declaringType))
{
break;
}

isSelected = true;

if (fieldNode.SelectionSet is { } childSelectionSet)
{
selections ??= [];
selections.AddRange(childSelectionSet.Selections);
}

break;

case InlineFragmentNode { TypeCondition: null } inlineFragment:
isSelected |= TryCollectSelectionSet(
schema,
inlineFragment.SelectionSet,
type,
field,
declaringType,
ref selections);

break;

case InlineFragmentNode inlineFragment:
if (inlineFragment.TypeCondition is null
|| !schema.Types.TryGetType(
inlineFragment.TypeCondition.Name.Value,
out var typeCondition)
|| !TypesOverlap(schema, type, typeCondition))
{
break;
}

isSelected |= TryCollectSelectionSet(
schema,
inlineFragment.SelectionSet,
typeCondition,
field,
declaringType,
ref selections);

break;
}
}

return isSelected;
}

private static bool TypesOverlap(
ISchemaDefinition schema,
ITypeDefinition left,
ITypeDefinition right)
{
foreach (var leftPossibleType in schema.GetPossibleTypes(left))
{
foreach (var rightPossibleType in schema.GetPossibleTypes(right))
{
if (leftPossibleType == rightPossibleType)
{
return true;
}
}
Comment on lines +170 to +176
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ internal static class FusionBuiltIns
new OverrideMutableDirectiveDefinition(s_stringType),
new ProvidesMutableDirectiveDefinition(s_fieldSelectionSetType),
new RequireMutableDirectiveDefinition(s_fieldSelectionMapType),
new ShareableMutableDirectiveDefinition()
new ShareableMutableDirectiveDefinition(),
new SubscribeMutableDirectiveDefinition(s_fieldSelectionSetType, s_stringType)
]).ToFrozenDictionary(d => d.Name);

public static FrozenDictionary<string, MutableScalarTypeDefinition> SourceSchemaScalars { get; } =
Expand Down
Loading
Loading