Add the ability to mark HotChocolate Generated types with directives to support federation
Product
Hot Chocolate
Is your feature request related to a problem?
When using the UsePaging attribute on queries there is a type, PageInfo, that is generated and added to the schema. In federated graphs on Federation v2, shared value types in sub-graphs must be tagged with the @shareable directive or the subgraph schema will fail the subgraph check with an error similar to what's below.
error[E029]: Encountered 4 build errors while trying to build subgraph "my-subgraph" into supergraph "my-supergraph".
Caused by:
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.hasNextPage" is resolved from multiple subgraphs: it is resolved from subgraphs "x", "y", and "z" and defined as non-shareable in subgraph "my-subgraph"
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.hasPreviousPage" is resolved from multiple subgraphs: it is resolved from subgraphs "x", "y", and "z" and defined as non-shareable in subgraph "my-subgraph"
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.startCursor" is resolved from multiple subgraphs: it is resolved from subgraphs "x", "y", and "z" and defined as non-shareable in subgraph "my-subgraph"
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.endCursor" is resolved from multiple subgraphs: it is resolved from subgraphs "x", "y", and "z" and defined as non-shareable in subgraph "my-subgraph"
The solution you'd like
I would like the HotChocolate library to support adding the shareable directive to HotChocolate framework generated types, such as PageInfo, so that they can be used in federated schemas on Federation v2.
It's a bit too late for 14 to add this ... but I will add it to the 14.1 backlog
Is there any workaround so far to rename the PageInfo type to avoid the conflicts in a federation scenario?
I was able to get around this by writing a custom type interceptor to add the shareable directive to the PageInfo type. However in order to get the @shareable directive imported correctly in the @link directive I had to add a dummy type with a field marked as shareable using the HotChocolate extension method.
public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, DefinitionBase definition)
{
if (definition is ObjectTypeDefinition otd)
{
switch (definition.Name)
{
case "PageInfo":
otd.AddDirective("shareable", completionContext.TypeInspector);
break;
}
}
base.OnBeforeCompleteType(completionContext, definition);
}
Yes ... a type interceptor is also how we will implement it in the core.
I was able to get around this by writing a custom type interceptor to add the shareable directive to the PageInfo type. However in order to get the
@shareabledirective imported correctly in the@linkdirective I had to add a dummy type with a field marked as shareable using the HotChocolate extension method.public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, DefinitionBase definition) { if (definition is ObjectTypeDefinition otd) { switch (definition.Name) { case "PageInfo": otd.AddDirective("shareable", completionContext.TypeInspector); break; } } base.OnBeforeCompleteType(completionContext, definition); }
Thanks a lot for sharing this! Could you also share the rest of it where you added the dummy type, etc? Thanks!
I tried to use the interceptor
public class ShareableDirectiveTypeInterceptor : TypeInterceptor
{
public override void OnBeforeCompleteType(
ITypeCompletionContext completionContext,
DefinitionBase definition)
{
if (definition is ObjectTypeDefinition otd)
{
switch (definition.Name)
{
case "PageInfo":
otd.AddDirective("shareable", completionContext.TypeInspector);
break;
}
}
base.OnBeforeCompleteType(completionContext, definition);
}
}
but got:
The specified directive `@shareable` is not allowed on the current location `Object`. (HotChocolate.Types.Pagination.PageInfoType)
I was able to get around this by writing a custom type interceptor to add the shareable directive to the PageInfo type. However in order to get the
@shareabledirective imported correctly in the@linkdirective I had to add a dummy type with a field marked as shareable using the HotChocolate extension method.public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, DefinitionBase definition) { if (definition is ObjectTypeDefinition otd) { switch (definition.Name) { case "PageInfo": otd.AddDirective("shareable", completionContext.TypeInspector); break; } } base.OnBeforeCompleteType(completionContext, definition); }Thanks a lot for sharing this! Could you also share the rest of it where you added the dummy type, etc? Thanks!
Here is a dummy type similar to what we used to get the shareable directive properly imported into the link directive. This type will be added to your graph, but by marking it inaccessible clients will not be able to query for it. Once HotChocolate supports marking PageInfo and other types as shareable we will remove this type from our graph.
```csharp
public class ShareableDirectiveLoaderType : ObjectType
{
public ShareableDirectiveLoaderType()
: base(descriptor =>
{
descriptor.Name("ShareableDirectiveLoader")
.Description("This type is only used to make HotChocolate .NET APIs import the Shareable directive from Apollo into their sub-graph. It is marked with the inaccessible directive to prevent clients from querying it.")
.Inaccessible();
descriptor.Field("ShareableField")
.Description(@"This field is tagged with HotChocolate's .Shareable() extension method. This makes HotChocolate import the shareable directive from Apollo and add it to the link directive. The field will also be marked with the shareable directive.")
.Type<StringType>()
.Shareable()
// this defines the resolver within the type
.Resolve(context =>
{
return "Shared field.";
});
})
{
}
}
Here is a very easy way to add the @shareable directive to the PageInfo type without any hacks:
public class PageInfoType : ObjectTypeExtension
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("PageInfo");
descriptor.Shareable();
}
}
Just register the type extension:
public static IRequestExecutorBuilder ConfigureGraphqlSchema(this IRequestExecutorBuilder builder) =>
builder
.AddApolloFederation()
.AddQueryType(ConfigureQueryType)
.AddTypeExtension<PageInfoType>();
@rohinz good point ... often simple type extensions are the simplest way to do these things :)
This is now implemented
https://github.com/ChilliCream/graphql-platform/releases/tag/14.0.0-p.163