├── Apollo.png ├── examples ├── CodeFirst │ ├── Accounts │ │ ├── appsettings.json │ │ ├── Data │ │ │ ├── User.cs │ │ │ └── UserRepository.cs │ │ ├── Types │ │ │ ├── QueryType.cs │ │ │ └── UserType.cs │ │ ├── Accounts.csproj │ │ ├── .vscode │ │ │ └── tasks.json │ │ └── Program.cs │ ├── Inventory │ │ ├── appsettings.json │ │ ├── Program.cs │ │ ├── Data │ │ │ └── Product.cs │ │ ├── Inventory.csproj │ │ ├── .vscode │ │ │ └── tasks.json │ │ └── Types │ │ │ └── ProductType.cs │ ├── Products │ │ ├── appsettings.json │ │ ├── Program.cs │ │ ├── Data │ │ │ ├── Product.cs │ │ │ └── ProductRepository.cs │ │ ├── Types │ │ │ ├── ProductType.cs │ │ │ └── QueryType.cs │ │ ├── Products.csproj │ │ └── .vscode │ │ │ └── tasks.json │ └── Reviews │ │ ├── appsettings.json │ │ ├── Data │ │ ├── Product.cs │ │ ├── User.cs │ │ ├── Review.cs │ │ ├── UserRepository.cs │ │ └── ReviewRepository.cs │ │ ├── Program.cs │ │ ├── Reviews.csproj │ │ ├── Types │ │ ├── ReviewType.cs │ │ ├── ProductType.cs │ │ └── UserType.cs │ │ └── .vscode │ │ └── tasks.json ├── AnnotationBased │ ├── Accounts │ │ ├── appsettings.json │ │ ├── Query.cs │ │ ├── Program.cs │ │ ├── UserRepository.cs │ │ ├── User.cs │ │ └── Accounts.csproj │ ├── Products │ │ ├── appsettings.json │ │ ├── Query.cs │ │ ├── Program.cs │ │ ├── Products.csproj │ │ ├── Product.cs │ │ └── ProductRepository.cs │ ├── Reviews │ │ ├── appsettings.json │ │ ├── Program.cs │ │ ├── UserRepository.cs │ │ ├── Product.cs │ │ ├── Reviews.csproj │ │ ├── Review.cs │ │ ├── User.cs │ │ └── ReviewRepository.cs │ └── Inventory │ │ ├── appsettings.json │ │ ├── Program.cs │ │ ├── Inventory.csproj │ │ └── Product.cs ├── supergraph.yaml └── README.md ├── global.json ├── CODEOWNERS ├── Federation.Tests ├── __snapshots__ │ ├── ServiceTypeTests.TestServiceTypeEmptyQueryTypePureCodeFirst.snap │ ├── ServiceTypeTests.TestServiceTypeEmptyQueryTypeSchemaFirst.snap │ ├── ServiceTypeTests.TestServiceTypeTypeSchemaFirst.snap │ ├── ServiceTypeTests.TestServiceTypeTypePureCodeFirst.snap │ ├── FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesSchemaFirst.snap │ ├── FederationSchemaPrinterTests.TestFederationPrinterTypeExtensionPureCodeFirst.snap │ ├── FederationSchemaPrinterTests.CustomDirective.snap │ ├── FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesPureCodeFirst.snap │ ├── FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst.snap │ └── FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst_With_DateTime.snap ├── CertificationSchema │ ├── CodeFirst │ │ ├── __snapshots__ │ │ │ ├── CertificationTests.Product_By_Id.snap │ │ │ ├── CertificationTests.Product_By_Package.snap │ │ │ ├── CertificationTests.Product_By_Variation.snap │ │ │ ├── CertificationTests.Requires.snap │ │ │ ├── CertificationTests.Provides.snap │ │ │ └── CertificationTests.Subgraph_SDL.snap │ │ ├── Types │ │ │ ├── ProductVariation.cs │ │ │ ├── ProductDimension.cs │ │ │ ├── Data.cs │ │ │ ├── User.cs │ │ │ ├── Product.cs │ │ │ ├── UserType.cs │ │ │ ├── QueryType.cs │ │ │ └── ProductType.cs │ │ └── SchemaSetup.cs │ └── AnnotationBased │ │ ├── __snapshots__ │ │ ├── CertificationTests.Product_By_Id.snap │ │ ├── CertificationTests.Product_By_Package.snap │ │ ├── CertificationTests.Product_By_Variation.snap │ │ ├── CertificationTests.Requires.snap │ │ ├── CertificationTests.Provides.snap │ │ └── CertificationTests.Subgraph_SDL.snap │ │ ├── Types │ │ ├── ProductVariation.cs │ │ ├── Query.cs │ │ ├── ProductDimension.cs │ │ ├── Data.cs │ │ ├── User.cs │ │ └── Product.cs │ │ └── SchemaSetup.cs ├── One │ └── __snapshots__ │ │ ├── FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesSchemaFirst.snap │ │ ├── FederationSchemaPrinterTests.TestFederationPrinterTypeExtensionPureCodeFirst.snap │ │ ├── FederationSchemaPrinterTests.CustomDirective.snap │ │ ├── FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesPureCodeFirst.snap │ │ ├── FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst.snap │ │ └── FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst_With_DateTime.snap ├── Directives │ └── __snapshots__ │ │ ├── ExternalDirectiveTests.AnnotateExternalToTypeFieldCodeFirst.snap │ │ ├── ExternalDirectiveTests.AnnotateExternalToTypeFieldSchemaFirst.snap │ │ ├── KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap │ │ ├── KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap │ │ ├── RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap │ │ ├── RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap │ │ ├── ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap │ │ ├── ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap │ │ ├── ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap │ │ ├── KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap │ │ ├── KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap │ │ ├── ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap │ │ ├── RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap │ │ └── KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap ├── ManualBatchScheduler.cs ├── TestHelper.cs ├── ApolloGraphQL.HotChocolate.Federation.Tests.csproj └── FederationTypesTestBase.cs ├── Compatibility ├── CodeFirst │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── docker-compose.yaml │ ├── Types │ │ ├── CustomDirectiveType.cs │ │ ├── ProductVariation.cs │ │ ├── CaseStudy.cs │ │ ├── ProductDimension.cs │ │ ├── Query.cs │ │ ├── Inventory.cs │ │ ├── DeprecatedProduct.cs │ │ ├── ProductResearch.cs │ │ └── Data.cs │ ├── Dockerfile │ ├── CodeFirst.csproj │ ├── Program.cs │ └── README.md └── AnnotationBased │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── docker-compose.yaml │ ├── Types │ ├── ProductVariation.cs │ ├── CustomSchema.cs │ ├── CaseStudy.cs │ ├── CustomAttribute.cs │ ├── CustomDirectiveType.cs │ ├── Query.cs │ ├── ProductDimension.cs │ ├── Inventory.cs │ ├── ProductResearch.cs │ ├── DeprecatedProduct.cs │ ├── Data.cs │ ├── Product.cs │ └── User.cs │ ├── Program.cs │ ├── Dockerfile │ ├── AnnotationBased.csproj │ └── README.md ├── .gitignore ├── Federation ├── Constants │ ├── WellKnownFieldNames.cs │ ├── WellKnownArgumentNames.cs │ ├── WellKnownContextData.cs │ └── WellKnownTypeNames.cs ├── Two │ ├── FederationVersion.cs │ ├── Scope.cs │ ├── TagValue.cs │ ├── RequiresScopes.cs │ ├── Link.cs │ ├── ExtendsDirectiveType.cs │ ├── InterfaceObjectDirectiveType.cs │ ├── Contact.cs │ ├── OverrideDirectiveType.cs │ ├── ComposeDirectiveType.cs │ ├── RequiresScopesDirectiveType.cs │ ├── AuthenticatedDirectiveType.cs │ ├── RequiresDirectiveType.cs │ ├── ScopeType.cs │ ├── ExternalDirectiveType.cs │ ├── ShareableDirectiveType.cs │ ├── ContactDirectiveType.cs │ ├── ProvidesDirectiveType.cs │ ├── LinkDirectiveType.cs │ ├── TagDirectiveType.cs │ ├── KeyDirectiveType.cs │ ├── FederatedSchema.cs │ └── InaccessibleDirectiveType.cs ├── SchemaTypeDescriptorAttribute.cs ├── Descriptors │ ├── EntityResolverDefinition.cs │ ├── ReferenceResolverDefinition.cs │ └── IEntityResolverDescriptor.cs ├── EntityType.cs ├── ExtendServiceTypeAttribute.cs ├── ReferenceResolverAttribute.cs ├── MapAttribute.cs ├── ExtendsAttribute.cs ├── Extensions │ ├── DirectiveTypeDescriptorExtensions.cs │ ├── ApolloFederationRequestExecutorBuilderExtensions.cs │ └── ApolloFederationSchemaBuilderExtensions.cs ├── InterfaceObjectAttribute.cs ├── One │ ├── ExtendsDirectiveType.cs │ ├── KeyDirectiveType.cs │ ├── FederationSchemaPrinter.InputType.cs │ ├── FederationSchemaPrinter.DirectiveType.cs │ ├── ExternalDirectiveType.cs │ ├── RequiresDirectiveType.cs │ └── ProvidesDirectiveType.cs ├── ApolloAuthenticatedAttribute.cs ├── Representation.cs ├── ShareableAttribute.cs ├── ExternalAttribute.cs ├── OverrideAttribute.cs ├── RequiresScopesAttribute.cs ├── ApolloTagAttribute.cs ├── ServiceType.cs ├── InaccessibleAttribute.cs ├── ApolloGraphQL.HotChocolate.Federation.csproj ├── Helpers │ └── ReferenceResolverHelper.cs ├── ComposeDirectiveAttribute.cs └── NonResolvableKeyAttribute.cs ├── .github ├── release-drafter.yml └── workflows │ ├── pr-check.yaml │ ├── build.yaml │ ├── continuous-integration.yaml │ ├── release.yaml │ └── compatibility.yaml ├── renovate.json ├── .circleci └── config.yml ├── LICENSE └── Directory.Build.props /Apollo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/federation-hotchocolate/HEAD/Apollo.png -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5001" 3 | } 4 | -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5004" 3 | } 4 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5003" 3 | } 4 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5002" 3 | } 4 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5001" 3 | } 4 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5003" 3 | } 4 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5002" 3 | } 4 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Inventory/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": "http://localhost:5004" 3 | } 4 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.417", 4 | "rollForward": "latestMajor" 5 | } 6 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the Apollo SecOps team 2 | # Please customize this file as needed prior to merging. 3 | 4 | * @apollographql/atlas 5 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/ServiceTypeTests.TestServiceTypeEmptyQueryTypePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | type Address @key(fields: "matchCode") { 2 | matchCode: String 3 | } 4 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/ServiceTypeTests.TestServiceTypeEmptyQueryTypeSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Address @key(fields: "matchCode") { 2 | matchCode: String! 3 | } 4 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/ServiceTypeTests.TestServiceTypeTypeSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Address @key(fields: "matchCode") { 2 | matchCode: String! 3 | } 4 | 5 | type Query { 6 | address: Address! 7 | } 8 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/ServiceTypeTests.TestServiceTypeTypePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | type Address @key(fields: "matchCode") { 2 | matchCode: String 3 | } 4 | 5 | type Query { 6 | address(id: Int!): Address! 7 | } 8 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/Query.cs: -------------------------------------------------------------------------------- 1 | namespace Accounts; 2 | 3 | public class Query 4 | { 5 | public Task Me(UserRepository userRepository) 6 | => userRepository.GetUserByIdAsync("1"); 7 | } 8 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Product_By_Id.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Product_By_Package.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Data/Product.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class Product 4 | { 5 | public Product(string upc) 6 | { 7 | Upc = upc; 8 | } 9 | 10 | public string Upc { get; } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | out 4 | 5 | # compatibility stuff 6 | products.graphql 7 | package.json 8 | package-lock.json 9 | results.md 10 | supergraph-compose.yaml 11 | supergraph-config.yaml 12 | supergraph.graphql 13 | node_modules -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Product_By_Id.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Product_By_Variation.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Product_By_Package.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Product_By_Variation.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "_entities": [ 4 | { 5 | "sku": "federation" 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Requires.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "product": { 4 | "dimensions": { 5 | "size": "1", 6 | "weight": 1 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Query { 2 | someField(a: Int): TestType 3 | } 4 | 5 | type TestType @key(fields: "id") { 6 | id: Int! 7 | name: String! 8 | } 9 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterTypeExtensionPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | extend type Product @key(fields: "upc") { 2 | upc: String! 3 | } 4 | 5 | type QueryRootOfProduct { 6 | entity(id: Int!): Product! 7 | } 8 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | products: 3 | # must be relative to the root of the project 4 | build: 5 | context: . 6 | dockerfile: ./Compatibility/CodeFirst/Dockerfile 7 | ports: 8 | - 4001:4001 9 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Query { 2 | someField(a: Int): TestType 3 | } 4 | 5 | type TestType @key(fields: "id") { 6 | id: Int! 7 | name: String! 8 | } 9 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterTypeExtensionPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | extend type Product @key(fields: "upc") { 2 | upc: String! 3 | } 4 | 5 | type QueryRootOfProduct { 6 | entity(id: Int!): Product! 7 | } 8 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Requires.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "product": { 4 | "dimensions": { 5 | "size": "1", 6 | "weight": 1 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | products: 3 | # must be relative to the root of the project 4 | build: 5 | context: . 6 | dockerfile: ./Compatibility/AnnotationBased/Dockerfile 7 | ports: 8 | - 4001:4001 9 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/ProductVariation.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class ProductVariation 4 | { 5 | public ProductVariation(string id) 6 | { 7 | Id = id; 8 | } 9 | 10 | [ID] 11 | public string Id { get; } 12 | } 13 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/Query.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Query 4 | { 5 | public IEnumerable GetTopProducts( 6 | ProductRepository productRepository, 7 | int? first) 8 | => productRepository.GetTop(first ?? 5); 9 | } 10 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Provides.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "product": { 4 | "createdBy": { 5 | "email": "support@apollographql.com", 6 | "totalProductsCreated": 1337 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Provides.snap: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "product": { 4 | "createdBy": { 5 | "email": "support@apollographql.com", 6 | "totalProductsCreated": 1337 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Federation/Constants/WellKnownFieldNames.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Constants; 2 | 3 | internal static class WellKnownFieldNames 4 | { 5 | public const string Service = "_service"; 6 | public const string Entities = "_entities"; 7 | public const string Sdl = "sdl"; 8 | } 9 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Inventory/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddGraphQLServer() 5 | .AddApolloFederation() 6 | .AddQueryType() 7 | .AddType(); 8 | 9 | var app = builder.Build(); 10 | app.MapGraphQL(); 11 | app.Run(); 12 | -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddGraphQLServer() 5 | .AddApolloFederation() 6 | .AddQueryType() 7 | .AddType(); 8 | 9 | var app = builder.Build(); 10 | app.MapGraphQL(); 11 | app.Run(); 12 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Data/User.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class User 4 | { 5 | public User(string id, string username) 6 | { 7 | Id = id; 8 | Username = username; 9 | } 10 | 11 | public string Id { get; } 12 | 13 | public string Username { get; } 14 | } 15 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/CustomSchema.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | using ApolloGraphQL.HotChocolate.Federation.Two; 3 | 4 | namespace Products; 5 | 6 | [ComposeDirective("@custom")] 7 | [Link("https://myspecs.dev/myCustomDirective/v1.0", new string[] { "@custom" })] 8 | public class CustomSchema : FederatedSchema 9 | { 10 | 11 | } -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Query { 6 | field(a: String): String @external 7 | } 8 | 9 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 10 | directive @external on FIELD_DEFINITION 11 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Query { 6 | field(a: Int): String @external 7 | } 8 | 9 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 10 | directive @external on FIELD_DEFINITION 11 | -------------------------------------------------------------------------------- /Federation/Two/FederationVersion.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 2 | 3 | /// 4 | /// Enum defining all supported Apollo Federation v2 versions. 5 | /// 6 | public enum FederationVersion 7 | { 8 | FEDERATION_20, 9 | FEDERATION_21, 10 | FEDERATION_22, 11 | FEDERATION_23, 12 | FEDERATION_24, 13 | FEDERATION_25, 14 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/Data/Product.cs: -------------------------------------------------------------------------------- 1 | namespace Inventory; 2 | 3 | public class Product 4 | { 5 | public Product(string upc) 6 | { 7 | Upc = upc; 8 | } 9 | 10 | public string Upc { get; } 11 | 12 | public int Weight { get; private set; } 13 | 14 | public int Price { get; private set; } 15 | 16 | public bool InStock => true; 17 | } 18 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/CaseStudy.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class CaseStudy 4 | { 5 | 6 | public CaseStudy(string caseNumber, string? description) 7 | { 8 | CaseNumber = caseNumber; 9 | Description = description; 10 | } 11 | 12 | [ID] 13 | public string CaseNumber { get; } 14 | public string? Description { get; } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/ProductVariation.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Relay; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 4 | 5 | public class ProductVariation 6 | { 7 | public ProductVariation(string id) 8 | { 9 | Id = id; 10 | } 11 | 12 | [ID] 13 | public string Id { get; } 14 | } 15 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.CustomDirective.snap: -------------------------------------------------------------------------------- 1 | enum EnumWithDeprecatedValue { 2 | DEPRECATED1 @deprecated 3 | DEPRECATED2 @deprecated 4 | ACTIVE 5 | } 6 | 7 | type Query { 8 | foo: String 9 | deprecated1: EnumWithDeprecatedValue @deprecated(reason: "deprecated") 10 | deprecated2: EnumWithDeprecatedValue @deprecated(reason: "deprecated") 11 | } 12 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Data/User.cs: -------------------------------------------------------------------------------- 1 | namespace Accounts; 2 | 3 | public class User 4 | { 5 | public User(string id, string name, string username) 6 | { 7 | Id = id; 8 | Name = name; 9 | Username = username; 10 | } 11 | 12 | public string Id { get; } 13 | 14 | public string Name { get; } 15 | 16 | public string Username { get; } 17 | } 18 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.CustomDirective.snap: -------------------------------------------------------------------------------- 1 | enum EnumWithDeprecatedValue { 2 | DEPRECATED1 @deprecated 3 | DEPRECATED2 @deprecated 4 | ACTIVE 5 | } 6 | 7 | type Query { 8 | foo: String 9 | deprecated1: EnumWithDeprecatedValue @deprecated(reason: "deprecated") 10 | deprecated2: EnumWithDeprecatedValue @deprecated(reason: "deprecated") 11 | } 12 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederation() 9 | .AddQueryType() 10 | .RegisterService(); 11 | 12 | var app = builder.Build(); 13 | app.MapGraphQL(); 14 | app.Run(); 15 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/ProductVariation.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Relay; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 4 | 5 | public class ProductVariation 6 | { 7 | public ProductVariation(string id) 8 | { 9 | Id = id; 10 | } 11 | 12 | [ID] 13 | public string Id { get; } 14 | } 15 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederation() 9 | .AddQueryType() 10 | .RegisterService(); 11 | 12 | var app = builder.Build(); 13 | app.MapGraphQL(); 14 | app.Run(); 15 | -------------------------------------------------------------------------------- /examples/supergraph.yaml: -------------------------------------------------------------------------------- 1 | federation_version: =2.5.5 2 | subgraphs: 3 | accounts: 4 | schema: 5 | subgraph_url: http://localhost:5001/graphql 6 | inventory: 7 | schema: 8 | subgraph_url: http://localhost:5002/graphql 9 | products: 10 | schema: 11 | subgraph_url: http://localhost:5003/graphql 12 | reviews: 13 | schema: 14 | subgraph_url: http://localhost:5004/graphql -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/CustomAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | 3 | namespace Products; 4 | 5 | public sealed class CustomAttribute : ObjectTypeDescriptorAttribute 6 | { 7 | protected override void OnConfigure(IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) 8 | { 9 | descriptor.Directive(CustomDirectiveType.CustomDirectiveName); 10 | } 11 | } -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/CustomDirectiveType.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public sealed class CustomDirectiveType : DirectiveType 4 | { 5 | public const string CustomDirectiveName = "custom"; 6 | 7 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 8 | => descriptor 9 | .Name(CustomDirectiveName) 10 | .Location(DirectiveLocation.Object); 11 | } -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/CustomDirectiveType.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public sealed class CustomDirectiveType : DirectiveType 4 | { 5 | public const string CustomDirectiveName = "custom"; 6 | 7 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 8 | => descriptor 9 | .Name(CustomDirectiveName) 10 | .Location(DirectiveLocation.Object); 11 | } 12 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/Query.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HotChocolate.Types.Relay; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 5 | 6 | [ExtendServiceType] 7 | public class Query 8 | { 9 | public Product? GetProduct([ID] string id, Data repository) 10 | => repository.Products.FirstOrDefault(t => t.Id.Equals(id)); 11 | } 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: v$NEXT_PATCH_VERSION 2 | tag-template: v$NEXT_PATCH_VERSION 3 | change-template: '- $TITLE (#$NUMBER) @$AUTHOR' 4 | categories: 5 | - title: Major Changes 6 | label: "change: major" 7 | 8 | - title: Minor Changes 9 | label: "change: minor" 10 | 11 | - title: Patch Changes 12 | label: "change: patch" 13 | 14 | - title: Other Changes 15 | 16 | template: | 17 | $CHANGES -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/ProductDimension.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 2 | 3 | public class ProductDimension 4 | { 5 | public ProductDimension(string size, double weight) 6 | { 7 | Size = size; 8 | Weight = weight; 9 | } 10 | 11 | public string? Size { get; } 12 | 13 | public double? Weight { get; } 14 | } 15 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | type Address { 2 | zipcode: String! @external 3 | } 4 | 5 | type QueryRootOfUser { 6 | entity(id: Int!): User! 7 | } 8 | 9 | type User @key(fields: "id") { 10 | id: Int! 11 | idCode: String! @external 12 | idCodeShort: String! @requires(fields: "idCode") 13 | address: Address! @provides(fields: "zipcode") 14 | } 15 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/ProductDimension.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 2 | 3 | public class ProductDimension 4 | { 5 | public ProductDimension(string size, double weight) 6 | { 7 | Size = size; 8 | Weight = weight; 9 | } 10 | 11 | public string? Size { get; } 12 | 13 | public double? Weight { get; } 14 | } 15 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterApolloDirectivesPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | type Address { 2 | zipcode: String! @external 3 | } 4 | 5 | type QueryRootOfUser { 6 | entity(id: Int!): User! 7 | } 8 | 9 | type User @key(fields: "id") { 10 | id: Int! 11 | idCode: String! @external 12 | idCodeShort: String! @requires(fields: "idCode") 13 | address: Address! @provides(fields: "zipcode") 14 | } 15 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/Data.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 4 | 5 | public class Data 6 | { 7 | public List Products { get; } = new() 8 | { 9 | new("apollo-federation", "federation", "@apollo/federation", "OSS"), 10 | new("apollo-studio", "studio", string.Empty, "platform") 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request Check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '*.md' 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build.yaml 13 | 14 | integration: 15 | needs: build 16 | permissions: 17 | pull-requests: write 18 | uses: ./.github/workflows/compatibility.yaml 19 | secrets: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/Program.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | builder.Services 6 | .AddSingleton(); 7 | 8 | builder.Services 9 | .AddGraphQLServer() 10 | .AddApolloFederation() 11 | .AddQueryType() 12 | .RegisterService(); 13 | 14 | var app = builder.Build(); 15 | app.MapGraphQL(); 16 | app.Run(); 17 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/Data.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 4 | 5 | public class Data 6 | { 7 | public List Products { get; } = new() 8 | { 9 | new("apollo-federation", "federation", "@apollo/federation", "OSS"), 10 | new("apollo-studio", "studio", string.Empty, "platform") 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Data/Product.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Product 4 | { 5 | public Product(string upc, string name, int price, int weight) 6 | { 7 | Upc = upc; 8 | Name = name; 9 | Price = price; 10 | Weight = weight; 11 | } 12 | 13 | public string Upc { get; } 14 | 15 | public string Name { get; } 16 | 17 | public int Price { get; } 18 | 19 | public int Weight { get; } 20 | } 21 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Types/QueryType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Accounts; 4 | 5 | public class QueryType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .Name("Query") 11 | .Field("me") 12 | .Type>() 13 | .Resolve(ctx => ctx.Service().GetUserByIdAsync("1")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederationV2(new CustomSchema()) 9 | .AddType() 10 | .AddType() 11 | .AddQueryType() 12 | .RegisterService(); 13 | 14 | var app = builder.Build(); 15 | 16 | app.MapGraphQL("/"); 17 | app.RunWithGraphQLCommands(args); 18 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Data/Review.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class Review 4 | { 5 | public Review(string id, string body, string authorId, string upc) 6 | { 7 | Id = id; 8 | Body = body; 9 | AuthorId = authorId; 10 | Product = new Product(upc); 11 | } 12 | 13 | public string Id { get; } 14 | 15 | public string Body { get; } 16 | 17 | public string AuthorId { get; } 18 | 19 | public Product Product { get; } 20 | } 21 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services.AddSingleton(); 4 | builder.Services.AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederation() 9 | .AddQueryType() 10 | .AddType() 11 | .RegisterService() 12 | .RegisterService(); 13 | 14 | var app = builder.Build(); 15 | app.MapGraphQL(); 16 | app.Run(); 17 | -------------------------------------------------------------------------------- /Federation/Constants/WellKnownArgumentNames.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Constants; 2 | 3 | internal static class WellKnownArgumentNames 4 | { 5 | public const string Fields = "fields"; 6 | public const string From = "from"; 7 | public const string Name = "name"; 8 | public const string Resolvable = "resolvable"; 9 | public const string Representations = "representations"; 10 | public const string Scopes = "scopes"; 11 | public const string Url = "url"; 12 | } 13 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "packageRules": [ 7 | { 8 | "groupName": "all non-major dependencies", 9 | "matchUpdateTypes": [ 10 | "patch", 11 | "minor" 12 | ], 13 | "groupSlug": "all-minor-patch" 14 | }, 15 | { 16 | "groupName": "HotChocolate", 17 | "matchPackagePrefixes": [ 18 | "HotChocolate" 19 | ] 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/Query.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Query 4 | { 5 | public Product? GetProduct([ID] string id, Data repository) 6 | => repository.Products.FirstOrDefault(t => t.Id.Equals(id)); 7 | 8 | [GraphQLDeprecated("Use product query instead")] 9 | public DeprecatedProduct? GetDeprecatedProduct(string sku, string package, Data repository) 10 | => repository.DeprecatedProducts.FirstOrDefault(t => t.Sku.Equals(sku) && t.Package.Equals(package)); 11 | } 12 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services.AddSingleton(); 4 | builder.Services.AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederation() 9 | .AddQueryType() 10 | .AddType() 11 | .RegisterService() 12 | .RegisterService(); 13 | 14 | var app = builder.Build(); 15 | 16 | app.MapGraphQL(); 17 | 18 | app.Run(); -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/ProductDimension.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Shareable] 6 | public class ProductDimension 7 | { 8 | public ProductDimension(string size, double weight, string? unit) 9 | { 10 | Size = size; 11 | Weight = weight; 12 | Unit = unit; 13 | } 14 | 15 | public string? Size { get; } 16 | 17 | public double? Weight { get; } 18 | 19 | [Inaccessible] 20 | public string? Unit { get; } 21 | } 22 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Query { 6 | someField(a: Int): TestType 7 | } 8 | 9 | type TestType @key(fields: "id") { 10 | id: Int 11 | name: String 12 | } 13 | 14 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 15 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 16 | 17 | "Scalar representing a set of fields." 18 | scalar _FieldSet 19 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Data/UserRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class UserRepository 4 | { 5 | private readonly Dictionary _users; 6 | 7 | public UserRepository() 8 | => _users = CreateUsers().ToDictionary(t => t.Id); 9 | 10 | public Task GetUserById(string id) 11 | => Task.FromResult(_users[id]); 12 | 13 | private static IEnumerable CreateUsers() 14 | { 15 | yield return new User("1", "@ada"); 16 | yield return new User("2", "@complete"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/UserRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class UserRepository 4 | { 5 | private readonly Dictionary _users; 6 | 7 | public UserRepository() 8 | => _users = CreateUsers().ToDictionary(t => t.Id); 9 | 10 | public Task GetUserById(string id) 11 | => Task.FromResult(_users[id]); 12 | 13 | private static IEnumerable CreateUsers() 14 | { 15 | yield return new User("1", "@ada"); 16 | yield return new User("2", "@complete"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/ProductVariation.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class ProductVariation 4 | { 5 | public ProductVariation(string id) 6 | { 7 | Id = id; 8 | } 9 | 10 | public string Id { get; } 11 | } 12 | 13 | public class ProductVariationType : ObjectType 14 | { 15 | protected override void Configure(IObjectTypeDescriptor descriptor) 16 | { 17 | descriptor.BindFieldsImplicitly(); 18 | descriptor.Field(pv => pv.Id).Type>(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | build: 8 | timeout-minutes: 30 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup .NET 14 | uses: actions/setup-dotnet@v4 15 | with: 16 | dotnet-version: 7.0.x 17 | - name: Restore dependencies 18 | run: dotnet restore 19 | - name: Build 20 | run: dotnet build --no-restore 21 | - name: Test 22 | run: dotnet test --no-build --verbosity normal 23 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/Product.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Reviews; 4 | 5 | [Key("upc")] 6 | [Extends] 7 | public class Product 8 | { 9 | public Product(string upc) 10 | { 11 | Upc = upc; 12 | } 13 | 14 | [External] 15 | public string Upc { get; } 16 | 17 | public Task> GetReviews(ReviewRepository repository) 18 | => repository.GetByProductUpcAsync(Upc); 19 | 20 | [ReferenceResolver] 21 | public static Product GetByIdAsync(string upc) => new(upc); 22 | } 23 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Types/UserType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Accounts; 4 | 5 | public class UserType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .Key("id") 11 | .ResolveReferenceWith(t => ResolveUserById(default!, default!)); 12 | } 13 | 14 | private static Task ResolveUserById( 15 | string id, 16 | UserRepository userRepository) 17 | => userRepository.GetUserByIdAsync(id); 18 | } 19 | -------------------------------------------------------------------------------- /Federation/SchemaTypeDescriptorAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation; 4 | 5 | /// 6 | /// Schema descriptor attribute that provides common mechanism of applying directives on schema type. 7 | /// 8 | /// NOTE: HotChocolate currently does not provide mechanism to apply those directives. 9 | /// 10 | public abstract class SchemaTypeDescriptorAttribute : Attribute 11 | { 12 | public abstract void OnConfigure(IDescriptorContext context, ISchemaTypeDescriptor descriptor, Type type); 13 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Types/ProductType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Products; 4 | 5 | public class ProductType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .Key("upc") 11 | .ResolveReferenceWith(t => GetProduct(default!, default!)); 12 | } 13 | 14 | private static Product GetProduct( 15 | string upc, 16 | ProductRepository productRepository) 17 | => productRepository.GetById(upc); 18 | } 19 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/UserRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Accounts; 2 | 3 | public class UserRepository 4 | { 5 | private readonly Dictionary _users; 6 | 7 | public UserRepository() 8 | => _users = CreateUsers().ToDictionary(t => t.Id); 9 | 10 | public Task GetUserByIdAsync(string id) 11 | => Task.FromResult(_users[id]); 12 | 13 | private static IEnumerable CreateUsers() 14 | { 15 | yield return new User("1", "Ada Lovelace", "@ada"); 16 | yield return new User("2", "Alan Turing", "@complete"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Data/UserRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Accounts; 2 | 3 | public class UserRepository 4 | { 5 | private readonly Dictionary _users; 6 | 7 | public UserRepository() 8 | => _users = CreateUsers().ToDictionary(t => t.Id); 9 | 10 | public Task GetUserByIdAsync(string id) 11 | => Task.FromResult(_users[id]); 12 | 13 | private static IEnumerable CreateUsers() 14 | { 15 | yield return new User("1", "Ada Lovelace", "@ada"); 16 | yield return new User("2", "Alan Turing", "@complete"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Federation/Two/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 2 | 3 | /// 4 | /// Scalar Scope representation. 5 | /// 6 | public sealed class Scope 7 | { 8 | /// 9 | /// Initializes a new instance of . 10 | /// 11 | /// 12 | /// Scope value 13 | /// 14 | public Scope(string value) 15 | { 16 | Value = value; 17 | } 18 | 19 | /// 20 | /// Retrieve scope value 21 | /// 22 | public string Value { get; } 23 | } -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/User.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Relay; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 4 | 5 | public class User 6 | { 7 | public User(string email) 8 | { 9 | Email = email; 10 | } 11 | 12 | public User(string email, int totalProductsCreated) 13 | { 14 | Email = email; 15 | TotalProductsCreated = totalProductsCreated; 16 | } 17 | 18 | public string Email { get; private set; } 19 | 20 | public int? TotalProductsCreated { get; private set; } 21 | } 22 | -------------------------------------------------------------------------------- /Federation/Two/TagValue.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 2 | 3 | /// 4 | /// Object representation of a @tag directive. 5 | /// 6 | public sealed class TagValue 7 | { 8 | 9 | /// 10 | /// Initializes a new instance of . 11 | /// 12 | /// 13 | /// Custom tag value 14 | /// 15 | public TagValue(string name) 16 | { 17 | Name = name; 18 | } 19 | 20 | /// 21 | /// Get tag value. 22 | /// 23 | public string Name { get; } 24 | } 25 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Types/QueryType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Products; 4 | 5 | public class QueryType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .Name("Query") 11 | .Field("topProducts") 12 | .Argument("first", a => a.Type()) 13 | .Type>>>() 14 | .Resolve(ctx => ctx.Service().GetTop( 15 | ctx.ArgumentValue("first") ?? 5)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | interface IQuery { 6 | someField(a: Int): TestType 7 | } 8 | 9 | type Query { 10 | someField(a: Int): TestType 11 | } 12 | 13 | type TestType @key(fields: "id") { 14 | id: Int! 15 | name: String! 16 | } 17 | 18 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 19 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 20 | 21 | "Scalar representing a set of fields." 22 | scalar _FieldSet 23 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env 3 | WORKDIR /app 4 | 5 | # Copy csproj and restore as distinct layers 6 | COPY ./Compatibility/CodeFirst ./Compatibility/CodeFirst 7 | COPY ./Federation/ ./Federation/ 8 | WORKDIR /app/Compatibility/CodeFirst 9 | RUN dotnet restore 10 | RUN dotnet publish -c Release -o out 11 | 12 | # Build runtime image 13 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 14 | WORKDIR /app 15 | COPY --from=build-env /app/Compatibility/CodeFirst/out . 16 | EXPOSE 4001 17 | ENV ASPNETCORE_URLS=http://*:4001 18 | ENTRYPOINT ["dotnet", "CodeFirst.dll"] -------------------------------------------------------------------------------- /Federation/Constants/WellKnownContextData.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Constants; 2 | 3 | internal static class WellKnownContextData 4 | { 5 | public const string KeyMarker = "ApolloGraphQL.HotChocolate.Federation.Key"; 6 | public const string ExtendMarker = "ApolloGraphQL.HotChocolate.Federation.Extend"; 7 | public const string ExternalSetter = "ApolloGraphQL.HotChocolate.Federation.ExternalSetter"; 8 | public const string EntityResolver = "ApolloGraphQL.HotChocolate.Federation.EntityResolver"; 9 | public const string DataField = "data"; 10 | public const string TypeField = "__type"; 11 | } 12 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/Reviews.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/User.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Accounts; 4 | 5 | [Key("id")] 6 | public class User 7 | { 8 | public User(string id, string name, string username) 9 | { 10 | Id = id; 11 | Name = name; 12 | Username = username; 13 | } 14 | 15 | public string Id { get; } 16 | 17 | public string Name { get; } 18 | 19 | public string Username { get; } 20 | 21 | [ReferenceResolver] 22 | public static Task GetByIdAsync( 23 | string id, 24 | UserRepository userRepository) 25 | => userRepository.GetUserByIdAsync(id); 26 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Accounts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/Inventory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Products.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Accounts/Accounts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/Products.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Inventory/Inventory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/User.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Relay; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 4 | 5 | [Key("email")] 6 | [ExtendServiceType] 7 | public class User 8 | { 9 | public User() 10 | { 11 | } 12 | 13 | public User(string email, int? totalProductsCreated) 14 | { 15 | Email = email; 16 | TotalProductsCreated = totalProductsCreated; 17 | } 18 | 19 | [ID] 20 | [External] 21 | public string Email { get; set; } = default!; 22 | 23 | [External] 24 | public int? TotalProductsCreated { get; set; } 25 | } 26 | -------------------------------------------------------------------------------- /Federation/Descriptors/EntityResolverDefinition.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors.Definitions; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.Descriptors; 4 | 5 | /// 6 | /// The entity definition allows to specify a reference resolver. 7 | /// 8 | public sealed class EntityResolverDefinition : DefinitionBase 9 | { 10 | /// 11 | /// The runtime type of the entity. 12 | /// 13 | public Type? EntityType { get; set; } 14 | 15 | /// 16 | /// The reference resolver definition. 17 | /// 18 | public ReferenceResolverDefinition? ResolverDefinition { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | secops: apollo/circleci-secops-orb@2.0.6 4 | workflows: 5 | security-scans: 6 | jobs: 7 | - secops/gitleaks: 8 | context: 9 | - secops-oidc 10 | - github-orb 11 | git-base-revision: <<#pipeline.git.base_revision>><><> 12 | git-revision: << pipeline.git.revision >> 13 | - secops/semgrep: 14 | context: 15 | - secops-oidc 16 | - github-orb 17 | git-base-revision: <<#pipeline.git.base_revision>><><> 18 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env 3 | WORKDIR /app 4 | 5 | # Copy csproj and restore as distinct layers 6 | COPY ./Compatibility/AnnotationBased/ ./Compatibility/AnnotationBased 7 | COPY ./Federation/ ./Federation/ 8 | WORKDIR /app/Compatibility/AnnotationBased 9 | RUN dotnet restore 10 | RUN dotnet publish -c Release -o out 11 | 12 | # Build runtime image 13 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 14 | WORKDIR /app 15 | COPY --from=build-env /app/Compatibility/AnnotationBased/out . 16 | EXPOSE 4001 17 | ENV ASPNETCORE_URLS=http://*:4001 18 | ENTRYPOINT ["dotnet", "AnnotationBased.dll"] -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/CaseStudy.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class CaseStudy 4 | { 5 | 6 | public CaseStudy(string caseNumber, string? description) 7 | { 8 | CaseNumber = caseNumber; 9 | Description = description; 10 | } 11 | 12 | public string CaseNumber { get; } 13 | public string? Description { get; } 14 | 15 | } 16 | 17 | public class CaseStudyType : ObjectType 18 | { 19 | protected override void Configure(IObjectTypeDescriptor descriptor) 20 | { 21 | descriptor.BindFieldsImplicitly(); 22 | descriptor.Field(f => f.CaseNumber).Type>(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '*.md' 9 | 10 | jobs: 11 | build: 12 | uses: ./.github/workflows/build.yaml 13 | 14 | integration: 15 | needs: build 16 | uses: ./.github/workflows/compatibility.yaml 17 | 18 | release-notes: 19 | timeout-minutes: 10 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | pull-requests: read 24 | steps: 25 | - name: Release Drafter 26 | uses: release-drafter/release-drafter@v5 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /Federation/EntityType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Properties; 2 | using static ApolloGraphQL.HotChocolate.Federation.Constants.WellKnownTypeNames; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// A union called _Entity which is a union of all types that use the @key directive, 8 | /// including both types native to the schema and extended types. 9 | /// 10 | public sealed class EntityType : UnionType 11 | { 12 | protected override void Configure(IUnionTypeDescriptor descriptor) 13 | => descriptor 14 | .Name(Entity) 15 | .Description(FederationResources.EntityType_Description); 16 | } 17 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/Inventory.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("id")] 6 | [InterfaceObject] 7 | public class Inventory 8 | { 9 | 10 | public Inventory(string id) 11 | { 12 | Id = id; 13 | } 14 | 15 | [ID] 16 | public string Id { get; } 17 | 18 | public List DeprecatedProducts(Data repository) => repository.DeprecatedProducts; 19 | 20 | [ReferenceResolver] 21 | public static Inventory? GetInventoryById( 22 | string id, 23 | Data repository) 24 | { 25 | return repository.Inventories().FirstOrDefault( 26 | r => r.Id.Equals(id)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/CodeFirst.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Subgraph_SDL.snap: -------------------------------------------------------------------------------- 1 | type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") { 2 | id: ID! 3 | createdBy: User! @provides(fields: "totalProductsCreated") 4 | sku: String 5 | package: String 6 | variation: ProductVariation 7 | dimensions: ProductDimension 8 | } 9 | 10 | type ProductDimension { 11 | size: String 12 | weight: Float 13 | } 14 | 15 | type ProductVariation { 16 | id: ID! 17 | } 18 | 19 | extend type Query { 20 | product(id: ID!): Product 21 | } 22 | 23 | extend type User @key(fields: "email") { 24 | email: ID! @external 25 | totalProductsCreated: Int @external 26 | } 27 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Subgraph_SDL.snap: -------------------------------------------------------------------------------- 1 | type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") { 2 | id: ID! 3 | sku: String 4 | package: String 5 | variation: ProductVariation 6 | dimensions: ProductDimension 7 | createdBy: User @provides(fields: "totalProductsCreated") 8 | } 9 | 10 | type ProductDimension { 11 | size: String 12 | weight: Float 13 | } 14 | 15 | type ProductVariation { 16 | id: ID! 17 | } 18 | 19 | extend type Query { 20 | product(id: ID!): Product 21 | } 22 | 23 | extend type User @key(fields: "email") { 24 | email: ID! @external 25 | totalProductsCreated: Int @external 26 | } 27 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/AnnotationBased.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/Review.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Reviews; 4 | 5 | [Key("id")] 6 | public class Review 7 | { 8 | public Review(string id, string body, string authorId, string upc) 9 | { 10 | Id = id; 11 | Body = body; 12 | AuthorId = authorId; 13 | Product = new Product(upc); 14 | } 15 | 16 | public string Id { get; } 17 | 18 | public string Body { get; } 19 | 20 | public string AuthorId { get; } 21 | 22 | public Product Product { get; } 23 | 24 | [Provides("username")] 25 | public Task GetAuthorAsync( 26 | UserRepository repository) 27 | => repository.GetUserById(AuthorId); 28 | } 29 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/Product.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("upc")] 6 | public class Product 7 | { 8 | public Product(string upc, string name, int price, int weight) 9 | { 10 | Upc = upc; 11 | Name = name; 12 | Price = price; 13 | Weight = weight; 14 | } 15 | 16 | public string Upc { get; } 17 | 18 | public string Name { get; } 19 | 20 | public int Price { get; } 21 | 22 | public int Weight { get; } 23 | 24 | [ReferenceResolver] 25 | public static Product GetByIdAsync( 26 | string upc, 27 | ProductRepository productRepository) 28 | => productRepository.GetById(upc); 29 | } 30 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/SchemaSetup.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 3 | using HotChocolate.Execution; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst; 7 | 8 | public static class SchemaSetup 9 | { 10 | public static async Task CreateAsync() 11 | => await new ServiceCollection() 12 | .AddSingleton() 13 | .AddGraphQL() 14 | .AddApolloFederation() 15 | .AddQueryType() 16 | .RegisterService() 17 | .BuildRequestExecutorAsync(); 18 | } 19 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | builder.Services 4 | .AddSingleton(); 5 | 6 | builder.Services 7 | .AddGraphQLServer() 8 | .AddApolloFederationV2(schemaConfiguration: s => 9 | { 10 | s.Link("https://myspecs.dev/myCustomDirective/v1.0", new string[] { "@custom" }); 11 | s.ComposeDirective("@custom"); 12 | }) 13 | .AddType() 14 | .AddType() 15 | .AddQueryType() 16 | .ModifyOptions(options => options.DefaultBindingBehavior = BindingBehavior.Explicit) 17 | .RegisterService(); 18 | 19 | var app = builder.Build(); 20 | 21 | app.MapGraphQL("/"); 22 | app.RunWithGraphQLCommands(args); 23 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/Product.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 2 | 3 | public class Product 4 | { 5 | public Product(string id, string sku, string package, string variation) 6 | { 7 | Id = id; 8 | Sku = sku; 9 | Package = package; 10 | Variation = new(variation); 11 | } 12 | 13 | public string Id { get; } 14 | 15 | public string? Sku { get; } 16 | 17 | public string? Package { get; } 18 | 19 | public ProductVariation? Variation { get; } 20 | 21 | public ProductDimension? Dimensions { get; } = new("1", 1); 22 | 23 | public User? CreatedBy { get; } = new("support@apollographql.com", 1337); 24 | } 25 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/SchemaSetup.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 3 | using HotChocolate.Execution; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased; 7 | 8 | public static class SchemaSetup 9 | { 10 | public static async Task CreateAsync() 11 | => await new ServiceCollection() 12 | .AddSingleton() 13 | .AddGraphQL() 14 | .AddApolloFederation() 15 | .AddQueryType() 16 | .RegisterService() 17 | .BuildRequestExecutorAsync(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Reviews.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/ProductDimension.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class ProductDimension 4 | { 5 | public ProductDimension(string size, double weight, string? unit) 6 | { 7 | Size = size; 8 | Weight = weight; 9 | Unit = unit; 10 | } 11 | 12 | public string? Size { get; } 13 | 14 | public double? Weight { get; } 15 | 16 | public string? Unit { get; } 17 | } 18 | 19 | public class ProductDimensionType : ObjectType 20 | { 21 | protected override void Configure(IObjectTypeDescriptor descriptor) 22 | { 23 | descriptor.BindFieldsImplicitly(); 24 | descriptor.Shareable(); 25 | descriptor.Field(pd => pd.Unit).Inaccessible(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Federation/Two/RequiresScopes.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 4 | 5 | /// 6 | /// Object representation of @requiresScopes directive. 7 | /// 8 | public sealed class RequiresScopes 9 | { 10 | /// 11 | /// Initializes new instance of 12 | /// 13 | /// 14 | /// List of a list of required JWT scopes. 15 | /// 16 | public RequiresScopes(List> scopes) 17 | { 18 | Scopes = scopes; 19 | } 20 | 21 | /// 22 | /// Retrieves list of a list of required JWT scopes. 23 | /// 24 | public List> Scopes { get; } 25 | } 26 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/User.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Reviews; 4 | 5 | [Key("id")] 6 | [Extends] 7 | public class User 8 | { 9 | public User(string id, string username) 10 | { 11 | Id = id; 12 | Username = username; 13 | } 14 | 15 | [External] 16 | public string Id { get; } 17 | 18 | [External] 19 | public string Username { get; } 20 | 21 | public Task> GetReviews( 22 | ReviewRepository repository) 23 | => repository.GetByUserIdAsync(Id); 24 | 25 | [ReferenceResolver] 26 | public static Task GetUserByIdAsync( 27 | string id, 28 | UserRepository repository) 29 | => repository.GetUserById(id); 30 | } 31 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String 7 | } 8 | 9 | type Query { 10 | someField(a: Int): Review 11 | } 12 | 13 | type Review @key(fields: "id") { 14 | id: Int 15 | product: Product @requires(fields: "id") 16 | } 17 | 18 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 19 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 20 | 21 | "Used to annotate the required input fieldset from a base type for a resolver." 22 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 23 | 24 | "Scalar representing a set of fields." 25 | scalar _FieldSet 26 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String! 7 | } 8 | 9 | type Query { 10 | someField(a: Int): Review 11 | } 12 | 13 | type Review @key(fields: "id") { 14 | id: Int! 15 | product: Product! @requires(fields: "id") 16 | } 17 | 18 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 19 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 20 | 21 | "Used to annotate the required input fieldset from a base type for a resolver." 22 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 23 | 24 | "Scalar representing a set of fields." 25 | scalar _FieldSet 26 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/ProductResearch.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("study { caseNumber }")] 6 | public class ProductResearch 7 | { 8 | 9 | public ProductResearch(CaseStudy study, string? outcome) 10 | { 11 | Study = study; 12 | Outcome = outcome; 13 | } 14 | 15 | public CaseStudy Study { get; } 16 | public string? Outcome { get; } 17 | 18 | [ReferenceResolver] 19 | public static ProductResearch? GetProductReasearchByCaseNumber( 20 | [Map("study.caseNumber")] string caseNumber, 21 | Data repository) 22 | { 23 | return repository.ProductResearches.FirstOrDefault( 24 | r => r.Study.CaseNumber.Equals(caseNumber)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Products/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class ProductRepository 4 | { 5 | private readonly Dictionary _products; 6 | 7 | public ProductRepository() 8 | { 9 | _products = CreateProducts().ToDictionary(product => product.Upc); 10 | } 11 | 12 | public IEnumerable GetTop(int amount) 13 | => _products.Values.Take(amount); 14 | 15 | public Product GetById(string upc) 16 | => _products[upc]; 17 | 18 | private static IEnumerable CreateProducts() 19 | { 20 | yield return new Product("1", "Table", 899, 100); 21 | yield return new Product("2", "Couch", 1299, 1000); 22 | yield return new Product("3", "Chair", 54, 50); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/CodeFirst/Products/Data/ProductRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class ProductRepository 4 | { 5 | private readonly Dictionary _products; 6 | 7 | public ProductRepository() 8 | { 9 | _products = CreateProducts().ToDictionary(product => product.Upc); 10 | } 11 | 12 | public IEnumerable GetTop(int amount) 13 | => _products.Values.Take(amount); 14 | 15 | public Product GetById(string upc) 16 | => _products[upc]; 17 | 18 | private static IEnumerable CreateProducts() 19 | { 20 | yield return new Product("1", "Table", 899, 100); 21 | yield return new Product("2", "Couch", 1299, 1000); 22 | yield return new Product("3", "Chair", 54, 50); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | doSomething(input: SomeInput): Boolean 3 | } 4 | 5 | type Query implements iQuery { 6 | someField(a: Int): TestType 7 | } 8 | 9 | enum SomeEnum { 10 | FOO 11 | BAR 12 | } 13 | 14 | input SomeInput { 15 | name: String! 16 | description: String 17 | someEnum: SomeEnum 18 | } 19 | 20 | type TestType @key(fields: "id") { 21 | id: Int! 22 | name: String! 23 | enum: SomeEnum 24 | } 25 | 26 | type TestTypeTwo { 27 | id: Int! 28 | } 29 | 30 | union TestTypes = TestType | TestTypeTwo 31 | 32 | interface iQuery { 33 | someField(a: Int): TestType 34 | } 35 | 36 | interface iTestType @key(fields: "id") { 37 | id: Int! 38 | external: String! @external 39 | } 40 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | doSomething(input: SomeInput): Boolean 3 | } 4 | 5 | type Query implements iQuery { 6 | someField(a: Int): TestType 7 | } 8 | 9 | enum SomeEnum { 10 | FOO 11 | BAR 12 | } 13 | 14 | input SomeInput { 15 | name: String! 16 | description: String 17 | someEnum: SomeEnum 18 | } 19 | 20 | type TestType @key(fields: "id") { 21 | id: Int! 22 | name: String! 23 | enum: SomeEnum 24 | } 25 | 26 | type TestTypeTwo { 27 | id: Int! 28 | } 29 | 30 | union TestTypes = TestType | TestTypeTwo 31 | 32 | interface iQuery { 33 | someField(a: Int): TestType 34 | } 35 | 36 | interface iTestType @key(fields: "id") { 37 | id: Int! 38 | external: String! @external 39 | } 40 | -------------------------------------------------------------------------------- /Federation/ExtendServiceTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation; 4 | 5 | /// 6 | /// This attribute is used to mark types as an extended type 7 | /// of a type that is defined by another service when 8 | /// using apollo federation. 9 | /// 10 | [AttributeUsage( 11 | AttributeTargets.Class | 12 | AttributeTargets.Struct | 13 | AttributeTargets.Interface)] 14 | [Obsolete("Use ExtendsAttribute instead")] 15 | public sealed class ExtendServiceTypeAttribute : ObjectTypeDescriptorAttribute 16 | { 17 | protected override void OnConfigure( 18 | IDescriptorContext context, 19 | IObjectTypeDescriptor descriptor, 20 | Type type) 21 | => descriptor.ExtendServiceType(); 22 | } 23 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/UserType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 4 | 5 | public class UserType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .ExtendServiceType() 11 | .Key("email") 12 | .ResolveReferenceWith(t => GetUserById(default!)); 13 | 14 | descriptor 15 | .Field(t => t.Email) 16 | .External() 17 | .ID(); 18 | 19 | descriptor 20 | .Field(t => t.TotalProductsCreated) 21 | .External(); 22 | } 23 | 24 | private static User GetUserById(string email) => new(email); 25 | } 26 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/QueryType.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HotChocolate.Types; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 5 | 6 | public class QueryType : ObjectType 7 | { 8 | protected override void Configure(IObjectTypeDescriptor descriptor) 9 | { 10 | descriptor 11 | .ExtendServiceType() 12 | .Name("Query") 13 | .Field("product") 14 | .Argument("id", a => a.Type>()) 15 | .Type() 16 | .Resolve(ctx => 17 | { 18 | var id = ctx.ArgumentValue("id"); 19 | return ctx.Service().Products.FirstOrDefault(t => t.Id.Equals(id)); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String 7 | } 8 | 9 | type Query { 10 | someField(a: Int): Review 11 | } 12 | 13 | type Review @key(fields: "id") { 14 | id: Int 15 | product: Product @provides(fields: "name") 16 | } 17 | 18 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 19 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 20 | 21 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 22 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 23 | 24 | "Scalar representing a set of fields." 25 | scalar _FieldSet 26 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String! 7 | } 8 | 9 | type Query { 10 | someField(a: Int): Review 11 | } 12 | 13 | type Review @key(fields: "id") { 14 | id: Int! 15 | product: Product! @provides(fields: "name") 16 | } 17 | 18 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 19 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 20 | 21 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 22 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 23 | 24 | "Scalar representing a set of fields." 25 | scalar _FieldSet 26 | -------------------------------------------------------------------------------- /Federation/ReferenceResolverAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ApolloGraphQL.HotChocolate.Federation.Descriptors; 3 | using HotChocolate.Types.Descriptors; 4 | 5 | namespace ApolloGraphQL.HotChocolate.Federation; 6 | 7 | /// 8 | /// The reference resolver enables your gateway's query planner to resolve a particular 9 | /// entity by whatever unique identifier your other subgraphs use to reference it. 10 | /// 11 | [AttributeUsage(AttributeTargets.Method)] 12 | public class ReferenceResolverAttribute : Attribute 13 | { 14 | public void Configure(IDescriptorContext context, IObjectTypeDescriptor descriptor, MethodInfo method) 15 | { 16 | var entityResolverDescriptor = new EntityResolverDescriptor(descriptor); 17 | entityResolverDescriptor.ResolveReferenceWith(method); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Federation.Tests/ManualBatchScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading.Tasks; 4 | using GreenDonut; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation; 7 | 8 | // copied over from GreenDonut 9 | public class ManualBatchScheduler : IBatchScheduler 10 | { 11 | private readonly object _sync = new(); 12 | private readonly ConcurrentQueue> _queue = new(); 13 | 14 | public void Dispatch() 15 | { 16 | lock (_sync) 17 | { 18 | while (_queue.TryDequeue(out var dispatch)) 19 | { 20 | dispatch(); 21 | } 22 | } 23 | } 24 | 25 | public void Schedule(Func dispatch) 26 | { 27 | lock (_sync) 28 | { 29 | _queue.Enqueue(dispatch); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Inventory/Product.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Inventory; 4 | 5 | [Key("upc")] 6 | [Extends] 7 | public class Product 8 | { 9 | public Product(string upc) 10 | { 11 | Upc = upc; 12 | } 13 | 14 | [External] 15 | public string Upc { get; } 16 | 17 | [External] 18 | public int Weight { get; private set; } 19 | 20 | [External] 21 | public int Price { get; private set; } 22 | 23 | public bool InStock { get; } = true; 24 | 25 | // free for expensive items, else the estimate is based on weight 26 | [Requires("price weight")] 27 | public int GetShippingEstimate() 28 | => Price > 1000 ? 0 : (int)(Weight * 0.5); 29 | 30 | [ReferenceResolver] 31 | public static Product GetProduct(string upc) 32 | => new Product(upc); 33 | } 34 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Types/ReviewType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Reviews; 4 | 5 | public class ReviewType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .Key("id"); 11 | 12 | descriptor 13 | .Field(t => t.Product) 14 | .Type>(); 15 | 16 | descriptor 17 | .Field("author") 18 | .Provides("username") 19 | .Type>() 20 | .Resolve(async ctx => 21 | { 22 | var repository = ctx.Service(); 23 | var authorId = ctx.Parent().AuthorId; 24 | return await repository.GetUserById(authorId); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Federation/MapAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// Maps an argument to a representation value. 5 | /// 6 | /// WARNING: Only scalar leaf values are supported. Path cannot point to an object nor a list field. 7 | /// 8 | [AttributeUsage(AttributeTargets.Parameter)] 9 | public class MapAttribute : Attribute 10 | { 11 | /// 12 | /// Represents an argument to a representation value. 13 | /// 14 | /// 15 | /// A path to field in the representation graph. 16 | /// 17 | public MapAttribute(string path) 18 | { 19 | Path = path ?? throw new ArgumentNullException(nameof(path)); 20 | } 21 | 22 | /// 23 | /// A path to field in the representation graph. 24 | /// 25 | public string Path { get; } 26 | } 27 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Products/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/Query.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Query 4 | { 5 | public Product? GetProduct([ID] string id, Data repository) 6 | => repository.Products.FirstOrDefault(t => t.Id.Equals(id)); 7 | 8 | public DeprecatedProduct? GetDeprecatedProduct(string sku, string package, Data repository) 9 | => repository.DeprecatedProducts.FirstOrDefault(t => t.Sku.Equals(sku) && t.Package.Equals(package)); 10 | } 11 | 12 | public class QueryType : ObjectType 13 | { 14 | protected override void Configure(IObjectTypeDescriptor descriptor) 15 | { 16 | descriptor.Field(q => q.GetProduct(default!, default!)) 17 | .Type(); 18 | descriptor.Field(q => q.GetDeprecatedProduct(default!, default!, default!)) 19 | .Type() 20 | .Deprecated("Use product query instead"); 21 | } 22 | } -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/DeprecatedProduct.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("sku package")] 6 | public class DeprecatedProduct 7 | { 8 | public DeprecatedProduct(string sku, string package, string? reason, User? createdBy) 9 | { 10 | Sku = sku; 11 | Package = package; 12 | Reason = reason; 13 | CreatedBy = createdBy; 14 | } 15 | 16 | public string Sku { get; } 17 | 18 | public string Package { get; } 19 | 20 | public string? Reason { get; } 21 | 22 | public User? CreatedBy { get; } 23 | 24 | [ReferenceResolver] 25 | public static DeprecatedProduct? GetProductByPackage( 26 | string sku, 27 | string package, 28 | Data repository) 29 | => repository.DeprecatedProducts.FirstOrDefault( 30 | t => t.Sku.Equals(sku) && 31 | t.Package.Equals(package)); 32 | } 33 | -------------------------------------------------------------------------------- /Federation/ExtendsAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation; 4 | 5 | /// 6 | /// 7 | /// directive @extends on OBJECT | INTERFACE 8 | /// 9 | /// 10 | /// The @extends directive is used to represent type extensions in the schema. Federated extended types should have 11 | /// corresponding @key directive defined that specifies primary key required to fetch the underlying object. 12 | /// 13 | /// # extended from the Users service 14 | /// type Foo @extends @key(fields: "id") { 15 | /// id: ID 16 | /// description: String 17 | /// } 18 | /// 19 | /// 20 | public sealed class ExtendsAttribute : ObjectTypeDescriptorAttribute 21 | { 22 | protected override void OnConfigure( 23 | IDescriptorContext context, 24 | IObjectTypeDescriptor descriptor, 25 | Type type) 26 | => descriptor.ExtendsType(); 27 | } 28 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/README.md: -------------------------------------------------------------------------------- 1 | # HotChocolate Federation Compatibility using Code-First approach 2 | 3 | Example HotChocolate subgraph (configured using explicit binding and code first approach) used for testing [Apollo Federation Subgraph Compatibility](https://github.com/apollographql/apollo-federation-subgraph-compatibility). 4 | 5 | ## Usage 6 | 7 | Install compatibility script 8 | 9 | ```shell 10 | npm install --dev @apollo/federation-subgraph-compatibility 11 | ``` 12 | 13 | Generate test schema 14 | 15 | ```shell 16 | dotnet run --project Compatibility/CodeFirst/CodeFirst.csproj schema export --output products.graphql 17 | ``` 18 | 19 | Run compatibility tests 20 | 21 | ```shell 22 | npx fedtest docker --compose Compatibility/CodeFirst/docker-compose.yaml --schema Compatibility/CodeFirst/products.graphql 23 | ``` 24 | 25 | See [compatibility script documentation](https://www.npmjs.com/package/@apollo/federation-subgraph-compatibility) for additional details. -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/README.md: -------------------------------------------------------------------------------- 1 | # HotChocolate Federation Compatibility usign AnnotationBased approach 2 | 3 | Example HotChocolate subgraph (configured using Attributes) used for testing [Apollo Federation Subgraph Compatibility](https://github.com/apollographql/apollo-federation-subgraph-compatibility). 4 | 5 | ## Usage 6 | 7 | Install compatibility script 8 | 9 | ```shell 10 | npm install --dev @apollo/federation-subgraph-compatibility 11 | ``` 12 | 13 | Generate test schema 14 | 15 | ```shell 16 | dotnet run --project Compatibility/AnnotationBased/AnnotationBased.csproj schema export --output products.graphql 17 | ``` 18 | 19 | Run compatibility tests 20 | 21 | ```shell 22 | npx fedtest docker --compose Compatibility/AnnotationBased/docker-compose.yaml --schema Compatibility/AnnotationBased/products.graphql 23 | ``` 24 | 25 | See [compatibility script documentation](https://www.npmjs.com/package/@apollo/federation-subgraph-compatibility) for additional details. -------------------------------------------------------------------------------- /Federation/Two/Link.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 4 | 5 | /// 6 | /// Object representation of @link directive. 7 | /// 8 | public sealed class Link 9 | { 10 | /// 11 | /// Initializes new instance of 12 | /// 13 | /// 14 | /// Url of specification to be imported 15 | /// 16 | /// 17 | /// Optional list of imported elements. 18 | /// 19 | public Link(string url, List? import) 20 | { 21 | Url = url; 22 | Import = import; 23 | } 24 | 25 | /// 26 | /// Gets imported specification url. 27 | /// 28 | public string Url { get; } 29 | 30 | /// 31 | /// Gets optional list of imported element names. 32 | /// 33 | 34 | public List? Import { get; } 35 | } 36 | -------------------------------------------------------------------------------- /Federation.Tests/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst_With_DateTime.snap: -------------------------------------------------------------------------------- 1 | "The `DateTime` scalar represents an ISO-8601 compliant date time type." 2 | scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") 3 | 4 | type Mutation { 5 | doSomething(input: SomeInput): Boolean 6 | } 7 | 8 | type Query implements iQuery { 9 | someField(a: Int): TestType 10 | } 11 | 12 | enum SomeEnum { 13 | FOO 14 | BAR 15 | } 16 | 17 | input SomeInput { 18 | name: String! 19 | description: String 20 | someEnum: SomeEnum 21 | time: DateTime 22 | } 23 | 24 | type TestType @key(fields: "id") { 25 | id: Int! 26 | name: String! 27 | enum: SomeEnum 28 | } 29 | 30 | type TestTypeTwo { 31 | id: Int! 32 | } 33 | 34 | union TestTypes = TestType | TestTypeTwo 35 | 36 | interface iQuery { 37 | someField(a: Int): TestType 38 | } 39 | 40 | interface iTestType @key(fields: "id") { 41 | id: Int! 42 | external: String! @external 43 | } 44 | -------------------------------------------------------------------------------- /Federation.Tests/One/__snapshots__/FederationSchemaPrinterTests.TestFederationPrinterSchemaFirst_With_DateTime.snap: -------------------------------------------------------------------------------- 1 | "The `DateTime` scalar represents an ISO-8601 compliant date time type." 2 | scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time") 3 | 4 | type Mutation { 5 | doSomething(input: SomeInput): Boolean 6 | } 7 | 8 | type Query implements iQuery { 9 | someField(a: Int): TestType 10 | } 11 | 12 | enum SomeEnum { 13 | FOO 14 | BAR 15 | } 16 | 17 | input SomeInput { 18 | name: String! 19 | description: String 20 | someEnum: SomeEnum 21 | time: DateTime 22 | } 23 | 24 | type TestType @key(fields: "id") { 25 | id: Int! 26 | name: String! 27 | enum: SomeEnum 28 | } 29 | 30 | type TestTypeTwo { 31 | id: Int! 32 | } 33 | 34 | union TestTypes = TestType | TestTypeTwo 35 | 36 | interface iQuery { 37 | someField(a: Int): TestType 38 | } 39 | 40 | interface iTestType @key(fields: "id") { 41 | id: Int! 42 | external: String! @external 43 | } 44 | -------------------------------------------------------------------------------- /Federation/Extensions/DirectiveTypeDescriptorExtensions.cs: -------------------------------------------------------------------------------- 1 | using static ApolloGraphQL.HotChocolate.Federation.Constants.WellKnownArgumentNames; 2 | using FieldSetTypeV1 = ApolloGraphQL.HotChocolate.Federation.One.FieldSetType; 3 | using FieldSetTypeV2 = ApolloGraphQL.HotChocolate.Federation.Two.FieldSetType; 4 | 5 | namespace ApolloGraphQL.HotChocolate.Federation; 6 | 7 | internal static class DirectiveTypeDescriptorExtensions 8 | { 9 | public static IDirectiveTypeDescriptor FieldsArgumentV1( 10 | this IDirectiveTypeDescriptor descriptor) 11 | { 12 | descriptor 13 | .Argument(Fields) 14 | .Type>(); 15 | return descriptor; 16 | } 17 | 18 | public static IDirectiveTypeDescriptor FieldsArgumentV2( 19 | this IDirectiveTypeDescriptor descriptor) 20 | { 21 | descriptor 22 | .Argument(Fields) 23 | .Type>(); 24 | return descriptor; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Types/ProductType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Reviews; 4 | 5 | public class ProductType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .ExtendServiceType() 11 | .Key("upc") 12 | .ResolveReferenceWith(t => GetByIdAsync(default!)); 13 | 14 | descriptor 15 | .Field(t => t.Upc) 16 | .External(); 17 | 18 | descriptor 19 | .Field("reviews") 20 | .Type>>>() 21 | .Resolve(async ctx => 22 | { 23 | var repository = ctx.Service(); 24 | var upc = ctx.Parent().Upc; 25 | return await repository.GetByProductUpcAsync(upc); 26 | }); 27 | } 28 | 29 | private static Product GetByIdAsync(string upc) => new(upc); 30 | } 31 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/Inventory.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Inventory 4 | { 5 | 6 | public Inventory(string id) 7 | { 8 | Id = id; 9 | } 10 | 11 | public string Id { get; } 12 | 13 | public List DeprecatedProducts(Data repository) => repository.DeprecatedProducts; 14 | } 15 | 16 | public class InventoryType : ObjectType 17 | { 18 | protected override void Configure(IObjectTypeDescriptor descriptor) 19 | { 20 | descriptor.BindFieldsImplicitly(); 21 | descriptor.Field(i => i.Id).Type>(); 22 | descriptor.Key("id") 23 | .ResolveReferenceWith(i => GetInventoryById(default!, default!)) 24 | .InterfaceObject(); 25 | } 26 | 27 | private static Inventory? GetInventoryById( 28 | string id, 29 | Data repository) 30 | { 31 | return repository.Inventories().FirstOrDefault( 32 | r => r.Id.Equals(id)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | release-code: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | if: github.repository == 'apollographql/federation-hotchocolate' 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: | 19 | 6.0.x 20 | 7.0.x 21 | - name: Create package 22 | run: | 23 | NEW_VERSION=$(echo "${GITHUB_REF}" | sed 's/.*\/v//') 24 | echo "New version: ${NEW_VERSION}" 25 | dotnet pack -p:Version=${NEW_VERSION} -p:Configuration=Release Federation/ApolloGraphQL.HotChocolate.Federation.csproj 26 | - name: Publish package 27 | run: dotnet nuget push Federation/bin/Release/*.nupkg -k $NUGET_AUTH_TOKEN -s https://api.nuget.org/v3/index.json 28 | env: 29 | NUGET_AUTH_TOKEN: ${{ secrets.NUGET_TOKEN }} 30 | -------------------------------------------------------------------------------- /Federation/InterfaceObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | 3 | namespace ApolloGraphQL.HotChocolate.Federation; 4 | 5 | /// 6 | /// 7 | /// directive @interfaceObject on OBJECT 8 | /// 9 | /// 10 | /// The @interfaceObject directive provides meta information to the router that this entity 11 | /// type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality 12 | /// of an interface across the supergraph without having to implement (or even be aware of) all its implementing types. 13 | /// 14 | /// NOTE: Only available in Federation v2 15 | /// 16 | /// type Foo @interfaceObject @key(fields: "ids") { 17 | /// id: ID! 18 | /// newCommonField: String 19 | /// } 20 | /// 21 | /// 22 | public sealed class InterfaceObjectAttribute : ObjectTypeDescriptorAttribute 23 | { 24 | protected override void OnConfigure(IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) 25 | { 26 | descriptor.InterfaceObject(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/AnnotationBased/Reviews/ReviewRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class ReviewRepository 4 | { 5 | private readonly Dictionary _reviews; 6 | 7 | public ReviewRepository() 8 | => _reviews = CreateReviews().ToDictionary(review => review.Id); 9 | 10 | public Task> GetByUserIdAsync(string userId) 11 | => Task.FromResult(_reviews.Values.Where(review => review.AuthorId == userId)); 12 | 13 | public Task> GetByProductUpcAsync(string upc) 14 | => Task.FromResult(_reviews.Values.Where(review => review.Product.Upc == upc)); 15 | 16 | public Task GetByIdAsync(string id) 17 | => Task.FromResult(_reviews[id]); 18 | 19 | private static IEnumerable CreateReviews() 20 | { 21 | yield return new Review("1", "Love it!", "1", "1"); 22 | yield return new Review("2", "Too expensive.", "1", "2"); 23 | yield return new Review("3", "Could be better.", "2", "3"); 24 | yield return new Review("4", "Prefer something else.", "2", "1"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Data/ReviewRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Reviews; 2 | 3 | public class ReviewRepository 4 | { 5 | private readonly Dictionary _reviews; 6 | 7 | public ReviewRepository() 8 | => _reviews = CreateReviews().ToDictionary(review => review.Id); 9 | 10 | public Task> GetByUserIdAsync(string userId) 11 | => Task.FromResult(_reviews.Values.Where(review => review.AuthorId == userId)); 12 | 13 | public Task> GetByProductUpcAsync(string upc) 14 | => Task.FromResult(_reviews.Values.Where(review => review.Product.Upc == upc)); 15 | 16 | public Task GetByIdAsync(string id) 17 | => Task.FromResult(_reviews[id]); 18 | 19 | private static IEnumerable CreateReviews() 20 | { 21 | yield return new Review("1", "Love it!", "1", "1"); 22 | yield return new Review("2", "Too expensive.", "1", "2"); 23 | yield return new Review("3", "Could be better.", "2", "3"); 24 | yield return new Review("4", "Prefer something else.", "2", "1"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Federation/One/ExtendsDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | /// 7 | /// 8 | /// directive @extends on OBJECT | INTERFACE 9 | /// 10 | /// 11 | /// The @extends directive is used to represent type extensions in the schema. Federated extended types should have 12 | /// corresponding @key directive defined that specifies primary key required to fetch the underlying object. 13 | /// 14 | /// # extended from the Users service 15 | /// type Foo @extends @key(fields: "id") { 16 | /// id: ID 17 | /// description: String 18 | /// } 19 | /// 20 | /// 21 | public sealed class ExtendsDirectiveType : DirectiveType 22 | { 23 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 24 | => descriptor 25 | .Name(WellKnownTypeNames.Extends) 26 | .Description(FederationResources.ExtendsDirective_Description) 27 | .Location(DirectiveLocation.Object | DirectiveLocation.Interface); 28 | } 29 | -------------------------------------------------------------------------------- /Federation/Two/ExtendsDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @extends on OBJECT | INTERFACE 9 | /// 10 | /// 11 | /// The @extends directive is used to represent type extensions in the schema. Federated extended types should have 12 | /// corresponding @key directive defined that specifies primary key required to fetch the underlying object. 13 | /// 14 | /// # extended from the Users service 15 | /// type Foo @extends @key(fields: "id") { 16 | /// id: ID 17 | /// description: String 18 | /// } 19 | /// 20 | /// 21 | public sealed class ExtendsDirectiveType : DirectiveType 22 | { 23 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 24 | => descriptor 25 | .Name(WellKnownTypeNames.Extends) 26 | .Description(FederationResources.ExtendsDirective_Description) 27 | .Location(DirectiveLocation.Object | DirectiveLocation.Interface); 28 | } 29 | -------------------------------------------------------------------------------- /examples/CodeFirst/Accounts/Program.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Execution; 2 | using HotChocolate.Execution.Instrumentation; 3 | using HotChocolate.Language; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | builder.Services 8 | .AddSingleton(); 9 | 10 | builder.Services 11 | .AddGraphQLServer() 12 | .AddApolloFederation() 13 | .AddQueryType() 14 | .RegisterService() 15 | .AddDiagnosticEventListener(); 16 | 17 | var app = builder.Build(); 18 | app.MapGraphQL(); 19 | app.Run(); 20 | 21 | public class Log : ExecutionDiagnosticEventListener 22 | { 23 | public override IDisposable ExecuteRequest(IRequestContext context) 24 | { 25 | Console.WriteLine(context.Request.Query?.ToString()); 26 | 27 | if (context.Request.VariableValues is not null) 28 | { 29 | foreach (var variable in context.Request.VariableValues) 30 | { 31 | Console.WriteLine($"{variable.Key}: {((IValueNode)variable.Value!).ToString()}"); 32 | } 33 | } 34 | 35 | return base.ExecuteRequest(context); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/CodeFirst/Inventory/Types/ProductType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Inventory; 4 | 5 | public class ProductType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .ExtendServiceType() 11 | .Key("upc") 12 | .ResolveReferenceWith(t => GetProduct(default!)); 13 | 14 | descriptor 15 | .Field(t => t.Upc) 16 | .External(); 17 | 18 | descriptor 19 | .Field(t => t.Weight) 20 | .External(); 21 | 22 | descriptor 23 | .Field(t => t.Price) 24 | .External(); 25 | 26 | // free for expensive items, else the estimate is based on weight 27 | descriptor 28 | .Field("shippingEstimate") 29 | .Requires("price weight") 30 | .Resolve( 31 | ctx => ctx.Parent().Price <= 1000 32 | ? (int)(ctx.Parent().Weight * 0.5) 33 | : 0); 34 | } 35 | 36 | private static Product GetProduct(string upc) => new(upc); 37 | } 38 | -------------------------------------------------------------------------------- /examples/CodeFirst/Reviews/Types/UserType.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types; 2 | 3 | namespace Reviews; 4 | 5 | public class UserType : ObjectType 6 | { 7 | protected override void Configure(IObjectTypeDescriptor descriptor) 8 | { 9 | descriptor 10 | .ExtendServiceType() 11 | .Key("id") 12 | .ResolveReferenceWith(t => GetUserByIdAsync(default!, default!)); 13 | 14 | descriptor 15 | .Field(t => t.Id) 16 | .External(); 17 | 18 | descriptor 19 | .Field(t => t.Username) 20 | .External(); 21 | 22 | descriptor 23 | .Field("reviews") 24 | .Type>>>() 25 | .Resolve(async ctx => 26 | { 27 | var repository = ctx.Service(); 28 | var id = ctx.Parent().Id; 29 | return await repository.GetByUserIdAsync(id); 30 | }); 31 | } 32 | 33 | private static Task GetUserByIdAsync( 34 | string id, 35 | UserRepository repository) 36 | => repository.GetUserById(id); 37 | } 38 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/DeprecatedProduct.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class DeprecatedProduct 4 | { 5 | public DeprecatedProduct(string sku, string package, string? reason, User? createdBy) 6 | { 7 | Sku = sku; 8 | Package = package; 9 | Reason = reason; 10 | CreatedBy = createdBy; 11 | } 12 | 13 | public string Sku { get; } 14 | 15 | public string Package { get; } 16 | 17 | public string? Reason { get; } 18 | 19 | public User? CreatedBy { get; } 20 | } 21 | 22 | class DeprecatedProductType : ObjectType 23 | { 24 | protected override void Configure(IObjectTypeDescriptor descriptor) 25 | { 26 | descriptor.BindFieldsImplicitly(); 27 | descriptor.Key("sku package") 28 | .ResolveReferenceWith(t => GetProductByPackage(default!, default!, default!)); 29 | } 30 | 31 | private static DeprecatedProduct? GetProductByPackage( 32 | string sku, 33 | string package, 34 | Data repository) 35 | => repository.DeprecatedProducts.FirstOrDefault( 36 | t => t.Sku.Equals(sku) && 37 | t.Package.Equals(package)); 38 | } -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/ProductResearch.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | public class ProductResearch 6 | { 7 | 8 | public ProductResearch(CaseStudy study, string? outcome) 9 | { 10 | Study = study; 11 | Outcome = outcome; 12 | } 13 | 14 | public CaseStudy Study { get; } 15 | public string? Outcome { get; } 16 | } 17 | 18 | public class ProductResearchType : ObjectType 19 | { 20 | protected override void Configure(IObjectTypeDescriptor descriptor) 21 | { 22 | descriptor.BindFieldsImplicitly(); 23 | descriptor.Field(pr => pr.Study).Type>(); 24 | descriptor.Key("study { caseNumber }") 25 | .ResolveReferenceWith(pr => GetProductReasearchByCaseNumber(default!, default!)); 26 | } 27 | 28 | // TODO [Map] 29 | private static ProductResearch? GetProductReasearchByCaseNumber( 30 | [Map("study.caseNumber")] string caseNumber, 31 | Data repository) 32 | { 33 | return repository.ProductResearches.FirstOrDefault( 34 | r => r.Study.CaseNumber.Equals(caseNumber)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Federation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Execution.Configuration; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Provides extensions to . 7 | /// 8 | public static class ApolloFederationRequestExecutorBuilderExtensions 9 | { 10 | /// 11 | /// Adds support for Apollo Federation to the schema. 12 | /// 13 | /// 14 | /// The . 15 | /// 16 | /// 17 | /// Returns the . 18 | /// 19 | /// 20 | /// The is null. 21 | /// 22 | public static IRequestExecutorBuilder AddApolloFederation( 23 | this IRequestExecutorBuilder builder) 24 | { 25 | if (builder is null) 26 | { 27 | throw new ArgumentNullException(nameof(builder)); 28 | } 29 | 30 | return builder.ConfigureSchema(s => ApolloFederationSchemaBuilderExtensions.AddApolloFederation(s)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Federation/Two/InterfaceObjectDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @interfaceObject on OBJECT 9 | /// 10 | /// 11 | /// The @interfaceObject directive provides meta information to the router that this entity 12 | /// type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality 13 | /// of an interface across the supergraph without having to implement (or even be aware of) all its implementing types. 14 | /// 15 | /// type Foo @interfaceObject @key(fields: "ids") { 16 | /// id: ID! 17 | /// newCommonField: String 18 | /// } 19 | /// 20 | /// 21 | public sealed class InterfaceObjectirectiveType : DirectiveType 22 | { 23 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 24 | => descriptor 25 | .Name(WellKnownTypeNames.InterfaceObject) 26 | .Description(FederationResources.InterfaceObjectDirective_Description) 27 | .Location(DirectiveLocation.Object); 28 | } 29 | -------------------------------------------------------------------------------- /Federation/ApolloAuthenticatedAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// 5 | /// directive @authenticated on 6 | /// ENUM 7 | /// | FIELD_DEFINITION 8 | /// | INTERFACE 9 | /// | OBJECT 10 | /// | SCALAR 11 | /// 12 | /// 13 | /// The @authenticated directive is used to indicate that the target element is accessible only to the authenticated supergraph users. 14 | /// For more granular access control, see the RequiresScopeDirectiveType directive usage. 15 | /// Refer to the Apollo Router article 16 | /// for additional details. 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID 20 | /// description: String @authenticated 21 | /// } 22 | /// 23 | /// 24 | [AttributeUsage( 25 | AttributeTargets.Class 26 | | AttributeTargets.Enum 27 | | AttributeTargets.Interface 28 | | AttributeTargets.Method 29 | | AttributeTargets.Property 30 | | AttributeTargets.Struct 31 | )] 32 | public sealed class ApolloAuthenticatedAttribute : Attribute 33 | { 34 | } 35 | -------------------------------------------------------------------------------- /Federation/Two/Contact.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 2 | 3 | public sealed class Contact 4 | { 5 | /// 6 | /// Initializes new instance of 7 | /// 8 | /// 9 | /// Contact title of the subgraph owner 10 | /// 11 | /// 12 | /// URL where the subgraph's owner can be reached 13 | /// 14 | /// 15 | /// Other relevant notes can be included here; supports markdown links 16 | /// 17 | public Contact(string name, string? url, string? description) 18 | { 19 | Name = name; 20 | Url = url; 21 | Description = description; 22 | } 23 | 24 | /// 25 | /// Gets the contact title of the subgraph owner. 26 | /// 27 | public string Name { get; } 28 | 29 | /// 30 | /// Gets the url where the subgraph's owner can be reached. 31 | /// 32 | public string? Url { get; } 33 | 34 | /// 35 | /// Gets other relevant notes about subgraph contact information. Can include markdown links. 36 | /// 37 | public string? Description { get; } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023- Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 4 | Copyright (c) 2021 ChilliCream Inc. (Michael & Rafael Staib) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /Federation/Constants/WellKnownTypeNames.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation.Constants; 2 | 3 | internal static class WellKnownTypeNames 4 | { 5 | public const string AuthenticatedDirective = "authenticated"; 6 | public const string ContactDirective = "contact"; 7 | public const string ComposeDirective = "composeDirective"; 8 | public const string Extends = "extends"; 9 | public const string External = "external"; 10 | public const string Inaccessible = "inaccessible"; 11 | public const string InterfaceObject = "interfaceObject"; 12 | public const string Key = "key"; 13 | public const string Link = "link"; 14 | public const string Override = "override"; 15 | public const string Provides = "provides"; 16 | public const string Requires = "requires"; 17 | public const string RequiresScopes = "requiresScopes"; 18 | public const string Shareable = "shareable"; 19 | public const string Tag = "tag"; 20 | public const string FieldSet = "_FieldSet"; 21 | public const string FieldSetV2 = "FieldSet"; 22 | public const string Scope = "Scope"; 23 | public const string Any = "_Any"; 24 | public const string Entity = "_Entity"; 25 | public const string Service = "_Service"; 26 | } 27 | -------------------------------------------------------------------------------- /Federation/Representation.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Language; 2 | using HotChocolate.Utilities; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// A representation is a blob of data that is supposed to match 8 | /// the combined requirements of the fields requested on an entity. 9 | /// 10 | public sealed class Representation 11 | { 12 | /// 13 | /// Initializes a new instance of . 14 | /// 15 | /// 16 | /// The type name of the entity. 17 | /// 18 | /// 19 | /// The required data to resolve the data from an entity. 20 | /// 21 | public Representation(string typeName, ObjectValueNode data) 22 | { 23 | TypeName = typeName.EnsureGraphQLName(); 24 | Data = data ?? throw new ArgumentNullException(nameof(data)); 25 | } 26 | 27 | /// 28 | /// Gets the type name of the entity. 29 | /// 30 | /// 31 | public string TypeName { get; } 32 | 33 | /// 34 | /// Gets the required data to resolve the data from an entity. 35 | /// 36 | public ObjectValueNode Data { get; } 37 | } 38 | -------------------------------------------------------------------------------- /Federation/One/KeyDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | /// 7 | /// 8 | /// directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 9 | /// 10 | /// 11 | /// The @key directive is used to indicate a combination of fields that can be used to uniquely 12 | /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"), 13 | /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can 14 | /// be specified on a target type. 15 | /// 16 | /// type Foo @key(fields: "id") { 17 | /// id: ID! 18 | /// field: String 19 | /// } 20 | /// 21 | /// 22 | public sealed class KeyDirectiveType : DirectiveType 23 | { 24 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 25 | => descriptor 26 | .Name(WellKnownTypeNames.Key) 27 | .Description(FederationResources.KeyDirective_Description) 28 | .Location(DirectiveLocation.Object | DirectiveLocation.Interface) 29 | .Repeatable() 30 | .FieldsArgumentV1(); 31 | } 32 | -------------------------------------------------------------------------------- /Federation/Two/OverrideDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @override(from: String!) on FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @override directive is used to indicate that the current subgraph is taking 12 | /// responsibility for resolving the marked field away from the subgraph specified in the from 13 | /// argument. Name of the subgraph to be overridden has to match the name of the subgraph that 14 | /// was used to publish their schema. 15 | /// 16 | /// type Foo @key(fields: "id") { 17 | /// id: ID! 18 | /// description: String @override(from: "BarSubgraph") 19 | /// } 20 | /// 21 | /// 22 | public sealed class OverrideDirectiveType : DirectiveType 23 | { 24 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 25 | => descriptor 26 | .Name(WellKnownTypeNames.Override) 27 | .Description(FederationResources.OverrideDirective_Description) 28 | .Location(DirectiveLocation.FieldDefinition) 29 | .Argument(WellKnownArgumentNames.From) 30 | .Type>(); 31 | } 32 | -------------------------------------------------------------------------------- /Federation/ShareableAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// 5 | /// directive @shareable repeatable on FIELD_DEFINITION | OBJECT 6 | /// 7 | /// 8 | /// The @shareable directive indicates that given object and/or field can be resolved by multiple subgraphs. 9 | /// If an object is marked as @shareable then all its fields are automatically shareable without the need 10 | /// for explicitly marking them with @shareable directive. All fields referenced from @key directive are 11 | /// automatically shareable as well. 12 | /// 13 | /// NOTE: Only available in Federation v2 14 | /// 15 | /// type Foo @key(fields: "id") { 16 | /// id: ID! # shareable because id is a key field 17 | /// name: String # non-shareable 18 | /// description: String @shareable # shareable 19 | /// } 20 | /// 21 | /// type Bar @shareable { 22 | /// description: String # shareable because User is marked shareable 23 | /// name: String # shareable because User is marked shareable 24 | /// } 25 | /// 26 | /// 27 | [AttributeUsage( 28 | AttributeTargets.Class 29 | | AttributeTargets.Struct 30 | | AttributeTargets.Method 31 | | AttributeTargets.Property 32 | )] 33 | public sealed class ShareableAttribute : Attribute 34 | { 35 | } 36 | -------------------------------------------------------------------------------- /Federation/One/FederationSchemaPrinter.InputType.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HotChocolate.Language; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | public static partial class FederationSchemaPrinter 7 | { 8 | private static InputObjectTypeDefinitionNode SerializeInputObjectType( 9 | InputObjectType inputObjectType, 10 | Context context) 11 | { 12 | var directives = SerializeDirectives(inputObjectType.Directives, context); 13 | 14 | var fields = inputObjectType.Fields 15 | .Select(t => SerializeInputField(t, context)) 16 | .ToList(); 17 | 18 | return new InputObjectTypeDefinitionNode( 19 | null, 20 | new NameNode(inputObjectType.Name), 21 | SerializeDescription(inputObjectType.Description), 22 | directives, 23 | fields); 24 | } 25 | 26 | private static InputValueDefinitionNode SerializeInputField( 27 | IInputField inputValue, 28 | Context context) 29 | { 30 | var directives = SerializeDirectives(inputValue.Directives, context); 31 | 32 | return new InputValueDefinitionNode( 33 | null, 34 | new NameNode(inputValue.Name), 35 | SerializeDescription(inputValue.Description), 36 | SerializeType(inputValue.Type, context), 37 | inputValue.DefaultValue, 38 | directives); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Federation/Descriptors/ReferenceResolverDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using HotChocolate.Resolvers; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Descriptors; 5 | 6 | /// 7 | /// A reference resolver definition. 8 | /// 9 | public readonly struct ReferenceResolverDefinition 10 | { 11 | /// 12 | /// Initializes a new instance of . 13 | /// 14 | /// 15 | /// The field resolver. 16 | /// 17 | /// 18 | /// Specifies the representation paths that are required for this resolver. 19 | /// 20 | /// 21 | /// The is null. 22 | /// 23 | public ReferenceResolverDefinition( 24 | FieldResolverDelegate resolver, 25 | IReadOnlyList? required = default) 26 | { 27 | Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); 28 | Required = required ?? Array.Empty(); 29 | } 30 | 31 | /// 32 | /// The reference resolver. 33 | /// 34 | public FieldResolverDelegate Resolver { get; } 35 | 36 | /// 37 | /// The representation paths that are required for this resolver. 38 | /// 39 | public IReadOnlyList Required { get; } 40 | } 41 | -------------------------------------------------------------------------------- /Federation/Two/ComposeDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @composeDirective(name: String!) repeatable on SCHEMA 9 | /// 10 | /// 11 | /// By default, Supergraph schema excludes all custom directives. The @composeDirective is used to specify 12 | /// custom directives that should be exposed in the Supergraph schema. 13 | /// 14 | /// NOTE: Only available in Federation v2 15 | /// 16 | /// extend schema @composeDirective(name: "@custom") 17 | /// @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"]) 18 | /// @link(url: "https://myspecs.dev/custom/v1.0", import: ["@custom"]) 19 | /// 20 | /// directive @custom on FIELD_DEFINITION 21 | /// 22 | /// type Query { 23 | /// helloWorld: String! @custom 24 | /// } 25 | /// 26 | /// 27 | public sealed class ComposeDirectiveType : DirectiveType 28 | { 29 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 30 | => descriptor 31 | .Name(WellKnownTypeNames.ComposeDirective) 32 | .Description(FederationResources.ComposeDirective_Description) 33 | .Location(DirectiveLocation.Schema) 34 | .Argument(WellKnownArgumentNames.Name) 35 | .Type>(); 36 | } 37 | -------------------------------------------------------------------------------- /Federation/One/FederationSchemaPrinter.DirectiveType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using HotChocolate.Language; 4 | using DirectiveLocationType = HotChocolate.Types.DirectiveLocation; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation.One; 7 | 8 | public static partial class FederationSchemaPrinter 9 | { 10 | private static DirectiveDefinitionNode SerializeDirectiveTypeDefinition( 11 | DirectiveType directiveType, 12 | Context context) 13 | { 14 | var arguments = directiveType.Arguments 15 | .Select(a => SerializeInputField(a, context)) 16 | .ToList(); 17 | 18 | var locations = DirectiveLocations(directiveType.Locations) 19 | .Select(l => new NameNode(DirectiveLocationExtensions.MapDirectiveLocation(l).ToString())) 20 | .ToList(); 21 | 22 | return new DirectiveDefinitionNode 23 | ( 24 | null, 25 | new NameNode(directiveType.Name), 26 | SerializeDescription(directiveType.Description), 27 | directiveType.IsRepeatable, 28 | arguments, 29 | locations 30 | ); 31 | } 32 | 33 | private static IEnumerable DirectiveLocations(DirectiveLocationType locations) 34 | { 35 | foreach (DirectiveLocationType value in Enum.GetValues(locations.GetType())) 36 | if (locations.HasFlag(value)) 37 | yield return value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Compatibility/CodeFirst/Types/Data.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Data 4 | { 5 | private static ProductDimension Dimension = new("small", 1, "kg"); 6 | private static User DefaultUser = new("support@apollographql.com", "Jane Smith"); 7 | private static ProductResearch FederationStudy = new(new CaseStudy("1234", "Federation Study"), null); 8 | private static ProductResearch StudioStudy = new(new CaseStudy("1235", "Studio Study"), null); 9 | 10 | public List Users { get; } = new List { 11 | DefaultUser 12 | }; 13 | 14 | public List ProductResearches { get; } = new List 15 | { 16 | FederationStudy, 17 | StudioStudy 18 | }; 19 | 20 | public List Products { get; } = new List 21 | { 22 | new("apollo-federation", "federation", "@apollo/federation", new ProductVariation("OSS"), Dimension, DefaultUser, null, new List { FederationStudy }), 23 | new("apollo-studio", "studio", string.Empty, new ProductVariation("platform"), Dimension, DefaultUser, null, new List { StudioStudy }) 24 | }; 25 | 26 | public List DeprecatedProducts { get; } = new List 27 | { 28 | new ("apollo-federation-v1", "@apollo/federation-v1", "Migrate to Federation V2", DefaultUser) 29 | }; 30 | 31 | public List Inventories() => new List { 32 | new ("apollo-oss") 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/Data.cs: -------------------------------------------------------------------------------- 1 | namespace Products; 2 | 3 | public class Data 4 | { 5 | private static ProductDimension Dimension = new("small", 1, "kg"); 6 | private static User DefaultUser = new("support@apollographql.com", "Jane Smith"); 7 | private static ProductResearch FederationStudy = new(new CaseStudy("1234", "Federation Study"), null); 8 | private static ProductResearch StudioStudy = new(new CaseStudy("1235", "Studio Study"), null); 9 | 10 | public List Users { get; } = new List { 11 | DefaultUser 12 | }; 13 | 14 | public List ProductResearches { get; } = new List 15 | { 16 | FederationStudy, 17 | StudioStudy 18 | }; 19 | 20 | public List Products { get; } = new List 21 | { 22 | new("apollo-federation", "federation", "@apollo/federation", new ProductVariation("OSS"), Dimension, DefaultUser, null, new List { FederationStudy }), 23 | new("apollo-studio", "studio", string.Empty, new ProductVariation("platform"), Dimension, DefaultUser, null, new List { StudioStudy }) 24 | }; 25 | 26 | public List DeprecatedProducts { get; } = new List 27 | { 28 | new ("apollo-federation-v1", "@apollo/federation-v1", "Migrate to Federation V2", DefaultUser) 29 | }; 30 | 31 | public List Inventories() => new List { 32 | new ("apollo-oss") 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /Federation/ExternalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using HotChocolate.Types.Descriptors; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// 8 | /// # federation v1 definition 9 | /// directive @external on FIELD_DEFINITION 10 | /// 11 | /// # federation v2 definition 12 | /// directive @external on OBJECT | FIELD_DEFINITION 13 | /// 14 | /// 15 | /// The @external directive is used to mark a field as owned by another service. 16 | /// This allows service A to use fields from service B while also knowing at runtime 17 | /// the types of that field. All the external fields should either be referenced from the @key, 18 | /// @requires or @provides directives field sets. 19 | /// 20 | /// Due to the smart merging of entity types, Federation v2 no longer requires @external directive 21 | /// on @key fields and can be safely omitted from the schema. @external directive is only required 22 | /// on fields referenced by the @requires and @provides directive. 23 | /// 24 | /// 25 | /// type Foo @key(fields: "id") { 26 | /// id: ID! 27 | /// remoteField: String @external 28 | /// localField: String @requires(fields: "remoteField") 29 | /// } 30 | /// 31 | /// 32 | public sealed class ExternalAttribute : ObjectFieldDescriptorAttribute 33 | { 34 | protected override void OnConfigure( 35 | IDescriptorContext context, 36 | IObjectFieldDescriptor descriptor, 37 | MemberInfo member) 38 | => descriptor.External(); 39 | } 40 | -------------------------------------------------------------------------------- /Federation/Two/RequiresScopesDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @@requiresScopes(scopes: [[Scope!]!]!) on 9 | /// ENUM 10 | /// | FIELD_DEFINITION 11 | /// | INTERFACE 12 | /// | OBJECT 13 | /// | SCALAR 14 | /// 15 | /// 16 | /// Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes. 17 | /// Refer to the Apollo Router article for additional details. 18 | /// 19 | /// type Foo @key(fields: "id") { 20 | /// id: ID 21 | /// description: String @requiresScopes(scopes: [["scope1"]]) 22 | /// } 23 | /// 24 | /// 25 | public sealed class RequiresScopesDirectiveType : DirectiveType 26 | { 27 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 28 | => descriptor 29 | .BindArgumentsImplicitly() 30 | .Name(WellKnownTypeNames.RequiresScopes) 31 | .Description(FederationResources.RequiresScopesDirective_Description) 32 | .Location(DirectiveLocation.Enum | DirectiveLocation.FieldDefinition | DirectiveLocation.Interface | DirectiveLocation.Object | DirectiveLocation.Scalar); 33 | } 34 | -------------------------------------------------------------------------------- /Federation/One/ExternalDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | /// 7 | /// 8 | /// directive @external on FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @external directive is used to mark a field as owned by another service. 12 | /// This allows service A to use fields from service B while also knowing at runtime 13 | /// the types of that field. All the external fields should either be referenced from the @key, 14 | /// @requires or @provides directives field sets. 15 | /// 16 | /// Due to the smart merging of entity types, Federation v2 no longer requires @external directive 17 | /// on @key fields and can be safely omitted from the schema. @external directive is only required 18 | /// on fields referenced by the @requires and @provides directive. 19 | /// 20 | /// 21 | /// type Foo @key(fields: "id") { 22 | /// id: ID! 23 | /// remoteField: String @external 24 | /// localField: String @requires(fields: "remoteField") 25 | /// } 26 | /// 27 | /// 28 | public sealed class ExternalDirectiveType : DirectiveType 29 | { 30 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 31 | => descriptor 32 | .Name(WellKnownTypeNames.External) 33 | .Description(FederationResources.ExternalDirective_Description) 34 | .Location(DirectiveLocation.FieldDefinition); 35 | } 36 | -------------------------------------------------------------------------------- /Federation/One/RequiresDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | /// 7 | /// 8 | /// directive @requires(fields: _FieldSet!) on FIELD_DEFINITON 9 | /// 10 | /// 11 | /// The @requires directive is used to specify external (provided by other subgraphs) 12 | /// entity fields that are needed to resolve target field. It is used to develop a query plan where 13 | /// the required fields may not be needed by the client, but the service may need additional 14 | /// information from other subgraphs. Required fields specified in the directive field set should 15 | /// correspond to a valid field on the underlying GraphQL interface/object and should be instrumented 16 | /// with @external directive. 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID! 20 | /// # this field will be resolved from other subgraph 21 | /// remote: String @external 22 | /// local: String @requires(fields: "remote") 23 | /// } 24 | /// 25 | /// 26 | public sealed class RequiresDirectiveType : DirectiveType 27 | { 28 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 29 | => descriptor 30 | .Name(WellKnownTypeNames.Requires) 31 | .Description(FederationResources.RequiresDirective_Description) 32 | .Location(DirectiveLocation.FieldDefinition) 33 | .FieldsArgumentV1(); 34 | } 35 | -------------------------------------------------------------------------------- /Federation/Two/AuthenticatedDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @authenticated on 9 | /// ENUM 10 | /// | FIELD_DEFINITION 11 | /// | INTERFACE 12 | /// | OBJECT 13 | /// | SCALAR 14 | /// 15 | /// 16 | /// The @authenticated directive is used to indicate that the target element is accessible only to the authenticated supergraph users. 17 | /// For more granular access control, see the RequiresScopeDirectiveType directive usage. 18 | /// Refer to the Apollo Router article 19 | /// for additional details. 20 | /// 21 | /// type Foo @key(fields: "id") { 22 | /// id: ID 23 | /// description: String @authenticated 24 | /// } 25 | /// 26 | /// 27 | public sealed class AuthenticatedDirectiveType : DirectiveType 28 | { 29 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 30 | => descriptor 31 | .Name(WellKnownTypeNames.AuthenticatedDirective) 32 | .Description(FederationResources.AuthenticatedDirective_Description) 33 | .Location(DirectiveLocation.Enum | DirectiveLocation.FieldDefinition | DirectiveLocation.Interface | DirectiveLocation.Object | DirectiveLocation.Scalar); 34 | } 35 | -------------------------------------------------------------------------------- /Federation/Two/RequiresDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @requires(fields: FieldSet!) on FIELD_DEFINITON 9 | /// 10 | /// 11 | /// The @requires directive is used to specify external (provided by other subgraphs) 12 | /// entity fields that are needed to resolve target field. It is used to develop a query plan where 13 | /// the required fields may not be needed by the client, but the service may need additional 14 | /// information from other subgraphs. Required fields specified in the directive field set should 15 | /// correspond to a valid field on the underlying GraphQL interface/object and should be instrumented 16 | /// with @external directive. 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID! 20 | /// # this field will be resolved from other subgraph 21 | /// remote: String @external 22 | /// local: String @requires(fields: "remote") 23 | /// } 24 | /// 25 | /// 26 | public sealed class RequiresDirectiveType : DirectiveType 27 | { 28 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 29 | => descriptor 30 | .Name(WellKnownTypeNames.Requires) 31 | .Description(FederationResources.RequiresDirective_Description) 32 | .Location(DirectiveLocation.FieldDefinition) 33 | .FieldsArgumentV2(); 34 | } 35 | -------------------------------------------------------------------------------- /Federation/Two/ScopeType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | using HotChocolate.Language; 4 | 5 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 6 | 7 | /// 8 | /// The Scope scalar representing a JWT scope. Serializes as a string. 9 | /// 10 | public sealed class ScopeType : ScalarType 11 | { 12 | /// 13 | /// Initializes a new instance of . 14 | /// 15 | public ScopeType() : this(WellKnownTypeNames.Scope) 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of . 21 | /// 22 | /// 23 | /// Scalar name 24 | /// 25 | /// 26 | /// Defines if this scalar shall bind implicitly to . 27 | /// 28 | public ScopeType(string name, BindingBehavior bind = BindingBehavior.Explicit) 29 | : base(name, bind) 30 | { 31 | Description = FederationResources.ScopeType_Description; 32 | } 33 | 34 | protected override Scope ParseLiteral(StringValueNode valueSyntax) 35 | => new Scope(valueSyntax.Value); 36 | 37 | public override IValueNode ParseResult(object? resultValue) 38 | => ParseValue(resultValue); 39 | 40 | protected override StringValueNode ParseValue(Scope runtimeValue) 41 | => new StringValueNode(runtimeValue.Value); 42 | } 43 | -------------------------------------------------------------------------------- /Federation/Two/ExternalDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @external on OBJECT | FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @external directive is used to mark a field as owned by another service. 12 | /// This allows service A to use fields from service B while also knowing at runtime 13 | /// the types of that field. All the external fields should either be referenced from the @key, 14 | /// @requires or @provides directives field sets. 15 | /// 16 | /// Due to the smart merging of entity types, Federation v2 no longer requires @external directive 17 | /// on @key fields and can be safely omitted from the schema. @external directive is only required 18 | /// on fields referenced by the @requires and @provides directive. 19 | /// 20 | /// 21 | /// type Foo @key(fields: "id") { 22 | /// id: ID! 23 | /// remoteField: String @external 24 | /// localField: String @requires(fields: "remoteField") 25 | /// } 26 | /// 27 | /// 28 | public sealed class ExternalDirectiveType : DirectiveType 29 | { 30 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 31 | => descriptor 32 | .Name(WellKnownTypeNames.External) 33 | .Description(FederationResources.ExternalDirective_Description) 34 | .Location(DirectiveLocation.Object | DirectiveLocation.FieldDefinition); 35 | } 36 | -------------------------------------------------------------------------------- /Federation.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Threading; 5 | using HotChocolate; 6 | using HotChocolate.Resolvers; 7 | using HotChocolate.Types; 8 | using Moq; 9 | 10 | namespace ApolloGraphQL.HotChocolate.Federation; 11 | 12 | public static class TestHelper 13 | { 14 | public static IResolverContext CreateResolverContext( 15 | ISchema schema, 16 | ObjectType? type = null, 17 | Action>? additionalMockSetup = null) 18 | { 19 | var contextData = new Dictionary(); 20 | 21 | var mock = new Mock(MockBehavior.Strict); 22 | mock.SetupGet(c => c.RequestAborted).Returns(CancellationToken.None); 23 | mock.SetupGet(c => c.ContextData).Returns(contextData); 24 | mock.SetupProperty(c => c.ScopedContextData); 25 | mock.SetupProperty(c => c.LocalContextData); 26 | mock.Setup(c => c.Clone()).Returns(mock.Object); 27 | mock.SetupGet(c => c.Schema).Returns(schema); 28 | 29 | if (type is not null) 30 | { 31 | mock.SetupGet(c => c.ObjectType).Returns(type); 32 | } 33 | 34 | if (additionalMockSetup is not null) 35 | { 36 | additionalMockSetup(mock); 37 | } 38 | 39 | var context = mock.Object; 40 | context.ScopedContextData = ImmutableDictionary.Empty; 41 | context.LocalContextData = ImmutableDictionary.Empty; 42 | return context; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Federation/OverrideAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using HotChocolate.Types.Descriptors; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// 8 | /// directive @override(from: String!) on FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @override directive is used to indicate that the current subgraph is taking 12 | /// responsibility for resolving the marked field away from the subgraph specified in the from 13 | /// argument. Name of the subgraph to be overridden has to match the name of the subgraph that 14 | /// was used to publish their schema. 15 | /// 16 | /// NOTE: Only available in Federation v2 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID! 20 | /// description: String @override(from: "BarSubgraph") 21 | /// } 22 | /// 23 | /// 24 | public sealed class OverrideAttribute : ObjectFieldDescriptorAttribute 25 | { 26 | 27 | /// 28 | /// Initializes new instance of 29 | /// 30 | /// 31 | /// Name of the subgraph to be overridden 32 | /// 33 | public OverrideAttribute(string from) 34 | { 35 | From = from; 36 | } 37 | 38 | /// 39 | /// Get name of the subgraph to be overridden. 40 | /// 41 | public string From { get; } 42 | 43 | protected override void OnConfigure( 44 | IDescriptorContext context, 45 | IObjectFieldDescriptor descriptor, 46 | MemberInfo member) 47 | { 48 | descriptor.Override(From); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Federation/RequiresScopesAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// 5 | /// directive @requiresScopes(scopes: [[Scope!]!]!) on 6 | /// ENUM 7 | /// | FIELD_DEFINITION 8 | /// | INTERFACE 9 | /// | OBJECT 10 | /// | SCALAR 11 | /// 12 | /// 13 | /// Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes. 14 | /// Refer to the Apollo Router article for additional details. 15 | /// 16 | /// type Foo @key(fields: "id") { 17 | /// id: ID 18 | /// description: String @requiresScopes(scopes: [["scope1"]]) 19 | /// } 20 | /// 21 | /// 22 | [AttributeUsage( 23 | AttributeTargets.Class 24 | | AttributeTargets.Enum 25 | | AttributeTargets.Interface 26 | | AttributeTargets.Method 27 | | AttributeTargets.Property 28 | | AttributeTargets.Struct, 29 | AllowMultiple = true 30 | )] 31 | public sealed class RequiresScopesAttribute : Attribute 32 | { 33 | /// 34 | /// Initializes new instance of 35 | /// 36 | /// 37 | /// Array of required JWT scopes. 38 | /// 39 | public RequiresScopesAttribute(string[] scopes) 40 | { 41 | Scopes = scopes; 42 | } 43 | 44 | /// 45 | /// Retrieves array of required JWT scopes. 46 | /// 47 | public string[] Scopes { get; } 48 | } 49 | -------------------------------------------------------------------------------- /Federation/ApolloTagAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// 5 | /// directive @tag(name: String!) repeatable on FIELD_DEFINITION 6 | /// | OBJECT 7 | /// | INTERFACE 8 | /// | UNION 9 | /// | ENUM 10 | /// | ENUM_VALUE 11 | /// | SCALAR 12 | /// | INPUT_OBJECT 13 | /// | INPUT_FIELD_DEFINITION 14 | /// | ARGUMENT_DEFINITION 15 | /// 16 | /// 17 | /// The @tag directive allows users to annotate fields and types with additional metadata information. 18 | /// Tagging is commonly used for creating variants of the supergraph using contracts. 19 | /// 20 | /// NOTE: Only available in Federation v2 21 | /// 22 | /// type Foo @tag(name: "internal") { 23 | /// id: ID! 24 | /// name: String 25 | /// } 26 | /// 27 | /// 28 | [AttributeUsage( 29 | AttributeTargets.Class 30 | | AttributeTargets.Enum 31 | | AttributeTargets.Field 32 | | AttributeTargets.Interface 33 | | AttributeTargets.Method 34 | | AttributeTargets.Parameter 35 | | AttributeTargets.Property 36 | | AttributeTargets.Struct, 37 | AllowMultiple = true 38 | )] 39 | public sealed class ApolloTagAttribute : Attribute 40 | { 41 | 42 | /// 43 | /// Initializes new instance of 44 | /// 45 | /// 46 | /// Tag metadata value 47 | /// 48 | public ApolloTagAttribute(string name) 49 | { 50 | Name = name; 51 | } 52 | 53 | /// 54 | /// Retrieves tag metadata value 55 | /// 56 | public string Name { get; } 57 | } 58 | -------------------------------------------------------------------------------- /Federation.Tests/ApolloGraphQL.HotChocolate.Federation.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | ApolloGraphQL.HotChocolate.Federation.Tests 5 | ApolloGraphQL.HotChocolate.Federation 6 | 7 | 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Federation/ServiceType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | using HotChocolate.Resolvers; 4 | using FederationSchemaPrinter = ApolloGraphQL.HotChocolate.Federation.One.FederationSchemaPrinter; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation; 7 | 8 | /// 9 | /// A new object type called _Service must be created. 10 | /// This type must have an sdl: String! field which exposes the SDL of the service's schema. 11 | /// 12 | /// This SDL (schema definition language) is a printed version of the service's 13 | /// schema including the annotations of federation directives. This SDL does not 14 | /// include the additions of the federation spec. 15 | /// 16 | public class ServiceType : ObjectType 17 | { 18 | public ServiceType(bool isV2 = false) 19 | { 20 | IsV2 = isV2; 21 | } 22 | 23 | public bool IsV2 { get; } 24 | 25 | protected override void Configure(IObjectTypeDescriptor descriptor) 26 | => descriptor 27 | .Name(WellKnownTypeNames.Service) 28 | .Description(FederationResources.ServiceType_Description) 29 | .Field(WellKnownFieldNames.Sdl) 30 | .Type>() 31 | .Resolve(resolver => PrintSchemaSDL(resolver, IsV2)); 32 | 33 | private string PrintSchemaSDL(IResolverContext resolver, bool isV2) 34 | { 35 | if (isV2) 36 | { 37 | return SchemaPrinter.Print(resolver.Schema); 38 | } 39 | else 40 | { 41 | return FederationSchemaPrinter.Print(resolver.Schema); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Federation/InaccessibleAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace ApolloGraphQL.HotChocolate.Federation; 2 | 3 | /// 4 | /// 5 | /// directive @inaccessible on FIELD_DEFINITION 6 | /// | OBJECT 7 | /// | INTERFACE 8 | /// | UNION 9 | /// | ENUM 10 | /// | ENUM_VALUE 11 | /// | SCALAR 12 | /// | INPUT_OBJECT 13 | /// | INPUT_FIELD_DEFINITION 14 | /// | ARGUMENT_DEFINITION 15 | /// 16 | /// 17 | /// The @inaccessible directive is used to mark location within schema as inaccessible 18 | /// from the GraphQL Router. Applying @inaccessible directive on a type is equivalent of applying 19 | /// it on all type fields. 20 | /// 21 | /// While @inaccessible fields are not exposed by the router to the clients, they are still available 22 | /// for query plans and can be referenced from @key and @requires directives. This allows you to not 23 | /// expose sensitive fields to your clients but still make them available for computations. 24 | /// Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple 25 | /// subgraphs without breaking composition. 26 | /// 27 | /// NOTE: Only available in Federation v2 28 | /// 29 | /// type Foo @inaccessible { 30 | /// hiddenId: ID! 31 | /// hiddenField: String 32 | /// } 33 | /// 34 | /// 35 | [AttributeUsage( 36 | AttributeTargets.Class 37 | | AttributeTargets.Enum 38 | | AttributeTargets.Field 39 | | AttributeTargets.Interface 40 | | AttributeTargets.Method 41 | | AttributeTargets.Parameter 42 | | AttributeTargets.Property 43 | | AttributeTargets.Struct 44 | )] 45 | public sealed class InaccessibleAttribute : Attribute 46 | { 47 | } 48 | -------------------------------------------------------------------------------- /Federation/Two/ShareableDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @shareable repeatable on FIELD_DEFINITION | OBJECT 9 | /// 10 | /// 11 | /// The @shareable directive indicates that given object and/or field can be resolved by multiple subgraphs. 12 | /// If an object is marked as @shareable then all its fields are automatically shareable without the need 13 | /// for explicitly marking them with @shareable directive. All fields referenced from @key directive are 14 | /// automatically shareable as well. 15 | /// 16 | /// type Foo @key(fields: "id") { 17 | /// id: ID! # shareable because id is a key field 18 | /// name: String # non-shareable 19 | /// description: String @shareable # shareable 20 | /// } 21 | /// 22 | /// type Bar @shareable { 23 | /// description: String # shareable because User is marked shareable 24 | /// name: String # shareable because User is marked shareable 25 | /// } 26 | /// 27 | /// 28 | public sealed class ShareableDirectiveType : DirectiveType 29 | { 30 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 31 | => descriptor 32 | .Name(WellKnownTypeNames.Shareable) 33 | .Description(FederationResources.ShareableDirective_Description) 34 | .Location(DirectiveLocation.FieldDefinition | DirectiveLocation.Object) 35 | .Repeatable(); 36 | } 37 | -------------------------------------------------------------------------------- /Federation/Descriptors/IEntityResolverDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Descriptors; 5 | 6 | /// 7 | /// The entity descriptor allows to specify a reference resolver. 8 | /// 9 | public interface IEntityResolverDescriptor 10 | { 11 | /// 12 | /// Resolve an entity from its representation. 13 | /// 14 | /// 15 | /// The reference resolver. 16 | /// 17 | /// 18 | /// Returns the descriptor for configuration chaining. 19 | /// 20 | IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method); 21 | } 22 | 23 | /// 24 | /// The entity descriptor allows to specify a reference resolver. 25 | /// 26 | public interface IEntityResolverDescriptor 27 | { 28 | /// 29 | /// Resolve an entity from its representation. 30 | /// 31 | /// 32 | /// The reference resolver selector. 33 | /// 34 | /// 35 | /// Returns the descriptor for configuration chaining. 36 | /// 37 | IObjectTypeDescriptor ResolveReferenceWith( 38 | Expression> method); 39 | 40 | /// 41 | /// Resolve an entity from its representation. 42 | /// 43 | /// 44 | /// The reference resolver. 45 | /// 46 | /// 47 | /// Returns the descriptor for configuration chaining. 48 | /// 49 | IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method); 50 | } 51 | -------------------------------------------------------------------------------- /Federation.Tests/FederationTypesTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ApolloGraphQL.HotChocolate.Federation.One; 3 | using HotChocolate; 4 | using HotChocolate.Types; 5 | 6 | namespace ApolloGraphQL.HotChocolate.Federation; 7 | 8 | public class FederationTypesTestBase 9 | { 10 | protected ISchemaBuilder CreateSchemaBuilder() 11 | { 12 | return SchemaBuilder.New() 13 | .ModifyOptions(opt => 14 | { 15 | // tag is added by default 16 | opt.EnableTag = false; 17 | // this is set after we process our federated schema extensions 18 | if (opt.QueryTypeName is null) 19 | { 20 | opt.QueryTypeName = "Query"; 21 | } 22 | }); 23 | } 24 | 25 | protected ISchema CreateSchema(Action configure) 26 | { 27 | var builder = 28 | CreateSchemaBuilder() 29 | .AddQueryType( 30 | c => 31 | { 32 | c.Name("Query"); 33 | c.Field("foo").Type().Resolve("bar"); 34 | }); 35 | 36 | configure(builder); 37 | 38 | return builder.Create(); 39 | } 40 | 41 | protected void AssertDirectiveHasFieldsArgument(DirectiveType directive) 42 | { 43 | Assert.Collection( 44 | directive.Arguments, 45 | t => 46 | { 47 | Assert.Equal("fields", t.Name); 48 | Assert.IsType(Assert.IsType(t.Type).Type); 49 | } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Federation/Two/ContactDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @contact( 9 | /// "Contact title of the subgraph owner" 10 | /// name: String! 11 | /// "URL where the subgraph's owner can be reached" 12 | /// url: String 13 | /// "Other relevant notes can be included here; supports markdown links" 14 | /// description: String 15 | /// ) on SCHEMA 16 | /// 17 | /// 18 | /// Contact schema directive can be used to provide team contact information to your subgraph schema. This information is automatically parsed and displayed by Apollo Studio. 19 | /// See Subgraph Contact Information for additional details. 20 | /// 21 | /// NOTE: Only available in Federation v2 22 | /// 23 | /// 24 | /// 25 | /// schema @contact(description : "send urgent issues to [#oncall](https://yourteam.slack.com/archives/oncall).", name : "My Team Name", url : "https://myteam.slack.com/archives/teams-chat-room-url"){ 26 | /// query: Query 27 | /// } 28 | /// 29 | /// 30 | /// 31 | public sealed class ContactDirectiveType : DirectiveType 32 | { 33 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 34 | => descriptor 35 | .Name(WellKnownTypeNames.ContactDirective) 36 | .Description(FederationResources.ContactDirective_Description) 37 | .Location(DirectiveLocation.Schema); 38 | } 39 | -------------------------------------------------------------------------------- /Federation/One/ProvidesDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.One; 5 | 6 | /// 7 | /// 8 | /// directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @provides directive is a router optimization hint specifying field set that 12 | /// can be resolved locally at the given subgraph through this particular query path. This 13 | /// allows you to expose only a subset of fields from the underlying entity type to be selectable 14 | /// from the federated schema without the need to call other subgraphs. Provided fields specified 15 | /// in the directive field set should correspond to a valid field on the underlying GraphQL 16 | /// interface/object type. @provides directive can only be used on fields returning entities. 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID! 20 | /// # implies name field can be resolved locally 21 | /// bar: Bar @provides(fields: "name") 22 | /// # name fields are external 23 | /// # so will be fetched from other subgraphs 24 | /// bars: [Bar] 25 | /// } 26 | /// 27 | /// type Bar @key(fields: "id") { 28 | /// id: ID! 29 | /// name: String @external 30 | /// } 31 | /// 32 | /// 33 | public sealed class ProvidesDirectiveType : DirectiveType 34 | { 35 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 36 | => descriptor 37 | .Name(WellKnownTypeNames.Provides) 38 | .Description(FederationResources.ProvidesDirective_Description) 39 | .Location(DirectiveLocation.FieldDefinition) 40 | .FieldsArgumentV1(); 41 | } 42 | -------------------------------------------------------------------------------- /Federation/Two/ProvidesDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @provides(fields: FieldSet!) on FIELD_DEFINITION 9 | /// 10 | /// 11 | /// The @provides directive is a router optimization hint specifying field set that 12 | /// can be resolved locally at the given subgraph through this particular query path. This 13 | /// allows you to expose only a subset of fields from the underlying entity type to be selectable 14 | /// from the federated schema without the need to call other subgraphs. Provided fields specified 15 | /// in the directive field set should correspond to a valid field on the underlying GraphQL 16 | /// interface/object type. @provides directive can only be used on fields returning entities. 17 | /// 18 | /// type Foo @key(fields: "id") { 19 | /// id: ID! 20 | /// # implies name field can be resolved locally 21 | /// bar: Bar @provides(fields: "name") 22 | /// # name fields are external 23 | /// # so will be fetched from other subgraphs 24 | /// bars: [Bar] 25 | /// } 26 | /// 27 | /// type Bar @key(fields: "id") { 28 | /// id: ID! 29 | /// name: String @external 30 | /// } 31 | /// 32 | /// 33 | public sealed class ProvidesDirectiveType : DirectiveType 34 | { 35 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 36 | => descriptor 37 | .Name(WellKnownTypeNames.Provides) 38 | .Description(FederationResources.ProvidesDirective_Description) 39 | .Location(DirectiveLocation.FieldDefinition) 40 | .FieldsArgumentV2(); 41 | } 42 | -------------------------------------------------------------------------------- /Federation/Two/LinkDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using DirectiveLocationType = HotChocolate.Types.DirectiveLocation; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @link(url: String!, import: [String]) repeatable on SCHEMA 9 | /// 10 | /// 11 | /// The @link directive links definitions within the document to external schemas. 12 | /// External schemas are identified by their url, which optionally ends with a name and version with 13 | /// the following format: `{NAME}/v{MAJOR}.{MINOR}` 14 | /// 15 | /// By default, external types should be namespaced (prefixed with namespace__, e.g. key directive 16 | /// should be namespaced as federation__key) unless they are explicitly imported. We automatically 17 | /// import ALL federation directives to avoid the need for namespacing. 18 | /// 19 | /// NOTE: We currently DO NOT support full @link directive capability as it requires support for 20 | /// namespacing and renaming imports. This functionality may be added in the future releases. 21 | /// See @link specification for details. 22 | /// 23 | /// extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"]) 24 | /// 25 | /// type Query { 26 | /// foo: Foo! 27 | /// } 28 | /// 29 | /// type Foo @key(fields: "id") { 30 | /// id: ID! 31 | /// name: String 32 | /// } 33 | /// 34 | /// 35 | public sealed class LinkDirectiveType : DirectiveType 36 | { 37 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 38 | => descriptor 39 | .BindArgumentsImplicitly() 40 | .Name(WellKnownTypeNames.Link) 41 | .Location(DirectiveLocationType.Schema) 42 | .Repeatable(); 43 | } 44 | -------------------------------------------------------------------------------- /Federation/Two/TagDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @tag(name: String!) repeatable on FIELD_DEFINITION 9 | /// | OBJECT 10 | /// | INTERFACE 11 | /// | UNION 12 | /// | ENUM 13 | /// | ENUM_VALUE 14 | /// | SCALAR 15 | /// | INPUT_OBJECT 16 | /// | INPUT_FIELD_DEFINITION 17 | /// | ARGUMENT_DEFINITION 18 | /// 19 | /// 20 | /// The @tag directive allows users to annotate fields and types with additional metadata information. 21 | /// Tagging is commonly used for creating variants of the supergraph using contracts. 22 | /// 23 | /// type Foo @tag(name: "internal") { 24 | /// id: ID! 25 | /// name: String 26 | /// } 27 | /// 28 | /// 29 | public sealed class TagDirectiveType : DirectiveType 30 | { 31 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 32 | => descriptor 33 | .BindArgumentsImplicitly() 34 | .Name(WellKnownTypeNames.Tag) 35 | .Description(FederationResources.TagDirective_Description) 36 | .Location( 37 | DirectiveLocation.FieldDefinition 38 | | DirectiveLocation.Object 39 | | DirectiveLocation.Interface 40 | | DirectiveLocation.Union 41 | | DirectiveLocation.Enum 42 | | DirectiveLocation.EnumValue 43 | | DirectiveLocation.Scalar 44 | | DirectiveLocation.Scalar 45 | | DirectiveLocation.InputObject 46 | | DirectiveLocation.InputFieldDefinition 47 | | DirectiveLocation.ArgumentDefinition); 48 | } 49 | -------------------------------------------------------------------------------- /Federation/Two/KeyDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE 9 | /// 10 | /// 11 | /// The @key directive is used to indicate a combination of fields that can be used to uniquely 12 | /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"), 13 | /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can 14 | /// be specified on a target type. 15 | /// 16 | /// Keys can also be marked as non-resolvable which indicates to router that given entity should never be 17 | /// resolved within given subgraph. This allows your subgraph to still reference target entity without 18 | /// contributing any fields to it. 19 | /// 20 | /// type Foo @key(fields: "id") { 21 | /// id: ID! 22 | /// field: String 23 | /// bars: [Bar!]! 24 | /// } 25 | /// 26 | /// type Bar @key(fields: "id", resolvable: false) { 27 | /// id: ID! 28 | /// } 29 | /// 30 | /// 31 | public sealed class KeyDirectiveType : DirectiveType 32 | { 33 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 34 | => descriptor 35 | .Name(WellKnownTypeNames.Key) 36 | .Description(FederationResources.KeyDirective_Description) 37 | .Location(DirectiveLocation.Object | DirectiveLocation.Interface) 38 | .Repeatable() 39 | .FieldsArgumentV2() 40 | .Argument(WellKnownArgumentNames.Resolvable) 41 | .Type() 42 | .DefaultValue(true); 43 | } 44 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/AnnotationBased/Types/Product.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HotChocolate.Types.Relay; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.AnnotationBased.Types; 5 | 6 | [Key("id")] 7 | [Key("sku package")] 8 | [Key("sku variation { id }")] 9 | public class Product 10 | { 11 | public Product(string id, string sku, string package, string variation) 12 | { 13 | Id = id; 14 | Sku = sku; 15 | Package = package; 16 | Variation = new(variation); 17 | } 18 | 19 | [ID] 20 | public string Id { get; } 21 | 22 | public string? Sku { get; } 23 | 24 | public string? Package { get; } 25 | 26 | public ProductVariation? Variation { get; } 27 | 28 | public ProductDimension? Dimensions { get; } = new("1", 1); 29 | 30 | [Provides("totalProductsCreated")] 31 | public User? CreatedBy { get; } = new("support@apollographql.com", 1337); 32 | 33 | [ReferenceResolver] 34 | public static Product? GetProductById( 35 | string id, 36 | Data repository) 37 | => repository.Products.FirstOrDefault(t => t.Id.Equals(id)); 38 | 39 | [ReferenceResolver] 40 | public static Product? GetProductByPackage( 41 | string sku, 42 | string package, 43 | Data repository) 44 | => repository.Products.FirstOrDefault( 45 | t => (t.Sku?.Equals(sku) ?? false) && 46 | (t.Package?.Equals(package) ?? false)); 47 | 48 | [ReferenceResolver] 49 | public static Product? GetProductByVariation( 50 | string sku, 51 | [Map("variation.id")] string variationId, 52 | Data repository) 53 | => repository.Products.FirstOrDefault( 54 | t => (t.Sku?.Equals(sku) ?? false) && 55 | (t.Variation?.Id.Equals(variationId) ?? false)); 56 | } 57 | -------------------------------------------------------------------------------- /Federation/ApolloGraphQL.HotChocolate.Federation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0; net6.0 5 | ApolloGraphQL.HotChocolate.Federation 6 | ApolloGraphQL.HotChocolate.Federation 7 | ApolloGraphQL.HotChocolate.Federation 8 | Apollo Federation Subgraph support for HotChocolate. 9 | README.md 10 | true 11 | true 12 | snupkg 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | True 34 | True 35 | FederationResources.resx 36 | 37 | 38 | ResXFileCodeGenerator 39 | FederationResources.Designer.cs 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Federation/Helpers/ReferenceResolverHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using HotChocolate.Language; 4 | using HotChocolate.Resolvers; 5 | using static ApolloGraphQL.HotChocolate.Federation.Constants.WellKnownContextData; 6 | 7 | namespace ApolloGraphQL.HotChocolate.Federation.Helpers; 8 | 9 | /// 10 | /// This class provides helpers for the reference resolver expression generators. 11 | /// 12 | internal static class ReferenceResolverHelper 13 | { 14 | public static bool Matches( 15 | IResolverContext context, 16 | IReadOnlyList required) 17 | => ArgumentParser.Matches( 18 | context.GetLocalStateOrDefault(DataField)!, 19 | required); 20 | 21 | public static ValueTask ExecuteAsync( 22 | IResolverContext context, 23 | FieldResolverDelegate resolver) 24 | => resolver(context); 25 | 26 | public static ValueTask Invalid(IResolverContext context) 27 | { 28 | var representation = context.GetLocalStateOrDefault(DataField)?.ToString() ?? "null"; 29 | 30 | throw new GraphQLException( 31 | new Error( 32 | "The entity for the given representation could not be resolved.", 33 | extensions: new Dictionary 34 | { 35 | { nameof(representation), representation } 36 | })); 37 | } 38 | 39 | public static void TrySetExternal( 40 | ObjectType type, 41 | IValueNode data, 42 | object entity, 43 | string[] path, 44 | Action setValue) 45 | { 46 | if (ArgumentParser.TryGetValue(data, type, path, out var value)) 47 | { 48 | setValue(entity, value); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Federation.Tests/CertificationSchema/CodeFirst/Types/ProductType.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using HotChocolate.Types; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.CertificationSchema.CodeFirst.Types; 5 | 6 | public class ProductType : ObjectType 7 | { 8 | protected override void Configure(IObjectTypeDescriptor descriptor) 9 | { 10 | descriptor 11 | .Key("id") 12 | .ResolveReferenceWith(t => GetProductById(default!, default!)); 13 | 14 | descriptor 15 | .Field(t => t.Id) 16 | .ID(); 17 | 18 | descriptor 19 | .Key("sku package") 20 | .ResolveReferenceWith(t => GetProductByPackage(default!, default!, default!)); 21 | 22 | descriptor 23 | .Key("sku variation { id }") 24 | .ResolveReferenceWith(t => GetProductByVariation(default!, default!, default!)); 25 | 26 | descriptor 27 | .Field(t => t.CreatedBy) 28 | .Provides("totalProductsCreated") 29 | .Type>(); 30 | } 31 | 32 | private static Product? GetProductById( 33 | string id, 34 | Data repository) 35 | => repository.Products.FirstOrDefault(t => t.Id.Equals(id)); 36 | 37 | private static Product? GetProductByPackage( 38 | string sku, 39 | string package, 40 | Data repository) 41 | => repository.Products.FirstOrDefault( 42 | t => (t.Sku?.Equals(sku) ?? false) && 43 | (t.Package?.Equals(package) ?? false)); 44 | 45 | private static Product? GetProductByVariation( 46 | string sku, 47 | [Map("variation.id")] string variationId, 48 | Data repository) 49 | => repository.Products.FirstOrDefault( 50 | t => (t.Sku?.Equals(sku) ?? false) && 51 | (t.Variation?.Id.Equals(variationId) ?? false)); 52 | } 53 | -------------------------------------------------------------------------------- /Federation/Two/FederatedSchema.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Configuration; 2 | using HotChocolate.Types.Descriptors; 3 | using HotChocolate.Types.Descriptors.Definitions; 4 | 5 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 6 | 7 | /// 8 | /// Apollo Federation v2 base schema object that allows users to apply custom schema directives (e.g. @composeDirective) 9 | /// 10 | public class FederatedSchema : Schema 11 | { 12 | /// 13 | /// Initializes new instance of 14 | /// 15 | /// 16 | /// Supported Apollo Federation version 17 | /// 18 | public FederatedSchema(FederationVersion version = FederationVersion.FEDERATION_25) 19 | { 20 | FederationVersion = version; 21 | } 22 | 23 | /// 24 | /// Retrieve supported Apollo Federation version 25 | /// 26 | public FederationVersion FederationVersion { get; } 27 | 28 | private IDescriptorContext _context = default!; 29 | protected override void OnAfterInitialize(ITypeDiscoveryContext context, DefinitionBase definition) 30 | { 31 | base.OnAfterInitialize(context, definition); 32 | _context = context.DescriptorContext; 33 | } 34 | 35 | protected override void Configure(ISchemaTypeDescriptor descriptor) 36 | { 37 | var schemaType = this.GetType(); 38 | if (schemaType.IsDefined(typeof(SchemaTypeDescriptorAttribute), true)) 39 | { 40 | foreach (var attribute in schemaType.GetCustomAttributes(true)) 41 | { 42 | if (attribute is SchemaTypeDescriptorAttribute casted) 43 | { 44 | casted.OnConfigure(_context, descriptor, schemaType); 45 | } 46 | } 47 | } 48 | var link = FederationUtils.GetFederationLink(FederationVersion); 49 | descriptor.Link(link.Url, link.Import?.ToArray()); 50 | } 51 | } -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/Product.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("id")] 6 | [Key("sku package")] 7 | [Key("sku variation { id }")] 8 | [Custom] 9 | public class Product 10 | { 11 | public Product(string id, string? sku, string? package, ProductVariation? variation, ProductDimension? dimensions, User? createdBy, string? notes, List research) 12 | { 13 | Id = id; 14 | Sku = sku; 15 | Package = package; 16 | Variation = variation; 17 | Dimensions = dimensions; 18 | CreatedBy = createdBy; 19 | Notes = notes; 20 | Research = research; 21 | } 22 | 23 | [ID] 24 | public string Id { get; } 25 | 26 | public string? Sku { get; } 27 | 28 | public string? Package { get; } 29 | 30 | public ProductVariation? Variation { get; } 31 | 32 | public ProductDimension? Dimensions { get; } 33 | 34 | [Provides("totalProductsCreated")] 35 | public User? CreatedBy { get; } 36 | 37 | [ApolloTag("internal")] 38 | public string? Notes { get; } 39 | 40 | public List Research { get; } 41 | 42 | [ReferenceResolver] 43 | public static Product? GetProductById( 44 | string id, 45 | Data repository) 46 | => repository.Products.FirstOrDefault(t => id.Equals(t.Id)); 47 | 48 | [ReferenceResolver] 49 | public static Product? GetProductByPackage( 50 | string sku, 51 | string package, 52 | Data repository) 53 | => repository.Products.FirstOrDefault( 54 | t => sku.Equals(t.Sku) && package.Equals(t.Package)); 55 | 56 | [ReferenceResolver] 57 | public static Product? GetProductByVariation( 58 | string sku, 59 | [Map("variation.id")] string variationId, 60 | Data repository) 61 | => repository.Products.FirstOrDefault( 62 | t => sku.Equals(t.Sku) && variationId.Equals(t.Variation?.Id) 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/compatibility.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request Federation Compatibility Check 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | token: 7 | required: false 8 | 9 | jobs: 10 | compatibility-annotationbased: 11 | timeout-minutes: 30 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: 7.0.x 20 | - name: Restore dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --no-restore 24 | - name: Generate schema file 25 | run: dotnet run --project Compatibility/AnnotationBased/AnnotationBased.csproj schema export --output products.graphql 26 | - name: Compatibility Test 27 | uses: apollographql/federation-subgraph-compatibility@v2 28 | with: 29 | compose: 'Compatibility/AnnotationBased/docker-compose.yaml' 30 | schema: 'Compatibility/AnnotationBased/products.graphql' 31 | debug: true 32 | token: ${{ secrets.token }} 33 | 34 | compatibility-codefirst: 35 | timeout-minutes: 30 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Setup .NET 41 | uses: actions/setup-dotnet@v4 42 | with: 43 | dotnet-version: 7.0.x 44 | - name: Restore dependencies 45 | run: dotnet restore 46 | - name: Build 47 | run: dotnet build --no-restore 48 | - name: Generate schema file 49 | run: dotnet run --project Compatibility/CodeFirst/CodeFirst.csproj schema export --output products.graphql 50 | - name: Compatibility Test 51 | uses: apollographql/federation-subgraph-compatibility@v2 52 | with: 53 | compose: 'Compatibility/CodeFirst/docker-compose.yaml' 54 | schema: 'Compatibility/CodeFirst/products.graphql' 55 | debug: true 56 | token: ${{ secrets.token }} 57 | -------------------------------------------------------------------------------- /Federation/ComposeDirectiveAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | using static ApolloGraphQL.HotChocolate.Federation.ThrowHelper; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// 8 | /// directive @composeDirective(name: String!) repeatable on SCHEMA 9 | /// 10 | /// 11 | /// By default, Supergraph schema excludes all custom directives. The @composeDirective is used to specify 12 | /// custom directives that should be exposed in the Supergraph schema. 13 | /// 14 | /// NOTE: Only available in Federation v2 15 | /// 16 | /// extend schema @composeDirective(name: "@custom") 17 | /// @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"]) 18 | /// @link(url: "https://myspecs.dev/custom/v1.0", import: ["@custom"]) 19 | /// 20 | /// directive @custom on FIELD_DEFINITION 21 | /// 22 | /// type Query { 23 | /// helloWorld: String! @custom 24 | /// } 25 | /// 26 | /// 27 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = true)] 28 | public sealed class ComposeDirectiveAttribute : SchemaTypeDescriptorAttribute 29 | { 30 | /// 31 | /// Initializes new instance of 32 | /// 33 | /// 34 | /// Name of the directive that should be preserved in the supergraph composition. 35 | /// 36 | public ComposeDirectiveAttribute(string name) 37 | { 38 | Name = name; 39 | } 40 | 41 | /// 42 | /// Gets the composed directive name. 43 | /// 44 | public string Name { get; } 45 | 46 | public override void OnConfigure(IDescriptorContext context, ISchemaTypeDescriptor descriptor, Type type) 47 | { 48 | if (string.IsNullOrEmpty(Name)) 49 | { 50 | throw ComposeDirective_Name_CannotBeEmpty(type); 51 | } 52 | descriptor.ComposeDirective(Name); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Federation/Extensions/ApolloFederationSchemaBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | using ApolloGraphQL.HotChocolate.Federation.One; 3 | using AnyType = ApolloGraphQL.HotChocolate.Federation.AnyType; 4 | 5 | namespace HotChocolate; 6 | 7 | /// 8 | /// Provides extensions to . 9 | /// 10 | public static class ApolloFederationSchemaBuilderExtensions 11 | { 12 | /// 13 | /// Adds support for Apollo Federation to the schema. 14 | /// 15 | /// 16 | /// The . 17 | /// 18 | /// 19 | /// Returns the . 20 | /// 21 | /// 22 | /// The is null. 23 | /// 24 | public static ISchemaBuilder AddApolloFederation( 25 | this ISchemaBuilder builder) 26 | { 27 | if (builder is null) 28 | { 29 | throw new ArgumentNullException(nameof(builder)); 30 | } 31 | // disable hot chocolate tag directive 32 | // specify default Query type name if not specified 33 | builder.ModifyOptions(opt => 34 | { 35 | opt.EnableTag = false; 36 | if (opt.QueryTypeName is null) 37 | { 38 | opt.QueryTypeName = "Query"; 39 | } 40 | }); 41 | 42 | builder.AddType(); 43 | builder.AddType(); 44 | builder.AddType(new ServiceType()); 45 | builder.AddType(); 46 | builder.AddType(); 47 | builder.AddType(); 48 | builder.AddType(); 49 | builder.AddType(); 50 | builder.AddType(); 51 | builder.TryAddTypeInterceptor(); 52 | return builder; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Query { 6 | entity(id: Int!): User! 7 | _service: _Service! 8 | _entities(representations: [_Any!]!): [_Entity]! 9 | } 10 | 11 | type User @key(fields: "id") { 12 | id: Int! 13 | idCode: String! @external 14 | } 15 | 16 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 17 | type _Service { 18 | sdl: String! 19 | } 20 | 21 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 22 | union _Entity = User 23 | 24 | "Directive to indicate that marks target object as extending part of the federated schema." 25 | directive @extends on OBJECT | INTERFACE 26 | 27 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 28 | directive @external on FIELD_DEFINITION 29 | 30 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 31 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 32 | 33 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 34 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 35 | 36 | "Used to annotate the required input fieldset from a base type for a resolver." 37 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 38 | 39 | "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." 40 | scalar _Any 41 | 42 | "Scalar representing a set of fields." 43 | scalar _FieldSet 44 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Hot Chocolate Federation Examples 2 | 3 | [Apollo Federation](https://www.apollographql.com/docs/federation/) example using HotChocolate and [Apollo Router](https://www.apollographql.com/docs/router/). 4 | 5 | The repository contains `AnnotationBased` and `CodeFirst` examples of of following subgraphs: 6 | 7 | 1. `Accounts`: HotChocolate service providing the federated `User` type 8 | 2. `Inventory`: HotChocolate service that extends the `Product` type with additional inventory information 9 | 3. `Products`: HotChocolate service providing the federated `Product` type 10 | 4. `Reviews`: HotChocolate service that extends the `Product` and `User` types with `Reviews` 11 | 12 | ## Running example locally 13 | 14 | 1. Install dependencies by running `dotnet restore` from root directory 15 | 2. Build projects locally `dotnet build` from root directory 16 | 3. Start individual subgraph by running (in separate shells, example below is for `AnnotationBased` projects) 17 | ```shell 18 | dotnet run --project examples/AnnotationBased/Accounts/Accounts.csproj 19 | dotnet run --project examples/AnnotationBased/Inventory/Inventory.csproj 20 | dotnet run --project examples/AnnotationBased/Products/Products.csproj 21 | dotnet run --project examples/AnnotationBased/Reviews/Reviews.csproj 22 | ``` 23 | 4. Start Federated Router 24 | 1. Install [rover CLI](https://www.apollographql.com/docs/rover/getting-started) 25 | 2. Start router and compose products schema using [rover dev command](https://www.apollographql.com/docs/rover/commands/dev) 26 | 27 | ```shell 28 | rover dev --supergraph-config examples/supergraph.yaml 29 | ``` 30 | 31 | 5. Open http://localhost:4000 for the query editor 32 | 33 | Example federated query 34 | 35 | ```graphql 36 | query ExampleQuery { 37 | me { 38 | id 39 | } 40 | topProducts { 41 | inStock 42 | name 43 | price 44 | reviews { 45 | author { 46 | id 47 | name 48 | } 49 | body 50 | id 51 | } 52 | } 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /Compatibility/AnnotationBased/Types/User.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation; 2 | 3 | namespace Products; 4 | 5 | [Key("email")] 6 | [Extends] 7 | public class User 8 | { 9 | public User(string email, string? name) 10 | { 11 | Email = email; 12 | Name = name; 13 | } 14 | 15 | [Requires("totalProductsCreated yearsOfEmployment")] 16 | public int? GetAverageProductsCreatedPerYear() 17 | { 18 | if (TotalProductsCreated != null && LengthOfEmployment != null) 19 | { 20 | return Convert.ToInt32((TotalProductsCreated * 1.0) / LengthOfEmployment); 21 | } 22 | return null; 23 | } 24 | 25 | [ID] 26 | [External] 27 | public string Email { get; set; } 28 | 29 | [Override("users")] 30 | public string? Name { get; } 31 | 32 | [External] 33 | public int? TotalProductsCreated { get; set; } = 1337; 34 | 35 | [GraphQLIgnore] 36 | public int? LengthOfEmployment { get; set; } 37 | 38 | [External] 39 | public int GetYearsOfEmployment() 40 | { 41 | if (LengthOfEmployment == null) 42 | { 43 | throw new InvalidOperationException("yearsOfEmployment should never be null - it should be populated by _entities query"); 44 | } 45 | 46 | return (int)LengthOfEmployment; 47 | } 48 | 49 | [ReferenceResolver] 50 | public static User? GetUserByEmail( 51 | string email, 52 | int? totalProductsCreated, 53 | int? yearsOfEmployment, 54 | Data repository) 55 | { 56 | User? user = repository.Users.FirstOrDefault(u => u.Email.Equals(email)); 57 | if (user != null) 58 | { 59 | if (totalProductsCreated != null) 60 | { 61 | user.TotalProductsCreated = totalProductsCreated; 62 | } 63 | 64 | if (yearsOfEmployment != null) 65 | { 66 | user.LengthOfEmployment = yearsOfEmployment; 67 | } 68 | } 69 | return user; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: QueryOfTestTypeClassDirective 3 | } 4 | 5 | type QueryOfTestTypeClassDirective { 6 | someField(id: Int!): TestTypeClassDirective! 7 | _service: _Service! 8 | _entities(representations: [_Any!]!): [_Entity]! 9 | } 10 | 11 | type TestTypeClassDirective @key(fields: "id") { 12 | id: Int! 13 | } 14 | 15 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 16 | type _Service { 17 | sdl: String! 18 | } 19 | 20 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 21 | union _Entity = TestTypeClassDirective 22 | 23 | "Directive to indicate that marks target object as extending part of the federated schema." 24 | directive @extends on OBJECT | INTERFACE 25 | 26 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 27 | directive @external on FIELD_DEFINITION 28 | 29 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 30 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 31 | 32 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 33 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 34 | 35 | "Used to annotate the required input fieldset from a base type for a resolver." 36 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 37 | 38 | "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." 39 | scalar _Any 40 | 41 | "Scalar representing a set of fields." 42 | scalar _FieldSet 43 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: QueryOfTestTypePropertyDirective 3 | } 4 | 5 | type QueryOfTestTypePropertyDirective { 6 | someField(id: Int!): TestTypePropertyDirective! 7 | _service: _Service! 8 | _entities(representations: [_Any!]!): [_Entity]! 9 | } 10 | 11 | type TestTypePropertyDirective @key(fields: "id") { 12 | id: Int! 13 | } 14 | 15 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 16 | type _Service { 17 | sdl: String! 18 | } 19 | 20 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 21 | union _Entity = TestTypePropertyDirective 22 | 23 | "Directive to indicate that marks target object as extending part of the federated schema." 24 | directive @extends on OBJECT | INTERFACE 25 | 26 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 27 | directive @external on FIELD_DEFINITION 28 | 29 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 30 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 31 | 32 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 33 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 34 | 35 | "Used to annotate the required input fieldset from a base type for a resolver." 36 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 37 | 38 | "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." 39 | scalar _Any 40 | 41 | "Scalar representing a set of fields." 42 | scalar _FieldSet 43 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String! 7 | } 8 | 9 | type Query { 10 | someField(id: Int!): Review! 11 | _service: _Service! 12 | _entities(representations: [_Any!]!): [_Entity]! 13 | } 14 | 15 | type Review @key(fields: "id") { 16 | id: Int! 17 | product: Product! @provides(fields: "name") 18 | } 19 | 20 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 21 | type _Service { 22 | sdl: String! 23 | } 24 | 25 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 26 | union _Entity = Review 27 | 28 | "Directive to indicate that marks target object as extending part of the federated schema." 29 | directive @extends on OBJECT | INTERFACE 30 | 31 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 32 | directive @external on FIELD_DEFINITION 33 | 34 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 35 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 36 | 37 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 38 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 39 | 40 | "Used to annotate the required input fieldset from a base type for a resolver." 41 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 42 | 43 | "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." 44 | scalar _Any 45 | 46 | "Scalar representing a set of fields." 47 | scalar _FieldSet 48 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | } 4 | 5 | type Product { 6 | name: String! 7 | } 8 | 9 | type Query { 10 | someField(id: Int!): Review! 11 | _service: _Service! 12 | _entities(representations: [_Any!]!): [_Entity]! 13 | } 14 | 15 | type Review @key(fields: "id") { 16 | id: Int! 17 | product: Product! @requires(fields: "id") 18 | } 19 | 20 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 21 | type _Service { 22 | sdl: String! 23 | } 24 | 25 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 26 | union _Entity = Review 27 | 28 | "Directive to indicate that marks target object as extending part of the federated schema." 29 | directive @extends on OBJECT | INTERFACE 30 | 31 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 32 | directive @external on FIELD_DEFINITION 33 | 34 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 35 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 36 | 37 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 38 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 39 | 40 | "Used to annotate the required input fieldset from a base type for a resolver." 41 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 42 | 43 | "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." 44 | scalar _Any 45 | 46 | "Scalar representing a set of fields." 47 | scalar _FieldSet 48 | -------------------------------------------------------------------------------- /Federation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap: -------------------------------------------------------------------------------- 1 | schema { 2 | query: QueryOfTestTypePropertyDirectives 3 | } 4 | 5 | type QueryOfTestTypePropertyDirectives { 6 | someField(id: Int!): TestTypePropertyDirectives! 7 | _service: _Service! 8 | _entities(representations: [_Any!]!): [_Entity]! 9 | } 10 | 11 | type TestTypePropertyDirectives @key(fields: "id name") { 12 | id: Int! 13 | name: String! 14 | } 15 | 16 | "This type provides a field named sdl: String! which exposes the SDL of the service's schema. This SDL (schema definition language) is a printed version of the service's schema including the annotations of federation directives. This SDL does not include the additions of the federation spec." 17 | type _Service { 18 | sdl: String! 19 | } 20 | 21 | "Union of all types that key directive applied. This information is needed by the Apollo federation gateway." 22 | union _Entity = TestTypePropertyDirectives 23 | 24 | "Directive to indicate that marks target object as extending part of the federated schema." 25 | directive @extends on OBJECT | INTERFACE 26 | 27 | "Directive to indicate that a field is owned by another service, for example via Apollo federation." 28 | directive @external on FIELD_DEFINITION 29 | 30 | "Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface." 31 | directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE 32 | 33 | "Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway." 34 | directive @provides(fields: _FieldSet!) on FIELD_DEFINITION 35 | 36 | "Used to annotate the required input fieldset from a base type for a resolver." 37 | directive @requires(fields: _FieldSet!) on FIELD_DEFINITION 38 | 39 | "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." 40 | scalar _Any 41 | 42 | "Scalar representing a set of fields." 43 | scalar _FieldSet 44 | -------------------------------------------------------------------------------- /Federation/Two/InaccessibleDirectiveType.cs: -------------------------------------------------------------------------------- 1 | using ApolloGraphQL.HotChocolate.Federation.Constants; 2 | using ApolloGraphQL.HotChocolate.Federation.Properties; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation.Two; 5 | 6 | /// 7 | /// 8 | /// directive @inaccessible on FIELD_DEFINITION 9 | /// | OBJECT 10 | /// | INTERFACE 11 | /// | UNION 12 | /// | ENUM 13 | /// | ENUM_VALUE 14 | /// | SCALAR 15 | /// | INPUT_OBJECT 16 | /// | INPUT_FIELD_DEFINITION 17 | /// | ARGUMENT_DEFINITION 18 | /// 19 | /// 20 | /// The @inaccessible directive is used to mark location within schema as inaccessible 21 | /// from the GraphQL Router. Applying @inaccessible directive on a type is equivalent of applying 22 | /// it on all type fields. 23 | /// 24 | /// While @inaccessible fields are not exposed by the router to the clients, they are still available 25 | /// for query plans and can be referenced from @key and @requires directives. This allows you to not 26 | /// expose sensitive fields to your clients but still make them available for computations. 27 | /// Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple 28 | /// subgraphs without breaking composition. 29 | /// 30 | /// type Foo @inaccessible { 31 | /// hiddenId: ID! 32 | /// hiddenField: String 33 | /// } 34 | /// 35 | /// 36 | public sealed class InaccessibleDirectiveType : DirectiveType 37 | { 38 | protected override void Configure(IDirectiveTypeDescriptor descriptor) 39 | => descriptor 40 | .Name(WellKnownTypeNames.Inaccessible) 41 | .Description(FederationResources.InaccessibleDirective_Description) 42 | .Location( 43 | DirectiveLocation.FieldDefinition 44 | | DirectiveLocation.Object 45 | | DirectiveLocation.Interface 46 | | DirectiveLocation.Union 47 | | DirectiveLocation.Enum 48 | | DirectiveLocation.EnumValue 49 | | DirectiveLocation.Scalar 50 | | DirectiveLocation.Scalar 51 | | DirectiveLocation.InputObject 52 | | DirectiveLocation.InputFieldDefinition 53 | | DirectiveLocation.ArgumentDefinition); 54 | } 55 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | preview 8 | 0.0.0 9 | $(NoWarn);CS0436;RS0026;RS0027;RS0041 10 | 11 | 12 | 13 | true 14 | $(NoWarn);CS1591;NU5104 15 | true 16 | 17 | 18 | 19 | ApolloGraphQL authors and contributors 20 | Copyright (c) 2023- Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 21 | Apollo.png 22 | MIT 23 | https://github.com/apollographql/federation-hotchocolate 24 | Release notes: https://github.com/apollographql/federation-hotchocolate/releases/$(PackageVersion) 25 | GraphQL Apollo Federation HotChocolate 26 | true 27 | 28 | 29 | 30 | true 31 | true 32 | https://github.com/apollographql/federation-hotchocolate.git 33 | GitHub 34 | true 35 | snupkg 36 | 37 | 38 | 39 | enable 40 | 41 | 42 | 43 | portable 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Federation/NonResolvableKeyAttribute.cs: -------------------------------------------------------------------------------- 1 | using HotChocolate.Types.Descriptors; 2 | using static ApolloGraphQL.HotChocolate.Federation.ThrowHelper; 3 | 4 | namespace ApolloGraphQL.HotChocolate.Federation; 5 | 6 | /// 7 | /// 8 | /// # federation v2 definition 9 | /// directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE 10 | /// 11 | /// 12 | /// The @key directive is used to indicate a combination of fields that can be used to uniquely 13 | /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"), 14 | /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can 15 | /// be specified on a target type. 16 | /// 17 | /// Keys can also be marked as non-resolvable which indicates to router that given entity should never be 18 | /// resolved within given subgraph. This allows your subgraph to still reference target entity without 19 | /// contributing any fields to it. 20 | /// 21 | /// type Foo @key(fields: "id") { 22 | /// id: ID! 23 | /// field: String 24 | /// bars: [Bar!]! 25 | /// } 26 | /// 27 | /// type Bar @key(fields: "id", resolvable: false) { 28 | /// id: ID! 29 | /// } 30 | /// 31 | /// 32 | /// 33 | public sealed class NonResolvableKeyAttribute : ObjectTypeDescriptorAttribute 34 | { 35 | /// 36 | /// Initializes a new instance of . 37 | /// 38 | /// 39 | /// The field set that describes the key. 40 | /// Grammatically, a field set is a selection set minus the braces. 41 | /// 42 | public NonResolvableKeyAttribute(string fieldSet) 43 | { 44 | FieldSet = fieldSet; 45 | } 46 | 47 | /// 48 | /// Gets the field set that describes the key. 49 | /// Grammatically, a field set is a selection set minus the braces. 50 | /// 51 | public string FieldSet { get; } 52 | 53 | protected override void OnConfigure(IDescriptorContext context, IObjectTypeDescriptor descriptor, Type type) 54 | { 55 | if (string.IsNullOrEmpty(FieldSet)) 56 | { 57 | throw Key_FieldSet_CannotBeEmpty(type); 58 | } 59 | descriptor.Key(FieldSet, false); 60 | } 61 | } 62 | --------------------------------------------------------------------------------