GithubHelp home page GithubHelp logo

apollographql / federation-hotchocolate Goto Github PK

View Code? Open in Web Editor NEW
16.0 9.0 8.0 342 KB

HotChocolate support for Apollo Federation

Home Page: https://www.apollographql.com/docs/federation/

License: MIT License

C# 99.65% Dockerfile 0.35%
dotnet federation graphql hotchocolate

federation-hotchocolate's Introduction

Continuous Integration MIT License Nuget Join the community forum Join our Discord server

Apollo Federation for Hot Chocolate

Warning Due to a breaking change to the public API, we cannot support newer versions of HotChocolate until their replacement API (currently work in progress) is complete. We can only support v13.5.x and v13.6.x releases.

This is the official Apollo Federation support library for Hot Chocolate with support for Federation 1 and Federation 2 subgraphs. For backwards compatibility, it was based on HotChocolate's original Fed 1 module with added support for Fed v2. We recommend migrating to this officially supported library as ongoing Federation support for HotChocolate ecosystem and using rover subgraph create to kickstart new projects.

Apollo Federation is a powerful, open architecture that helps you create a unified supergraph that combines multiple GraphQL APIs. ApolloGraphQL.HotChocolate.Federation provides Apollo Federation support for building subgraphs in the HotChocolate ecosystem. Individual subgraphs can be run independently of each other but can also specify relationships to the other subgraphs by using Federated directives. See Apollo Federation documentation for details.

Installation

ApolloGraphQL.HotChocolate.Federation package is published to Nuget. Update your .csproj file with following package references

  <ItemGroup>
    <!-- make sure to also include HotChocolate package -->
    <PackageReference Include="HotChocolate.AspNetCore" Version="13.6.0" />
    <!-- federation package -->
    <PackageReference Include="ApolloGraphQL.HotChocolate.Federation" Version="$LatestVersion" />
  </ItemGroup>

After installing the necessary packages, you need to register Apollo Federation with your GraphQL service.

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2() 
    // register your types and services
    ;

var app = builder.Build();
app.MapGraphQL();
app.Run();

If you would like to opt-in to Federation v1 schema, you need to use .AddApolloFederation() extension instead.

Usage

Refer to HotChocolate documentation for detailed information on how to create GraphQL schemas and configure your server.

Apollo Federation requires subgraphs to provide some additional metadata to make them supergraph aware. Entities are GraphQL objects that can be uniquely identified across the supergraph by the specified @keys. Since entities can be extended by various subgraphs, we need an extra entry point to access the entities, i.e. subgraphs need to implement reference resolvers for entities that they support.

See Apollo documentation for additional Federation details.

Annotation

All federated directives are provided as attributes that can be applied directly on classes/fields/methods.

[Key("id")]
public class Product
{
    public Product(string id, string name, string? description)
    {
        Id = id;
        Name = name;
        Description = description;
    }

    [ID]
    public string Id { get; }

    public string Name { get; }

    public string? Description { get; }

    // assumes ProductRepository with GetById method exists
    // reference resolver method must be public static
    [ReferenceResolver]
    public static Product GetByIdAsync(
        string id,
        ProductRepository productRepository)
        => productRepository.GetById(id);
}

This will generate following type

type Product @key(fields: "id") {
    id: ID!
    name: String!
    description: String
}

Federation Attributes

Federation v1 directives

Federation v2 directives (includes all of the v1 directives)

Entity resolution

  • Map applicable on entity resolver method paramaters, allows you to map complex argument to a simpler representation value, e.g. [Map("foo.bar")] string bar
  • ReferenceResolver applicable on public static methods within an entity class to indicate entity resolver

Code First

Alternatively, if you need more granular control, you can use code first approach and manually populate federation information on the underlying GraphQL type descriptor. All federated directives expose corresponding methods on the applicable descriptor.

public class Product
{
    public Product(string id, string name, string? description)
    {
        Id = id;
        Name = name;
        Description = description;
    }

    [ID]
    public string Id { get; }

    public string Name { get; }

    public string? Description { get; }
}

public class ProductType : ObjectType<Product>
{
    protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
    {
        descriptor
            .Key("id")
            .ResolveReferenceWith(t => GetProduct(default!, default!));
    }

    private static Product GetProduct(
        string id,
        ProductRepository productRepository)
        => productRepository.GetById(upc);
}

This will generate following type

type Product @key(fields: "id") {
    id: ID!
    name: String!
    description: String
}

Descriptor Extensions

Federation v1 directives

Federation v2 directives (includes all of the v1 directives)

Entity resolution

  • you have to provide ResolveReferenceWith function to be able to resolve the entities

Advanced Use Cases

Generating schema at build time

See HotChocolate documentation for details on the server support for command line interface. In order to generate schema at build time, you need to add additional dependency on HotChocolate.AspNetCore.CommandLine package and configure your server to allow it to RunWithGraphQLCommands.

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2()
    // register your types and services
    ;

var app = builder.Build();
app.MapGraphQL();
app.RunWithGraphQLCommands(args);

You can then generate your schema by running

dotnet run -- schema export --output schema.graphql

Specifying Federation Version

By default, ApolloGraphQL.HotChocolate.Federation will generate schema using latest supported Federation version. If you would like to opt-in to use older versions you can so by specifying the version when configuring AddApolloFederationV2 extension.

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(FederationVersion.FEDERATION_23)
    // register your types and services
    ;

Alternatively, you can also provide custom FederatedSchema that targets specific Federation version

public class CustomSchema : FederatedSchema
{
    public CustomSchema() : base(FederationVersion.FEDERATION_23) {
    }
}

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(new CustomSchema())
    // register your types and services
    ;

Customizing Schema

If you would like to customize your schema by applying some directives, you can also provide custom FederatedSchema that can be annotated with attributes extending SchemaTypeDescriptorAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = true)]
public sealed class CustomAttribute : SchemaTypeDescriptorAttribute
{
    public override void OnConfigure(IDescriptorContext context, ISchemaTypeDescriptor descriptor, Type type)
    {
        // configure your directive here
    }
}

[Custom]
public class CustomSchema : FederatedSchema
{
    public CustomSchema() : base(FederationVersion.FEDERATION_23) {
    }
}

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(new CustomSchema())
    // register your types and services
    ;

Alternatively, you can also specify custom schema configuration action when building federated subgraph

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(schemaConfiguration: s =>
    {
        // apply your directive here
    })
    // register your types and services
    ;

Non-resolvable @key

Your subgraphs can use an entity as a field's return type without contributing any fields to that entity. Since we still need a type definition to generate a valid schema, we can define a stub object with [NonResolvableKeyAttribute].

public class Review {
    public Review(Product product, int score)
    {
        Product = product;
        Score = score
    }

    public Product Product { get; }
    public int Score { get; }
}


[NonResolvableKey("id")]
public class Product {
    public Product(string id)
    {
        Id = id;
    }

    public string Id { get; }
}

@composedDirective usage

By default, Supergraph schema excludes all custom directives. The @composeDirective is used to specify custom directives that should be preserved in the Supergraph schema.

ApolloGraphQL.HotChocolate.Federation provides common FederatedSchema class that automatically applies Apollo Federation v2 @link definition. When applying any custom schema directives, you should extend this class and add required attributes/directives.

When applying @composedDirective you also need to @link it your specification. Your custom schema should then be passed to the AddApolloFederationV2 extension.

[ComposeDirective("@custom")]
[Link("https://myspecs.dev/myCustomDirective/v1.0", new string[] { "@custom" })]
public class CustomSchema : FederatedSchema
{
}

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(new CustomSchema())
    // register your types and services
    ;

Alternatively, you can apply @composedDirective by directly applying it on a target schema by using configuration action

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(schemaConfiguration: s =>
    {
        s.Link("https://myspecs.dev/myCustomDirective/v1.0", new string[] { "@custom" });
        s.ComposeDirective("@custom");
    })
    // register your types and services
    ;

@interfaceObject usage

Apollo Federation v2 supports entity interfaces, a powerful extension to the GraphQL interfaces that allows you to extend functionality of an interface across the supergraph without having to implement (or even be aware of) all its implementing types.

In a subgraph defininig the interface we need to apply @key

[InterfaceType]
[KeyInterface("id")]
public interface Product
{
    [ID]
    string Id { get; }

    string Name { get; }
}

[Key("id")]
public class Book : Product
{
    [ID]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Content { get; set; }
}

We can then extend the interface in another subgraph by making it a type, applying @interfaceObject and same @key directive. This allows you add new fields to every entity that implements your interface (e.g. adding Reviews field to all Product implementations).

[Key("id")]
[InterfaceObject]
public class Product
{
    [ID]
    public string Id { get; set; }

    public List<string> Reviews { get; set; }
}

Access control through @requiresScopes

The @requiresScopes directive is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes. Refer to the Apollo Router article for additional details.

public class Query
{
    [RequiresScopes(scopes: new string[] { "scope1, scope2", "scope3" })]
    [RequiresScopes(scopes: new string[] { "scope4" })]
    public Product? GetProduct([ID] string id, Data repository)
        => repository.Products.FirstOrDefault(t => t.Id.Equals(id));
}

This will generate the following schema

type Query {
    product(id: ID!): Product @requiresScopes(scopes: [ [ "scope1, scope2", "scope3" ], [ "scope4" ] ])
}

Providing subgraph contact information

You can use the @contact directive to add your team's contact information to a subgraph schema. This information is displayed in Studio, which helps other teams know who to contact for assistance with the subgraph. See documentation for details.

We need to apply [Contact] attribute on a schema. You can either apply [Contact] attribute on a custom schema and pass your custom schema to the AddApolloFederationV2 extension.

[Contact("MyTeamName", "https://myteam.slack.com/archives/teams-chat-room-url", "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)")]
public class CustomSchema : FederatedSchema
{
}

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddType<ContactDirectiveType>();
    .AddApolloFederationV2(new CustomSchema())
    // register your types and services
    ;

or apply @contact directive directly on a schema by providing custom schema configuration action

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2(schemaConfiguration: s =>
    {
        s.Contact("MyTeamName", "https://myteam.slack.com/archives/teams-chat-room-url", "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall)");
    })
    // register your types and services
    ;

Custom Query types

ApolloGraphQL.HotChocolate.Federation automatically defaults to use Query type name. When using custom root Query operation types, you have to explicitly configure schema with those custom values.

public class CustomQuery
{
    public Foo? GetFoo([ID] string id, Data repository)
        => repository.Foos.FirstOrDefault(t => t.Id.Equals(id));
}

var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddGraphQLServer()
    .ModifyOptions(opts => opts.QueryTypeName = "CustomQuery")
    .AddApolloFederationV2()
    .AddQueryType<CustomQuery>()
    // register your other types and services
    ;

var app = builder.Build();
app.MapGraphQL();
app.Run();

Migration Guide

Migrating from HotChocolate.Federation to ApolloGraphQL.HotChocolate.Federation is easy. Simply update your package import to point to a new module

  <ItemGroup>
    <!-- make sure to also include HotChocolate package -->
    <PackageReference Include="HotChocolate.AspNetCore" Version="13.6.0" />
    <!-- federation package -->
-    <PackageReference Include="HotChocolate.ApolloFederation" Version="$LatestVersion" />
+    <PackageReference Include="ApolloGraphQL.HotChocolate.Federation" Version="$LatestVersion" />
  </ItemGroup>

and update namespace imports

- using HotChocolate.ApolloFederation;
+ using ApolloGraphQL.HotChocolate.Federation;

While we tried to make migration process as seamless as possible, we had to make few tweaks to the library. Due to the dependency on some of the internal APIs, we had to make following breaking changes to the library:

  • [Key] is now applicable only on classes and you no longer can apply it on individual fields
  • [ReferenceResolver] is now applicable only on public static methods within an entity, it is no longer applicable on classes

Known Limitations

Entity Resolver Auto-Map Only Scalar Values

[EntityResolver]s can automatically map entity representation to the supported @key/@requires values. Scalars @key fields are automatically mapped and we can use [Map] attribute to auto map scalar values from complex selection sets.

Currently we don't support auto-mapping of List and Object values.

As a workaround, you need to manually parse the representation object in your implementation.

[ReferenceResolver]
public static Foo GetByFooBar(
    [LocalState] ObjectValueNode data
    Data repository)
{
    // TODO implement logic here by manually reading values from local state data
}

Limited @link support

Currently we only support importing elements from the referenced subgraphs.

Namespacing and renaming elements is currently unsupported. See issue for details.

Contact

If you have a specific question about the library or code, please start a discussion in the Apollo community forums or start a conversation on our Discord server.

Contributing

To get started, please fork the repo and checkout a new branch. You can then build the library locally by running

# install dependencies
dotnet restore
# build project
dotnet build
# run tests
dotnet test

See more info in CONTRIBUTING.md.

After you have your local branch set up, take a look at our open issues to see where you can contribute.

Security

For more info on how to contact the team for security issues, see our Security Policy.

License

This library is licensed under The MIT License (MIT).

federation-hotchocolate's People

Contributors

damienpontifex avatar dariuszkuc avatar prasek avatar renovate[bot] avatar svc-secops avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

federation-hotchocolate's Issues

Reference Resolver on Interface not working

I have an interface type in Graph 1 and an interfaceObject in Graph 2 matching each other (let's say interface A). When a query initiated from graph 2 referencing object from graph 1 with reference resolver, graph 1 will throw exception The apollo gateway tries to resolve an entity for which no EntityResolver method was found even though I have defined the reference resolver in the interface type in Graph 1

Updates:
It looks like EntitiesResolver only tries to locate resolver from ObjectType, but by definition here https://www.apollographql.com/docs/federation/federated-types/interfaces#interface-reference-resolver , resolvers can also be defined in interface type
image

@link directive is missing when using hotchocolate authorization

Adding HotChocolate.AspNetCore.Authorization to the schema removes the @link directive from the schema.

How to reproduce

  1. create a graphql schema with federation support
using ApolloGraphQL.HotChocolate.Federation;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2()
    .AddQueryType<Query>();

var app = builder.Build();

app.MapGraphQL("/");
app.RunWithGraphQLCommands(args);

public class Query
{
    public User GetUser() => new("1", "David");
    [ReferenceResolver]
    public User ResolveUser(string id) => new(id, "David");
}

[Key("id")]
public record User(string Id, string Name);
  1. print the schema with dotnet run -- schema export

the schema contains the @link directive

schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject" ]) {
  query: Query
}

type Query {
  user: User!
  resolveUser(id: String!): User!
  _service: _Service!
  _entities(representations: [_Any!]!): [_Entity]!
}
...
  1. add HotChocolate.AspNetCore.Authorization nuget and add authorization to the schema builder:
using ApolloGraphQL.HotChocolate.Federation;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2()
    .AddAuthorization()
    .AddQueryType<Query>();

var app = builder.Build();

app.MapGraphQL("/");
app.RunWithGraphQLCommands(args);

public class Query
{
    public User GetUser() => new("1", "David");
    [ReferenceResolver]
    public User ResolveUser(string id) => new(id, "David");
}

[Key("id")]
public record User(string Id, string Name);
  1. print the schema again with dotnet run -- schema export

Expectation

  • the schema will still define a @link directive

Current Behavior

  • the @link directive is gone :-(
schema {
  query: Query
}

type Query {
  user: User!
  resolveUser(id: String!): User!
  _service: _Service!
  _entities(representations: [_Any!]!): [_Entity]!
}

Unable to use Apollo Federation with GraphQL service

I did only the steps specified in the Installation but still getting Banana Cake Pop. Please Help!!

Screenshots:

image
image

Project File:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="HotChocolate.AspNetCore" Version="13.6.0" />
	  <PackageReference Include="ApolloGraphQL.HotChocolate.Federation" Version="1.3.0" />
  </ItemGroup>

</Project>

bug: Unable to setup server with AddApolloFederationV2

Description of the bug

I'm trying to add federation v2 to my Hotchocolate application and I'm running into the following issue and not sure why it's causing issues.

Unhandled exception. System.InvalidOperationException: No compatible constructor found for input type type `ApolloGraphQL.HotChocolate.Federation.Two.Link`.
Either you have to provide a public constructor with settable properties or a public constructor that allows to pass in values for read-only properties. There was no way to set the following properties: .
   at HotChocolate.Utilities.Serialization.InputObjectConstructorResolver.GetCompatibleConstructor[T](Type type, FieldCollection`1 fields, Dictionary`2 fieldMap, HashSet`1 required)
   at HotChocolate.Utilities.Serialization.InputObjectCompiler.CompileFactory(DirectiveType directiveType, ConstructorInfo constructor)
   at HotChocolate.Types.DirectiveType.OnCompleteCreateInstance(ITypeCompletionContext context, DirectiveTypeDefinition definition)
   at HotChocolate.Types.DirectiveType.OnCompleteType(ITypeCompletionContext context, DirectiveTypeDefinition definition)
   at HotChocolate.Types.TypeSystemObjectBase`1.CompleteType(ITypeCompletionContext context)
   at HotChocolate.Configuration.TypeInitializer.CompleteType(RegisteredType registeredType)
   at HotChocolate.Configuration.TypeInitializer.<CompleteTypes>b__29_0(RegisteredType type)
   at HotChocolate.Configuration.TypeInitializer.ProcessTypes(TypeDependencyFulfilled fulfilled, Func`2 action)
   at HotChocolate.Configuration.TypeInitializer.CompleteTypes()
   at HotChocolate.Configuration.TypeInitializer.Initialize()
   at HotChocolate.SchemaBuilder.Setup.InitializeTypes(SchemaBuilder builder, IDescriptorContext context, IReadOnlyList`1 types)
   at HotChocolate.SchemaBuilder.Setup.Create(SchemaBuilder builder, LazySchema lazySchema, IDescriptorContext context)
   at HotChocolate.SchemaBuilder.Create(IDescriptorContext context)
   at HotChocolate.SchemaBuilder.HotChocolate.ISchemaBuilder.Create(IDescriptorContext context)
   at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaAsync(ConfigurationContext context, RequestExecutorSetup setup, RequestExecutorOptions executorOptions, IServiceProvider schemaServices, TypeModuleChangeMonitor typeModuleChangeMonitor, CancellationToken cancellationToken)

I've tried all possible versions that are available for v2 of federation but none of them work. The only federation version that I get working is v1 of federation.

feat: shareable framework types such as CollectionSegmentInfo

The type CollectionSegmentInfo is defined within HotChocolate for paging responses. Considering that, two subgraphs that use this same type end up with the below error on rover subgraph validation.

INVALID_FIELD_SHARING: Non-shareable field "CollectionSegmentInfo.hasNextPage" is resolved from multiple subgraphs:
INVALID_FIELD_SHARING: Non-shareable field "CollectionSegmentInfo.hasPreviousPage" is resolved from multiple subgraphs: it is resolved from subgraphs "a" and "b" and defined as non-shareable in subgraph "a"

These are troublesome to add decorators to e.g. I've had to do this to add a @tag decorator to them

public class PublicTypeInterceptor : TypeInterceptor
{
    public override void OnBeforeCompleteType(ITypeCompletionContext completionContext, DefinitionBase definition)
    {
        if (definition is not IHasDirectiveDefinition otd) return;
        switch (definition.Name)
        {
            case "ProductCollectionSegment":
            case "CollectionSegmentInfo":
                otd.AddDirective("tag", new[] { new ArgumentNode("name", "public") });
                break;
        }

        base.OnBeforeCompleteType(completionContext, definition);
    }
}

It'd be great to provide some ease of configuration or at least "standards" to be opted in to say so that such things such as @shareable might be added to some of these types to avoid every team having to provide a type interceptor to add these decorators and hence avoid conflicts such as the error above.
The code example of adding the tag decorator might be specific to us, but again, does become something that needs repeating in each subgraph for a pageable type that needs it

n.b. I believe there are other common types e.g. cursor based paging vs token based that might also be included in this general nice to have outcome

feat: full support for `@link` directive

Currently we don't support all features of @link directive.

directive @link(
    url: String!,
    as: String,
    import: [Import],
    for: Purpose)
repeatable on SCHEMA

scalar Import

enum Purpose {
  SECURITY
  EXECUTION
}

Using @link directive we can "import" other definitions into our current schema. If we don't explicitly import them, those types should be namespaced (prefixed with __ which defaults to spec name). Currently we explicitly import ALL federation directives regardless whether they are used within the schema or not. Similarly when importing elements, users can specify custom names within the local schema, e.g. import @key to become @myKey.

We should support following features

  • ability to specify list of imported elements
  • namespace unimported spec elements
  • ability to provide custom namespace (should default to spec name)
  • ability to rename imported elements

bug: Unable to input UUID as argument for an ID field

In ArgumentParser.TryGetValue<T>, the casting of all string value node type is done in the following manner:

value = (T)scalarType.ParseLiteral(valueNode)!;

Refer here.

This causes the following error from the subgraph when the input type for an ID field is UUID:

{
  "errors": [
    {
      "message": "Unexpected Execution Error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "_entities"
      ],
      "extensions": {
        "message": "Unable to cast object of type 'System.String' to type 'System.Guid'.",
        "stackTrace": "   at ApolloGraphQL.HotChocolate.Federation.Helpers.ArgumentParser.TryGetValue[T](IValueNode valueNode, IType type, String[] path, Int32 i, T& value)\n   at ApolloGraphQL.HotChocolate.Federation.Helpers.ArgumentParser.TryGetValue[T](IValueNode valueNode, IType type, String[] path, Int32 i, T& value)\n   at ApolloGraphQL.HotChocolate.Federation.Helpers.ArgumentParser.GetValue[T](IValueNode valueNode, IType type, String[] path)\n   at lambda_method15(Closure, IResolverContext)\n   at ApolloGraphQL.HotChocolate.Federation.Helpers.EntitiesResolver.ResolveAsync(ISchema schema, IReadOnlyList`1 representations, IResolverContext context)\n   at HotChocolate.Types.ResolveObjectFieldDescriptorExtensions.<>c__DisplayClass3_0`1.<<Resolve>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at HotChocolate.Types.Helpers.FieldMiddlewareCompiler.<>c__DisplayClass9_0.<<CreateResolverMiddleware>b__0>d.MoveNext()\n--- End of stack trace from previous location ---\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\n   at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"
      }
    }
  ]
}

HotChocolate v13.7.0+ support

Due to a breaking change to the public API, we cannot support newer versions of HotChocolate until their replacement API (currently work in progress) is complete.

feat: Add IRequestExecutorBuilder extension for adding contact information

It would be nice to have a convenience/extension method for adding contact info instead of the custom schema object as referenced in the README.

Proposal

//
// Summary:
//     Adds contact information and @contact decorator to schema
//
// Parameters:
//   name:
//     Contact title of the subgraph owner
//
//   url:
//     URL where the subgraph's owner can be reached
//
//   description:
//     Other relevant contact notes; supports markdown links
public static IRequestExecutorBuilder AddContact(this IRequestExecutorBuilder builder, string name, string? url = null, string? description = null);

This would make configuration inline with other graphql configuration and avoid needing to define a custom schema type only to add this attribute.

ArgumentParser Index out of bounds exception

I wonder how is this going to work , i is incremented by one at L54 and it looks path always only contain 1 item, it is guaranteed to throw exception if there are more object node to traverse

if (path.Length < ++i && field.Type.IsCompositeType())

I have a entity requesting complex object from another graph, and I get the above exception
image
What I got back in the argument is this
image

How to extend a type from on subgraph into another

What

I'm trying to extend another type using GraphQL federation under using the following syntax:

[Key("id")]
[Extends]
public class Parent
{
    [ID]
    public string Id { get; set; }

    public List<Child> Children()
    {
        return new List<Child>
        {
            new Child("1", "Child 1"),
            new Child("2", "Child 2")
        };
    }
}

And the following registration code:

   .AddTypeExtension<Parent>()

Is what I'm trying to do possible? If so, what am I doing wrong?

Repro

I have created a minimal reproduction of my scenario here:
https://github.com/rquackenbush/apollo-federation-repro

Using two subgraphs with paging via [UsePaging] results in INVALID_FIELD_SHARING

  • Create two or more subgraphs that each use [UsePaging] from Hot Chocolate.
  • Try to compose them, and receive the error:
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.hasNextPage" is resolved from multiple subgraphs: it is resolved from subgraphs "A", "B" and "C" and defined as non-shareable in all of them
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.hasPreviousPage" is resolved from multiple subgraphs: it is resolved from subgraphs "A", "B" and "C" and defined as non-shareable in all of them
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.startCursor" is resolved from multiple subgraphs: it is resolved from subgraphs "A", "B" and "C" and defined as non-shareable in all of them
INVALID_FIELD_SHARING: Non-shareable field "PageInfo.endCursor" is resolved from multiple subgraphs: it is resolved from subgraphs "A", "B" and "C" and defined as non-shareable in all of them

Each of the subgraphs separately define PageInfo, this isn't explcit but is created by Hot Chocolate:

"Information about pagination in a connection."
type PageInfo {
  "Indicates whether more edges exist following the set defined by the clients arguments."
  hasNextPage: Boolean!
  "Indicates whether more edges exist prior the set defined by the clients arguments."
  hasPreviousPage: Boolean!
  "When paginating backwards, the cursor to continue."
  startCursor: String
  "When paginating forwards, the cursor to continue."
  endCursor: String
}

Fed v2 version hardcoded into URL for FederatedSchema

We're getting the following ever from Rover when trying to publish to a variant that supports Federation v2.4:

UNKNOWN_FEDERATION_LINK_VERSION: [directory] Invalid version v2.5 for the federation feature in @link direction on schema

Looks like the Federation version is hardcoded into the URL @ https://github.com/apollographql/federation-hotchocolate/blob/main/src/Federation/Two/FederatedSchema.cs#L10:

/// <summary>
/// Apollo Federation v2 base schema object that allows users to apply custom schema directives (e.g. @composeDirective)
/// </summary>
[Link("https://specs.apollo.dev/federation/v2.5", new string[] {
                "@composeDirective",
                "@extends",
                "@external",
                "@key",
                "@inaccessible",
                "@interfaceObject",
                "@override",
                "@provides",
                "@requires",
                "@shareable",
                "@tag",
                "FieldSet"
})]

Since the schema version is specified per variant in Apollo Studio, can we get this to be an argument to the .AddApolloFederationV2() method? For example, right now we're running Router 1.19.0 which only supports Fed v2.4, so being able to specify something like the below would be helpful:

...

builder.Services
   .AddGraphQLServer()
   .AddApolloFederationV2('2.4')
   ...
...

Thoughts?

bug: entity resolver argument parser fails to unwrap list values

Reference resolver currently fails to map @keys that specify list fields. ArgumentParser is currently missing case for SyntaxKind.ListValue so whenever we have to process representation with list values it is unable to locate the target resolver.

[Key("ids")]
public class Foo
{

    public Foo(List<string> ids, string? name)
    {
        Ids = ids;
        Name = name;   
    }

    public List<string> Ids { get; }
    public string? Name { get; }

    [ReferenceResolver]
    public static Foo? GetFooByIds(
        List<string> ids,
        Data repository)
    {
        // TODO implement logic
       return null;
    }
}

Workaround is available, instead of doing the auto mapping of arguments we process representation manually

    [ReferenceResolver]
    public static Foo? GetFooByIds(
        [LocalState] ObjectValueNode data,
        Data repository)
    {
        // TODO implement logic by manually reading representation from data
       return null;
    }

Related #6

[bug] relax constraint on schemas with no entities

While most federated schemas will contain some entities, it is perfectly valid to have a subgraph without any entities.

We need to relax the existing constraint to allow using subgraphs without entities.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Edited/Blocked

These updates have been manually edited so Renovate will no longer make changes. To discard all commits and start over, click on a checkbox.

  • chore(deps): update actions/checkout action to v4
  • chore(deps): update dotnet monorepo (major) (dotnet-sdk, mcr.microsoft.com/dotnet/aspnet, mcr.microsoft.com/dotnet/sdk)

Vulnerabilities

Renovate has not found any CVEs on osv.dev.

Detected dependencies

docker-compose
compatibility/docker-compose.yaml
dockerfile
compatibility/dockerfile
  • mcr.microsoft.com/dotnet/sdk 7.0
  • mcr.microsoft.com/dotnet/aspnet 7.0
github-actions
.github/workflows/build.yaml
  • actions/checkout v3
  • actions/setup-dotnet v3
.github/workflows/compatibility.yaml
  • actions/checkout v3
  • actions/setup-dotnet v3
  • apollographql/federation-subgraph-compatibility v2
.github/workflows/continuous-integration.yaml
  • release-drafter/release-drafter v5
.github/workflows/release.yaml
  • actions/checkout v3
  • actions/setup-dotnet v3
nuget
Directory.Build.props
  • Microsoft.SourceLink.GitHub 1.1.1
compatibility/Products.csproj
  • HotChocolate.AspNetCore.CommandLine 13.5.1
  • HotChocolate.AspNetCore 13.5.1
global.json
  • dotnet-sdk 6.0.415
src/Federation/ApolloGraphQL.HotChocolate.Federation.csproj
  • HotChocolate 13.5.1

Errors with `rover subgraph publish` due to invalid definition

Subgraph publish errors when using this library:

DIRECTIVE_DEFINITION_INVALID: [shop] Invalid definition for directive "@key": argument "fields" should have type "_FieldSet!" but found type "FieldSet!"
DIRECTIVE_DEFINITION_INVALID: [shop] Invalid definition for directive "@requires": argument "fields" should have type "_FieldSet!" but found type "FieldSet!"
DIRECTIVE_DEFINITION_INVALID: [shop] Invalid definition for directive "@provides": argument "fields" should have type "_FieldSet!" but found type "FieldSet!"

The directives that are written in the schema (whether through /graphql?sdl or rover subgraph introspect) are

directive @allowAnonymous repeatable on FIELD_DEFINITION

"Indicates to composition that the target element is accessible only to the authenticated supergraph users."
directive @authenticated on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM

directive @authorize("The name of the authorization policy that determines access to the annotated resource." policy: String "Roles that are allowed to access the annotated resource." roles: [String!] "Defines when when the authorize directive shall be applied.By default the authorize directives are applied during the validation phase." apply: ApplyPolicy! = BEFORE_RESOLVER) repeatable on OBJECT | FIELD_DEFINITION

"Marks underlying custom directive to be included in the Supergraph schema."
directive @composeDirective(name: String!) on SCHEMA

"Provides contact information of the owner responsible for this subgraph schema."
directive @contact(name: String! url: String description: String) on SCHEMA

"Directive to indicate that marks target object as extending part of the federated schema."
directive @extends on OBJECT | INTERFACE

"Directive to indicate that a field is owned by another service, for example via Apollo federation."
directive @external on OBJECT | FIELD_DEFINITION

"Marks location within schema as inaccessible from the GraphQL Gateway"
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

"Provides meta information to the router that this entity type is an interface in the supergraph."
directive @interfaceObject on OBJECT

"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE

directive @link(url: String! import: [String]) repeatable on SCHEMA

"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
directive @override(from: String!) on FIELD_DEFINITION

"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
directive @provides(fields: FieldSet!) on FIELD_DEFINITION

"Used to annotate the required input fieldset from a base type for a resolver."
directive @requires(fields: FieldSet!) on FIELD_DEFINITION

"Indicates to composition that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes."
directive @requiresScopes(scopes: [[Scope!]!]!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM

"Indicates that given object and\/or field can be resolved by multiple subgraphs."
directive @shareable repeatable on OBJECT | FIELD_DEFINITION

"Allows users to annotate fields and types with additional metadata information."
directive @tag(name: String!) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

"The built-in `Decimal` scalar type."
scalar Decimal

"Scalar representing a set of fields."
scalar FieldSet

"Scalar representing a JWT scope"
scalar Scope

"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any

Versions used

ApolloGraphQL.HotChocolate.Federation: 1.2.1
HotChocolate.* 13.6.1

Setup code

builder.Services.AddGraphQLServer()
    .AddApolloFederationV2(schemaConfiguration: schema =>
    {
        schema.Contact(
            name: "<value>",
            url: "<value>",
            description: "<value>"
        );
    })
    .AddDirectiveType<ContactDirectiveType>()
    .AddQueryType<Query>()
    .AddAuthorization()
    .AddInstrumentation(o =>
    {
        o.IncludeDocument = true;
        o.RenameRootActivity = true;
    })
    .InitializeOnStartup();

bug: entity resolver argument parser fails to map to objects

Argument parser currently is only able to map scalar values. Attempting to use an object as an argument in [EntityResolver] leads to runtime errors.

[Key("bar { baz }")]
public class Foo
{

    public Foo(Bar bar, string? name)
    {
        Bar = bar;
        Name = name;   
    }

    public Bar bar { get; }
    public string? Name { get; }

    [ReferenceResolver]
    public static Foo? GetFooByBarBaz{
        Bar bar,
        Data repository)
    {
        // TODO implement logic
       return null;
    }
}

public class Bar {

    public Bar(string baz)
    {
        Baz = baz;
    }

    public Baz baz { get; }
}

Following workarounds are available

  1. map scalar arguments instead
    [ReferenceResolver]
    public static Foo? GetFooByBarBaz(
        [Map("bar.baz")] string baz
        Data repository)
    {
        // TODO implement logic
       return null;
    }
  1. instead of doing the auto mapping of arguments we process representation manually
    [ReferenceResolver]
    public static Foo? GetFooByBarBaz(
        [LocalState] ObjectValueNode data,
        Data repository)
    {
        // TODO implement logic by manually reading representation from data
       return null;
    }

Related #6

Deprecation of this package?

Federation V2 is now fully supported by the official HotChocolate Federation package. Does this mean this package should be deprecated and no longer be recommeded? It seems like this is no longer maintained and only the official version support the latest HotChocolate version.

It seems very misleading seeing the recommendation in this package unless I misunderstood what's going on. Can you give some clarifications on the future of this library?

Thank you

bug: map `@key` resolver logic fails to unwrap the entity representation

Reference resolver currently fails to map nested selection set representation.

Failing case (link)

[Key("id")]
[Key("sku variation { id }")]
public class Product
{
    public Product(string id, string? sku, string? package, ProductVariation? variation)
    {
        Id = id;
        Sku = sku;
        Package = package;
        Variation = variation;
    }

    [ID]
    public string Id { get; }

    public string? Sku { get; }

    public string? Package { get; }

    public ProductVariation? Variation { get; }

    [ReferenceResolver]
    public static Product? GetProductById(
        string id,
        Data repository)
        => repository.Products.FirstOrDefault(t => t.Id.Equals(id));

    // THIS RESOLVER FAILS
    [ReferenceResolver]
    public static Product? GetProductByVariation(
        string sku,
        [Map("variation.id")] string variationId,
        Data repository)
        => repository.Products.FirstOrDefault(
            t => (t.Sku?.Equals(sku) ?? false) &&
                (t.Variation?.Id.Equals(variationId) ?? false));
}

@key directive not emitted in SDL

I noticed that the SDL doesn't include the @key directive on classes that are adorned with the KeyAttribute. This occurs for both the ChilliCream and Apollo GraphQL implementations. What am I missing here?

Emitted SDL

directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE

scalar _Any

union _Entity = Product

type _Service {
  sdl: String!
}

scalar _FieldSet

type Query {
  book: Book!
  _service: _Service!
  _entities(representations: [_Any!]!): [_Entity]!
}

type Product {
  id: ID!
  name: String!
  price: Float!
}

type Book {
  title: String!
  author: Author!
}

type Author {
  name: String!
}

Code


using ApolloGraphQL.HotChocolate.Federation;

namespace HotChocolate.Apollo.Sample;

public class Book
{
    public string Title { get; set; }

    public Author Author { get; set; }
}

public class Author
{
    public string Name { get; set; }
}

[Key("id")]
public class Product
{
    [ID]
    public string Id { get; set; }

    public string Name { get; set; }

    public float Price { get; set; }

    [ReferenceResolver]
    public static Product? ResolveReference(
        // Represents the value that would be in the Id property of a Product
        string id
    )
    {
        return new Product()
        {
            Id = id,
            Name = $"Name of {id}",
            Price = 1f
        };
    }
}


public class Query
{
    public Book GetBook() =>
        new Book
        {
            Title = "C# in depth.",
            Author = new Author
            {
                Name = "Jon Skeet"
            }
        };
}

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddGraphQLServer()
    .AddApolloFederationV2()
    .AddQueryType<Query>()
    .AddType<Product>();


var app = builder.Build();

app.MapGraphQL();

app.Run();

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

circleci
.circleci/config.yml
  • secops 2.0.6
docker-compose
Compatibility/AnnotationBased/docker-compose.yaml
Compatibility/CodeFirst/docker-compose.yaml
dockerfile
Compatibility/AnnotationBased/Dockerfile
  • docker/dockerfile 1
  • mcr.microsoft.com/dotnet/sdk 7.0
  • mcr.microsoft.com/dotnet/aspnet 7.0
Compatibility/CodeFirst/Dockerfile
  • docker/dockerfile 1
  • mcr.microsoft.com/dotnet/sdk 7.0
  • mcr.microsoft.com/dotnet/aspnet 7.0
github-actions
.github/workflows/build.yaml
  • actions/checkout v4
  • actions/setup-dotnet v4
.github/workflows/compatibility.yaml
  • actions/checkout v4
  • actions/setup-dotnet v4
  • apollographql/federation-subgraph-compatibility v2
  • actions/checkout v4
  • actions/setup-dotnet v4
  • apollographql/federation-subgraph-compatibility v2
.github/workflows/continuous-integration.yaml
  • release-drafter/release-drafter v5
.github/workflows/release.yaml
  • actions/checkout v4
  • actions/setup-dotnet v4
nuget
Compatibility/AnnotationBased/AnnotationBased.csproj
  • HotChocolate.AspNetCore.CommandLine 13.6.1
  • HotChocolate.AspNetCore 13.6.1
Compatibility/CodeFirst/CodeFirst.csproj
  • HotChocolate.AspNetCore.CommandLine 13.6.1
  • HotChocolate.AspNetCore 13.6.1
Directory.Build.props
  • Microsoft.SourceLink.GitHub 8.0.0
Federation.Tests/ApolloGraphQL.HotChocolate.Federation.Tests.csproj
  • HotChocolate.AspNetCore 13.6.1
  • ChilliCream.Testing.Utilities 0.2.0
  • Snapshooter.Xunit 0.13.0
  • Moq 4.20.70
  • xunit.runner.visualstudio 2.5.5
  • xunit 2.6.3
  • coverlet.msbuild 6.0.0
  • Microsoft.NET.Test.Sdk 17.8.0
Federation/ApolloGraphQL.HotChocolate.Federation.csproj
  • Microsoft.SourceLink.GitHub 8.0.0
  • HotChocolate 13.6.1
global.json
  • dotnet-sdk 6.0.417

  • Check this box to trigger a request for Renovate to run again on this repository

Schema linting errors when using this package

I'm shifting over to use federation and directives with this package, but now getting linting errors (from Apollo Studio) within our pipeline. I believe they shouldn't exist and hence the issue here

I've bumped HotChocolate libraries from 13.5.0 to 13.6.1 and using 1.2.1 of this apollo package.

The schema file is generated by running rover subgraph introspect "http://localhost:5000/graphql" > "my-schema.graphql"

And then we do rover subgraph check <graph-ref>@<variant> --name my-service --schema my-schema.graphql

┌─────────┬─────────────────┬──────┬──────────────────────────────────────────────────────────┐
│  Level  │   Coordinate    │ Line │                       Description                        │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ Query._entities │ 29   │ Field names should use camelCase style.                  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ Query._service  │ 28   │ Field names should use camelCase style.                  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ ERROR   │ _Any            │ 167  │ Type names should use PascalCase style.                  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ ERROR   │ _Entity         │ 95   │ Type names should use PascalCase style.                  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ ERROR   │ _Service        │ 90   │ Type names should use PascalCase style.                  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ @link           │ 131  │ Schema element @link is missing a description.           │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ Query._entities │ 29   │ Schema element Query._entities is missing a description. │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ Query._service  │ 28   │ Schema element Query._service is missing a description.  │
├─────────┼─────────────────┼──────┼──────────────────────────────────────────────────────────┤
│ WARNING │ _Service.sdl    │ 91   │ Schema element _Service.sdl is missing a description.    │
└─────────┴─────────────────┴──────┴──────────────────────────────────────────────────────────┘

Is this an issue in the schema rover is introspecting/generating? Or something about this package addition (reason I ask this as it wasn't failing prior when just utilising HotChocolate 13.5)?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.