Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve issue where an Entity is required in subgraphs and remove non-resolvable from _entity output #7165

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -12,8 +12,8 @@
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using HotChocolate.Types.Helpers;
using static HotChocolate.ApolloFederation.ThrowHelper;
using static HotChocolate.ApolloFederation.FederationContextData;
using static HotChocolate.ApolloFederation.ThrowHelper;
using static HotChocolate.Types.TagHelper;

namespace HotChocolate.ApolloFederation;
Expand Down Expand Up @@ -95,7 +95,10 @@ public override IEnumerable<TypeReference> RegisterMoreTypes(

_registeredTypes = true;
yield return _typeInspector.GetTypeRef(typeof(_Service));
yield return _typeInspector.GetTypeRef(typeof(_EntityType));
if (_entityTypes.Count > 0)
{
yield return _typeInspector.GetTypeRef(typeof(_EntityType));
}
yield return _typeInspector.GetTypeRef(typeof(_AnyType));
yield return _typeInspector.GetTypeRef(typeof(FieldSetType));

Expand Down Expand Up @@ -283,14 +286,6 @@ internal override void OnAfterResolveRootType(
}
}

public override void OnTypesInitialized()
{
if (_entityTypes.Count == 0)
{
throw EntityType_NoEntities();
}
}

public override void OnBeforeCompleteType(
ITypeCompletionContext completionContext,
DefinitionBase definition)
Expand Down Expand Up @@ -388,7 +383,10 @@ private void AddServiceTypeToQueryType(

var objectTypeDefinition = (ObjectTypeDefinition)definition!;
objectTypeDefinition.Fields.Add(ServerFields.CreateServiceField(_context));
objectTypeDefinition.Fields.Add(ServerFields.CreateEntitiesField(_context));
if (_entityTypes.Count > 0)
{
objectTypeDefinition.Fields.Add(ServerFields.CreateEntitiesField(_context));
}
}

private void ApplyMethodLevelReferenceResolvers(
Expand Down Expand Up @@ -432,8 +430,15 @@ private void AddToUnionIfHasTypeLevelKeyDirective(
ObjectType objectType,
ObjectTypeDefinition objectTypeDefinition)
{
if (objectTypeDefinition.Directives.Any(d => d.Value is KeyDirective) ||
objectTypeDefinition.Fields.Any(f => f.ContextData.ContainsKey(KeyMarker)))
if (objectTypeDefinition.Directives.FirstOrDefault(d => d.Value is KeyDirective) is { } keyDirective &&
((KeyDirective)keyDirective.Value).Resolvable)
{
_entityTypes.Add(objectType);
return;
}

if (objectTypeDefinition.Fields.Any(f => f.ContextData.TryGetValue(KeyMarker, out var resolvable) &&
resolvable is true))
{
_entityTypes.Add(objectType);
}
Expand All @@ -457,11 +462,22 @@ private void AggregatePropertyLevelKeyDirectives(

IReadOnlyList<ObjectFieldDefinition> fields = objectTypeDefinition.Fields;
var fieldSet = new StringBuilder();
bool? resolvable = null;

foreach (var fieldDefinition in fields)
{
if (fieldDefinition.ContextData.ContainsKey(KeyMarker))
if (fieldDefinition.ContextData.TryGetValue(KeyMarker, out var value) &&
value is bool currentResolvable)
{
if (resolvable is null)
{
resolvable = currentResolvable;
}
else if (resolvable != currentResolvable)
{
throw Key_FieldSet_ResolvableMustBeConsistent(fieldDefinition.Member!);
}

if (fieldSet.Length > 0)
{
fieldSet.Append(' ');
Expand All @@ -472,7 +488,7 @@ private void AggregatePropertyLevelKeyDirectives(
}

// add the key directive with the dynamically generated field set.
AddKeyDirective(objectTypeDefinition, fieldSet.ToString());
AddKeyDirective(objectTypeDefinition, fieldSet.ToString(), resolvable ?? true);

// register dependency to the key directive so that it is completed before
// we complete this type.
Expand All @@ -486,9 +502,12 @@ private void AggregatePropertyLevelKeyDirectives(
discoveryContext.Dependencies.Add(new(directiveDefinition.Type));
}

// since this type has now a key directive we also need to add this type to
// the _Entity union type.
_entityTypes.Add(objectType);
if (resolvable ?? true)
{
// since this type has now a key directive we also need to add this type to
// the _Entity union type provided that the key is resolvable.
_entityTypes.Add(objectType);
}
}

private void AddMemberTypesToTheEntityUnionType(
Expand All @@ -507,11 +526,12 @@ private void AddMemberTypesToTheEntityUnionType(

private void AddKeyDirective(
ObjectTypeDefinition objectTypeDefinition,
string fieldSet)
string fieldSet,
bool resolvable)
{
objectTypeDefinition.Directives.Add(
new DirectiveDefinition(
new KeyDirective(fieldSet),
new KeyDirective(fieldSet, resolvable),
_keyDirectiveReference));
}
}
}

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 @@ -171,9 +171,6 @@
<data name="FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
</data>
<data name="ThrowHelper_EntityType_NoEntities" xml:space="preserve">
<value>The schema has no types with a KeyDirective and therefore no entities. Apollo federation requires at least one entity.</value>
</data>
<data name="ThrowHelper_EntityResolver_NoEntityResolverFound" xml:space="preserve">
<value>The apollo gateway tries to resolve an entity for which no EntityResolver method was found.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,6 @@ public static SerializationException Scalar_CannotParseValue(
.Build(),
scalarType);

/// <summary>
/// The schema doesn't contain any types with a key directive
/// and therefore no entities. An Apollo federation service
/// needs at least one entity.
/// </summary>
public static SchemaException EntityType_NoEntities() =>
new SchemaException(
SchemaErrorBuilder.New()
.SetMessage(ThrowHelper_EntityType_NoEntities)
// .SetCode(ErrorCodes.ApolloFederation.NoEntitiesDeclared)
.Build());

/// <summary>
/// The apollo gateway tries to resolve an entity for which no
/// EntityResolver method was found.
Expand All @@ -120,13 +108,13 @@ public static SchemaException Key_FieldSet_CannotBeEmpty(
.Build());

/// <summary>
/// The key attribute is used on the type level without specifying the fieldset.
/// The key attribute is used on the field level whilst specifying the fieldset.
/// </summary>
public static SchemaException Key_FieldSet_MustBeEmpty(
MemberInfo member)
{
var type = member.ReflectedType ?? member.DeclaringType!;

return new SchemaException(
SchemaErrorBuilder.New()
.SetMessage("The specified key attribute must not specify a fieldset when annotated to a field.")
Expand All @@ -136,6 +124,23 @@ public static SchemaException Key_FieldSet_MustBeEmpty(
.Build());
}

/// <summary>
/// The key attribute is used on the field level with inconsistent resolvable values.
/// </summary>
public static SchemaException Key_FieldSet_ResolvableMustBeConsistent(
MemberInfo member)
{
var type = member.ReflectedType ?? member.DeclaringType!;

return new SchemaException(
SchemaErrorBuilder.New()
.SetMessage("The specified key attributes must shared the same resolvable values when annotated on multiple fields.")
.SetExtension("type", type.FullName ?? type.Name)
.SetExtension("member", member.Name)
// .SetCode(ErrorCodes.ApolloFederation.KeyFieldSetNullOrEmpty)
.Build());
}

/// <summary>
/// The provides attribute is used and the fieldset is set to <c>null</c> or
/// <see cref="string.Empty"/>.
Expand Down Expand Up @@ -209,10 +214,10 @@ public static SchemaException FederationVersion_Unknown(
ThrowHelper_FederationVersion_Unknown,
version)
.Build());

public static SchemaException Contact_Not_Repeatable() =>
new SchemaException(
SchemaErrorBuilder.New()
.SetMessage("The @contact directive is not repeatable and can.")
.Build());
}
.Build());
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ private void ConfigureField(MemberInfo member, IDescriptor descriptor)
switch (descriptor)
{
case IObjectFieldDescriptor fieldDesc:
fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, true);
fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, Resolvable);
break;

case IInterfaceFieldDescriptor fieldDesc:
fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, true);
fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, Resolvable);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static IEntityResolverDescriptor Key(
{
ArgumentNullException.ThrowIfNull(descriptor);
ArgumentException.ThrowIfNullOrEmpty(fieldSet);

descriptor.Directive(new KeyDirective(fieldSet, resolvable));
return new EntityResolverDescriptor<object>(descriptor);
}
Expand Down Expand Up @@ -139,7 +139,7 @@ public static IInterfaceTypeDescriptor Key(
{
ArgumentNullException.ThrowIfNull(descriptor);
ArgumentException.ThrowIfNullOrEmpty(fieldSet);

return descriptor.Directive(new KeyDirective(fieldSet, resolvable));
}
}
}
Loading
Loading