diff --git a/.editorconfig b/.editorconfig
index fb8adc980ac..820a244b54e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -121,3 +121,12 @@ resharper_trailing_comma_in_multiline_lists = true
resharper_wrap_before_binary_pattern_op = false
resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_chained_binary_patterns = chop_if_long
+
+# Require braces on all control statements
+resharper_csharp_braces_for_ifelse = required
+resharper_csharp_braces_for_for = required
+resharper_csharp_braces_for_foreach = required
+resharper_csharp_braces_for_while = required
+resharper_csharp_braces_for_using = required
+resharper_csharp_braces_for_lock = required
+resharper_csharp_braces_for_fixed = required
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea91981d37c..2d418fa88b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-## [12.0.1]
+## [12.0.1] - 2021-10-01
### Hot Chocolate
@@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Return argument value as field definition runtimeType if requested as object type by @benmccallum (#4291)
-## [12.0.0]
+## [12.0.0] - 2021-09-27
### Hot Chocolate
@@ -106,7 +106,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Optimized component code splitting for better load times
- Fixed shortcut label texts for Windows and Linux
-## [11.1.0]
+## [11.1.0] - 2021-03-30
### Added
@@ -181,7 +181,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed nullable matrix types in schema first. (#2998)
- Fixed fragment projections issue with the operation compiler. (#2920)
-## [11.0.9]
+## [11.0.9] - 2021-01-27
### Added
@@ -197,7 +197,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed selection optimizer were not resolved correctly. (#2889)
- Fixed projection of edge type (#2888)
-## [11.0.8]
+## [11.0.8] - 2021-01-14
### Added
@@ -218,7 +218,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed string literals in places of enum values have to raise a query errors. (#2846)
- Fixed issues with Apollo active persisted queries flow. (#2864)
-## [11.0.7]
+## [11.0.7] - 2021-01-06
### Added
@@ -230,38 +230,38 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed argument renaming during schema stitching. (#2784)
- Fixed cursor backward pagination with two list elements (#2777)
-## [11.0.6]
+## [11.0.6] - 2020-12-23
### Fixed
- Fixed error filters not being activated (#2774)
-## [11.0.5]
+## [11.0.5] - 2020-12-18
### Fixed
- Fixes query rewriting when fields are merged during stitching. (#2765)
-## [11.0.4]
+## [11.0.4] - 2020-12-16
### Fixed
- Fixed executable detection (#2762)
-## [11.0.3]
+## [11.0.3] - 2020-12-15
### Fixed
- Added back the syntax serializers for backward compatibility (#2758)
-## [11.0.2]
+## [11.0.2] - 2020-12-08
### Fixed
- Fixed PagingAmountRewriter for stitching in migration guide (#2737)
- Fixed execution of batch requests (#2726)
-## [11.0.1]
+## [11.0.1] - 2020-12-02
### Added
@@ -277,7 +277,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Removed legacy syntax printer and ensured that only the new one is used. (#2711)
-## [11.0.0]
+## [11.0.0] - 2020-11-23
### Added
@@ -338,37 +338,37 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed inference of enum values from GraphQL SDL
- Fixed issue with non-null arguments that have defaults (#2441)
-## [10.5.5]
+## [10.5.5] - 2020-11-23
### Fixed
- Fixed operation serialization [#2646](https://github.com/ChilliCream/graphql-platform/pull/2646)
-## [10.5.4]
+## [10.5.4] - 2020-11-18
### Fixed
- Fixed QueryRequestBuild handling of extensions. [#2608](https://github.com/ChilliCream/graphql-platform/pull/2608)
-## [10.5.3]
+## [10.5.3] - 2020-09-27
### Fixed
- Fixed ConnectionMiddleware and IEnumerable + IConnection [#2378](https://github.com/ChilliCream/graphql-platform/pull/2378)
-## [10.5.2]
+## [10.5.2] - 2020-07-28
### Fixed
- Fixed ID serialization on input types [#2174](https://github.com/ChilliCream/graphql-platform/pull/2174)
-## [10.5.1]
+## [10.5.1] - 2020-07-28
### Fixed
- Fixed field discovery [#2167](https://github.com/ChilliCream/graphql-platform/pull/2167)
-## [10.5.0]
+## [10.5.0] - 2020-07-27
### Added
@@ -402,7 +402,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed schema type discovery issues.
- Fixed field discovery for object type extensions.
-## [10.4.0]
+## [10.4.0] - 2020-03-18
### Added
@@ -429,45 +429,45 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed issue where the type dependencies of type extension where not correctly merged with the target type.
- Fixed `UUID` type serialization.
-## [10.3.6]
+## [10.3.6] - 2020-02-28
### Fixed
- Fixed handling of variables when delegating data fetching through the stitching context. [#1390](https://github.com/ChilliCream/graphql-platform/pull/1390)
-## [10.3.5]
+## [10.3.5] - 2020-01-13
### Fixed
- Fixed issue that caused errors when collecting fields on a directive context
-## [10.3.4]
+## [10.3.4] - 2020-01-09
### Fixed
- Fixed default hash provider dependency injection configuration [#1363](https://github.com/ChilliCream/graphql-platform/pull/1363)
-## [10.3.3]
+## [10.3.3] - 2020-01-09
### Fixed
- Fixed argument non-null validation.
- Fixed variable coercion.
-## [10.3.2]
+## [10.3.2] - 2020-01-06
### Fixed
- Fixed issue where input fields were no longer automatically converted.
- Fixed issue where the float was rounded when provided as variable.
-## [10.3.1]
+## [10.3.1] - 2019-12-31
### Fixed
- Fixed issue that private setters where not used during input deserialization.
-## [10.3.0]
+## [10.3.0] - 2019-12-26
### Added
@@ -499,7 +499,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed parser error handling in middleware. [#1028](https://github.com/ChilliCream/graphql-platform/pull/1028)
- Fixed directive delegation (@skip/@include) in stitching. [#937](https://github.com/ChilliCream/graphql-platform/pull/937)
-## [10.2.0]
+## [10.2.0] - 2019-10-31
### Added
@@ -528,7 +528,7 @@ Return argument value as field definition runtimeType if requested as object typ
- The complecity middleware when multiplier where activated did only take the firs level into account. [#1137](https://github.com/ChilliCream/graphql-platform/pull/1137)
- Errors when attempting to filter on nullable types. [#1121](https://github.com/ChilliCream/graphql-platform/pull/1121)
-## [10.1.0]
+## [10.1.0] - 2019-09-13
### Added
@@ -547,7 +547,7 @@ Return argument value as field definition runtimeType if requested as object typ
- Fixed scoped service handling. [#1066](https://github.com/ChilliCream/graphql-platform/pull/1066)
- Fixed Duplicate service registration. [#1066](https://github.com/ChilliCream/graphql-platform/pull/1066)
-## [10.0.0]
+## [10.0.0] - 2019-08-14
### Added
@@ -883,7 +883,42 @@ Return argument value as field definition runtimeType if requested as object typ
- Diagnostic source which can be used to track field execution times and other events.
- Implementing a directive middleware has now become much easier with this release. We have built the authorize-directive with these new APIs.
-[unreleased]: https://github.com/ChilliCream/graphql-platform/compare/0.8.2...HEAD
+[unreleased]: https://github.com/ChilliCream/graphql-platform/compare/12.0.1...HEAD
+[12.0.1]: https://github.com/ChilliCream/graphql-platform/compare/12.0.0...12.0.1
+[12.0.0]: https://github.com/ChilliCream/graphql-platform/compare/11.1.0...12.0.0
+[11.1.0]: https://github.com/ChilliCream/graphql-platform/compare/11.0.9...11.1.0
+[11.0.9]: https://github.com/ChilliCream/graphql-platform/compare/11.0.8...11.0.9
+[11.0.8]: https://github.com/ChilliCream/graphql-platform/compare/11.0.7...11.0.8
+[11.0.7]: https://github.com/ChilliCream/graphql-platform/compare/11.0.6...11.0.7
+[11.0.6]: https://github.com/ChilliCream/graphql-platform/compare/11.0.5...11.0.6
+[11.0.5]: https://github.com/ChilliCream/graphql-platform/compare/11.0.4...11.0.5
+[11.0.4]: https://github.com/ChilliCream/graphql-platform/compare/11.0.3...11.0.4
+[11.0.3]: https://github.com/ChilliCream/graphql-platform/compare/11.0.2...11.0.3
+[11.0.2]: https://github.com/ChilliCream/graphql-platform/compare/11.0.1...11.0.2
+[11.0.1]: https://github.com/ChilliCream/graphql-platform/compare/11.0.0...11.0.1
+[11.0.0]: https://github.com/ChilliCream/graphql-platform/compare/10.5.5...11.0.0
+[10.5.5]: https://github.com/ChilliCream/graphql-platform/compare/10.5.4...10.5.5
+[10.5.4]: https://github.com/ChilliCream/graphql-platform/compare/10.5.3...10.5.4
+[10.5.3]: https://github.com/ChilliCream/graphql-platform/compare/10.5.2...10.5.3
+[10.5.2]: https://github.com/ChilliCream/graphql-platform/compare/10.5.1...10.5.2
+[10.5.1]: https://github.com/ChilliCream/graphql-platform/compare/10.5.0...10.5.1
+[10.5.0]: https://github.com/ChilliCream/graphql-platform/compare/10.4.0...10.5.0
+[10.4.0]: https://github.com/ChilliCream/graphql-platform/compare/10.3.6...10.4.0
+[10.3.6]: https://github.com/ChilliCream/graphql-platform/compare/10.3.5...10.3.6
+[10.3.5]: https://github.com/ChilliCream/graphql-platform/compare/10.3.4...10.3.5
+[10.3.4]: https://github.com/ChilliCream/graphql-platform/compare/10.3.3...10.3.4
+[10.3.3]: https://github.com/ChilliCream/graphql-platform/compare/10.3.2...10.3.3
+[10.3.2]: https://github.com/ChilliCream/graphql-platform/compare/10.3.1...10.3.2
+[10.3.1]: https://github.com/ChilliCream/graphql-platform/compare/10.3.0...10.3.1
+[10.3.0]: https://github.com/ChilliCream/graphql-platform/compare/10.2.0...10.3.0
+[10.2.0]: https://github.com/ChilliCream/graphql-platform/compare/10.1.0...10.2.0
+[10.1.0]: https://github.com/ChilliCream/graphql-platform/compare/10.0.0...10.1.0
+[10.0.0]: https://github.com/ChilliCream/graphql-platform/compare/9.0.4...10.0.0
+[9.0.4]: https://github.com/ChilliCream/graphql-platform/compare/9.0.3...9.0.4
+[9.0.3]: https://github.com/ChilliCream/graphql-platform/compare/9.0.2...9.0.3
+[9.0.2]: https://github.com/ChilliCream/graphql-platform/compare/9.0.1...9.0.2
+[9.0.1]: https://github.com/ChilliCream/graphql-platform/compare/9.0.0...9.0.1
+[9.0.0]: https://github.com/ChilliCream/graphql-platform/compare/0.8.2...9.0.0
[0.8.2]: https://github.com/ChilliCream/graphql-platform/compare/0.8.1...0.8.2
[0.8.1]: https://github.com/ChilliCream/graphql-platform/compare/0.8.0...0.8.1
[0.8.0]: https://github.com/ChilliCream/graphql-platform/compare/0.7.0...0.8.0
diff --git a/src/HotChocolate/ApolloFederation/Directory.Build.props b/src/HotChocolate/ApolloFederation/Directory.Build.props
index c3a1a4be296..457246e96e5 100644
--- a/src/HotChocolate/ApolloFederation/Directory.Build.props
+++ b/src/HotChocolate/ApolloFederation/Directory.Build.props
@@ -3,6 +3,6 @@
enable
- $(Library2TargetFrameworks)
+ $(Library3TargetFrameworks)
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownArgumentNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownArgumentNames.cs
deleted file mode 100644
index 2ba2d494e34..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownArgumentNames.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace HotChocolate.ApolloFederation.Constants;
-
-internal static class WellKnownArgumentNames
-{
- public const string Fields = "fields";
- public const string Representations = "representations";
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownContextData.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownContextData.cs
deleted file mode 100644
index 5180cf6e0be..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownContextData.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace HotChocolate.ApolloFederation.Constants;
-
-internal static class WellKnownContextData
-{
- public const string KeyMarker = "HotChocolate.ApolloFederation.Key";
- public const string ExtendMarker = "HotChocolate.ApolloFederation.Extend";
- public const string ExternalSetter = "HotChocolate.ApolloFederation.ExternalSetter";
- public const string EntityResolver = "HotChocolate.ApolloFederation.EntityResolver";
- public const string DataField = "data";
- public const string TypeField = "__type";
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownTypeNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownTypeNames.cs
deleted file mode 100644
index 89c1ae9fba1..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownTypeNames.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace HotChocolate.ApolloFederation.Constants;
-
-internal static class WellKnownTypeNames
-{
- public const string External = "external";
- public const string Requires = "requires";
- public const string Provides = "provides";
- public const string Key = "key";
- public const string FieldSet = "_FieldSet";
- public const string Any = "_Any";
- public const string Entity = "_Entity";
- public const string Service = "_Service";
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/IEntityResolverDescriptor.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/IEntityResolverDescriptor.cs
deleted file mode 100644
index e25defb1199..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/IEntityResolverDescriptor.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using System.Linq.Expressions;
-using System.Reflection;
-using HotChocolate.Resolvers;
-
-namespace HotChocolate.ApolloFederation.Descriptors;
-
-///
-/// The entity descriptor allows to specify a reference resolver.
-///
-public interface IEntityResolverDescriptor
-{
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The resolver.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReference(
- FieldResolverDelegate fieldResolver);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The reference resolver selector.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(
- Expression> method);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith();
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The reference resolver.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(Type type);
-}
-
-///
-/// The entity descriptor allows to specify a reference resolver.
-///
-public interface IEntityResolverDescriptor
-{
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The resolver.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReference(
- FieldResolverDelegate fieldResolver);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The reference resolver selector.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(
- Expression> method);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The reference resolver selector.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(
- Expression> method);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith();
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The reference resolver.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method);
-
- ///
- /// Resolve an entity from its representation.
- ///
- ///
- /// The type on which the reference resolver is located.
- ///
- ///
- /// Returns the descriptor for configuration chaining.
- ///
- IObjectTypeDescriptor ResolveReferenceWith(Type type);
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/EntityType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/EntityType.cs
deleted file mode 100644
index 6f070a1e6f9..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/EntityType.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using HotChocolate.ApolloFederation.Properties;
-using static HotChocolate.ApolloFederation.Constants.WellKnownTypeNames;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// A union called _Entity which is a union of all types that use the @key directive,
-/// including both types native to the schema and extended types.
-///
-public sealed class EntityType : UnionType
-{
- protected override void Configure(IUnionTypeDescriptor descriptor)
- => descriptor
- .Name(Entity)
- .Description(FederationResources.EntityType_Description);
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExtendServiceTypeAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExtendServiceTypeAttribute.cs
deleted file mode 100644
index 47ff6842b02..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExtendServiceTypeAttribute.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using HotChocolate.Types.Descriptors;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// This attribute is used to mark types as an extended type
-/// of a type that is defined by another service when
-/// using apollo federation.
-///
-[AttributeUsage(
- AttributeTargets.Class |
- AttributeTargets.Struct |
- AttributeTargets.Interface)]
-public sealed class ExtendServiceTypeAttribute : ObjectTypeDescriptorAttribute
-{
- protected override void OnConfigure(
- IDescriptorContext context,
- IObjectTypeDescriptor descriptor,
- Type type)
- => descriptor.ExtendServiceType();
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions.cs
deleted file mode 100644
index 721b9eb53c5..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Descriptors;
-using HotChocolate.Language;
-using static HotChocolate.ApolloFederation.Properties.FederationResources;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
-
-namespace HotChocolate.Types;
-
-///
-/// Provides extensions for type system descriptors.
-///
-public static partial class ApolloFederationDescriptorExtensions
-{
- ///
- /// Adds the @external directive which is used to mark a field as owned by another service.
- /// This allows service A to use fields from service B while also knowing at runtime
- /// the types of that field.
- ///
- ///
- /// # extended from the Users service
- /// extend type User @key(fields: "email") {
- /// email: String @external
- /// reviews: [Review]
- /// }
- ///
- ///
- ///
- /// The object field descriptor on which this directive shall be annotated.
- ///
- ///
- /// Returns the object field descriptor.
- ///
- ///
- /// The is null .
- ///
- public static IObjectFieldDescriptor External(
- this IObjectFieldDescriptor descriptor)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- return descriptor.Directive(WellKnownTypeNames.External);
- }
-
- ///
- /// Adds the @key directive which is used to indicate a combination of fields that
- /// can be used to uniquely identify and fetch an object or interface.
- ///
- /// type Product @key(fields: "upc") {
- /// upc: UPC!
- /// name: String
- /// }
- ///
- ///
- ///
- /// The object type descriptor on which this directive shall be annotated.
- ///
- ///
- /// The field set that describes the key.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- ///
- ///
- /// is null .
- ///
- ///
- /// is null or .
- ///
- public static IEntityResolverDescriptor Key(
- this IObjectTypeDescriptor descriptor,
- string fieldSet)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- if (string.IsNullOrEmpty(fieldSet))
- {
- throw new ArgumentException(
- FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty,
- nameof(fieldSet));
- }
-
- descriptor.Directive(
- WellKnownTypeNames.Key,
- new ArgumentNode(
- WellKnownArgumentNames.Fields,
- new StringValueNode(fieldSet)));
-
- return new EntityResolverDescriptor(descriptor);
- }
-
- ///
- /// Adds the @requires directive which is used to annotate the required
- /// input fieldset from a base type for a resolver. It is used to develop
- /// a query plan where the required fields may not be needed by the client, but the
- /// service may need additional information from other services.
- ///
- ///
- /// # extended from the Users service
- /// extend type User @key(fields: "id") {
- /// id: ID! @external
- /// email: String @external
- /// reviews: [Review] @requires(fields: "email")
- /// }
- ///
- ///
- ///
- /// The object field descriptor on which this directive shall be annotated.
- ///
- ///
- /// The describes which fields may
- /// not be needed by the client, but are required by
- /// this service as additional information from other services.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- ///
- /// Returns the object field descriptor.
- ///
- ///
- /// is null .
- ///
- ///
- /// is null or .
- ///
- public static IObjectFieldDescriptor Requires(
- this IObjectFieldDescriptor descriptor,
- string fieldSet)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- if (string.IsNullOrEmpty(fieldSet))
- {
- throw new ArgumentException(
- FieldDescriptorExtensions_Requires_FieldSet_CannotBeNullOrEmpty,
- nameof(fieldSet));
- }
-
- return descriptor.Directive(
- WellKnownTypeNames.Requires,
- new ArgumentNode(
- WellKnownArgumentNames.Fields,
- new StringValueNode(fieldSet)));
- }
-
- ///
- /// Adds the @provides directive which is used to annotate the expected returned
- /// fieldset from a field on a base type that is guaranteed to be selectable by
- /// the gateway.
- ///
- ///
- /// # extended from the Users service
- /// type Review @key(fields: "id") {
- /// product: Product @provides(fields: "name")
- /// }
- ///
- /// extend type Product @key(fields: "upc") {
- /// upc: String @external
- /// name: String @external
- /// }
- ///
- ///
- ///
- /// The object field descriptor on which this directive shall be annotated.
- ///
- ///
- /// The fields that are guaranteed to be selectable by the gateway.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- ///
- /// Returns the object field descriptor.
- ///
- ///
- /// is null .
- ///
- ///
- /// is null or .
- ///
- public static IObjectFieldDescriptor Provides(
- this IObjectFieldDescriptor descriptor,
- string fieldSet)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- if (string.IsNullOrEmpty(fieldSet))
- {
- throw new ArgumentException(
- FieldDescriptorExtensions_Provides_FieldSet_CannotBeNullOrEmpty,
- nameof(fieldSet));
- }
-
- return descriptor.Directive(
- WellKnownTypeNames.Provides,
- new ArgumentNode(
- WellKnownArgumentNames.Fields,
- new StringValueNode(fieldSet)));
- }
-
- ///
- /// Mark the type as an extension
- /// of a type that is defined by another service when
- /// using apollo federation.
- ///
- public static IObjectTypeDescriptor ExtendServiceType(
- this IObjectTypeDescriptor descriptor)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- descriptor
- .Extend()
- .OnBeforeCreate(d => d.ContextData[ExtendMarker] = true);
-
- return descriptor;
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions~1.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions~1.cs
deleted file mode 100644
index 99eb7ed4e56..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationDescriptorExtensions~1.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Descriptors;
-using HotChocolate.Language;
-using static HotChocolate.ApolloFederation.Properties.FederationResources;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
-
-namespace HotChocolate.Types;
-
-///
-/// Provides extensions for type system descriptors.
-///
-public static partial class ApolloFederationDescriptorExtensions
-{
- ///
- /// Adds the @key directive which is used to indicate a combination of fields that
- /// can be used to uniquely identify and fetch an object or interface.
- ///
- /// type Product @key(fields: "upc") {
- /// upc: UPC!
- /// name: String
- /// }
- ///
- ///
- ///
- /// The object type descriptor on which this directive shall be annotated.
- ///
- ///
- /// The field set that describes the key.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- ///
- ///
- /// is null .
- ///
- ///
- /// is null or .
- ///
- public static IEntityResolverDescriptor Key(
- this IObjectTypeDescriptor descriptor,
- string fieldSet)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- if (string.IsNullOrEmpty(fieldSet))
- {
- throw new ArgumentException(
- FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty,
- nameof(fieldSet));
- }
-
- descriptor.Directive(
- WellKnownTypeNames.Key,
- new ArgumentNode(
- WellKnownArgumentNames.Fields,
- new StringValueNode(fieldSet)));
-
- return new EntityResolverDescriptor(descriptor);
- }
-
- ///
- /// Mark the type as an extension
- /// of a type that is defined by another service when
- /// using apollo federation.
- ///
- public static IObjectTypeDescriptor ExtendServiceType(
- this IObjectTypeDescriptor descriptor)
- {
- if (descriptor is null)
- {
- throw new ArgumentNullException(nameof(descriptor));
- }
-
- descriptor
- .Extend()
- .OnBeforeCreate(d => d.ContextData[ExtendMarker] = true);
-
- return descriptor;
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs
index 5d6ddf5f0b5..511735be58d 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs
@@ -1,6 +1,7 @@
-using System;
+using HotChocolate.ApolloFederation;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Execution.Configuration;
-using Microsoft.Extensions.DependencyInjection;
+using FederationVersion = HotChocolate.ApolloFederation.FederationVersion;
namespace Microsoft.Extensions.DependencyInjection;
@@ -15,6 +16,9 @@ public static class ApolloFederationRequestExecutorBuilderExtensions
///
/// The .
///
+ ///
+ /// The apollo federation version to use.
+ ///
///
/// Returns the .
///
@@ -22,13 +26,12 @@ public static class ApolloFederationRequestExecutorBuilderExtensions
/// The is null .
///
public static IRequestExecutorBuilder AddApolloFederation(
- this IRequestExecutorBuilder builder)
+ this IRequestExecutorBuilder builder,
+ FederationVersion version = FederationVersion.Latest)
{
- if (builder is null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- return builder.ConfigureSchema(s => s.AddApolloFederation());
+ ArgumentNullException.ThrowIfNull(builder);
+ builder.SetContextData(FederationContextData.FederationVersion, version);
+ builder.TryAddTypeInterceptor();
+ return builder;
}
}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationSchemaBuilderExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationSchemaBuilderExtensions.cs
deleted file mode 100644
index a112a1b5d93..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationSchemaBuilderExtensions.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using HotChocolate.ApolloFederation;
-using AnyType = HotChocolate.ApolloFederation.AnyType;
-
-namespace HotChocolate;
-
-///
-/// Provides extensions to .
-///
-public static class ApolloFederationSchemaBuilderExtensions
-{
- ///
- /// Adds support for Apollo Federation to the schema.
- ///
- ///
- /// The .
- ///
- ///
- /// Returns the .
- ///
- ///
- /// The is null .
- ///
- internal static ISchemaBuilder AddApolloFederation(
- this ISchemaBuilder builder)
- {
- if (builder is null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.AddType();
- builder.TryAddTypeInterceptor();
- return builder;
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/DirectiveTypeDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/DirectiveTypeDescriptorExtensions.cs
deleted file mode 100644
index 94b0acfa0f7..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/DirectiveTypeDescriptorExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using static HotChocolate.ApolloFederation.Constants.WellKnownArgumentNames;
-
-namespace HotChocolate.ApolloFederation;
-
-internal static class DirectiveTypeDescriptorExtensions
-{
- public static IDirectiveTypeDescriptor FieldsArgument(
- this IDirectiveTypeDescriptor descriptor)
- {
- descriptor
- .Argument(Fields)
- .Type>();
-
- return descriptor;
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs
new file mode 100644
index 00000000000..537a1a53209
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using HotChocolate.Types.Descriptors;
+using HotChocolate.Types.Descriptors.Definitions;
+
+namespace HotChocolate.ApolloFederation;
+
+internal static class FederationVersionExtensions
+{
+ private static readonly Dictionary _uriToVersion = new()
+ {
+ [new Uri(FederationVersionUrls.Federation20)] = FederationVersion.Federation20,
+ [new Uri(FederationVersionUrls.Federation21)] = FederationVersion.Federation21,
+ [new Uri(FederationVersionUrls.Federation22)] = FederationVersion.Federation22,
+ [new Uri(FederationVersionUrls.Federation23)] = FederationVersion.Federation23,
+ [new Uri(FederationVersionUrls.Federation24)] = FederationVersion.Federation24,
+ [new Uri(FederationVersionUrls.Federation25)] = FederationVersion.Federation25,
+ };
+
+ private static readonly Dictionary _versionToUri = new()
+ {
+ [FederationVersion.Federation20] = new(FederationVersionUrls.Federation20),
+ [FederationVersion.Federation21] = new(FederationVersionUrls.Federation21),
+ [FederationVersion.Federation22] = new(FederationVersionUrls.Federation22),
+ [FederationVersion.Federation23] = new(FederationVersionUrls.Federation23),
+ [FederationVersion.Federation24] = new(FederationVersionUrls.Federation24),
+ [FederationVersion.Federation25] = new(FederationVersionUrls.Federation25),
+ };
+
+ public static FederationVersion GetFederationVersion(
+ this IDescriptor descriptor)
+ where T : DefinitionBase
+ {
+ var contextData = descriptor.Extend().Context.ContextData;
+ if (contextData.TryGetValue(FederationContextData.FederationVersion, out var value) &&
+ value is FederationVersion version and > FederationVersion.Unknown)
+ {
+ return version;
+ }
+
+ // TODO : resources
+ throw new InvalidOperationException("The configuration state is invalid.");
+ }
+
+ public static FederationVersion GetFederationVersion(
+ this IDescriptorContext context)
+ {
+ if (context.ContextData.TryGetValue(FederationContextData.FederationVersion, out var value) &&
+ value is FederationVersion version and > FederationVersion.Unknown)
+ {
+ return version;
+ }
+
+ // TODO : resources
+ throw new InvalidOperationException("The configuration state is invalid.");
+ }
+
+ public static Uri ToUrl(this FederationVersion version)
+ {
+ if(_versionToUri.TryGetValue(version, out var url))
+ {
+ return url;
+ }
+
+ // TODO : resources
+ throw new ArgumentException("The federation version is not supported.", nameof(version));
+ }
+
+ public static FederationVersion ToVersion(this Uri url)
+ {
+ if(_uriToVersion.TryGetValue(url, out var version))
+ {
+ return version;
+ }
+
+ // TODO : resources
+ throw new ArgumentException("The federation url is not supported.", nameof(url));
+ }
+
+ public static bool TryToVersion(this Uri url, out FederationVersion version)
+ => _uriToVersion.TryGetValue(url, out version);
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalAttribute.cs
deleted file mode 100644
index b05bb154494..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalAttribute.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using HotChocolate.Types.Descriptors;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @external directive is used to mark a field as owned by another service.
-/// This allows service A to use fields from service B while also knowing at runtime
-/// the types of that field.
-///
-///
-/// # extended from the Users service
-/// extend type User @key(fields: "email") {
-/// email: String @external
-/// reviews: [Review]
-/// }
-///
-///
-public sealed class ExternalAttribute : ObjectFieldDescriptorAttribute
-{
- protected override void OnConfigure(
- IDescriptorContext context,
- IObjectFieldDescriptor descriptor,
- MemberInfo member)
- => descriptor.External();
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalDirectiveType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalDirectiveType.cs
deleted file mode 100644
index 52b9e4bcd47..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ExternalDirectiveType.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Properties;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @external directive is used to mark a field as owned by another service.
-/// This allows service A to use fields from service B while also knowing at runtime
-/// the types of that field.
-///
-///
-/// # extended from the Users service
-/// extend type User @key(fields: "email") {
-/// email: String @external
-/// reviews: [Review]
-/// }
-///
-///
-public sealed class ExternalDirectiveType : DirectiveType
-{
- protected override void Configure(IDirectiveTypeDescriptor descriptor)
- => descriptor
- .Name(WellKnownTypeNames.External)
- .Description(FederationResources.ExternalDirective_Description)
- .Location(DirectiveLocation.FieldDefinition);
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationContextData.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationContextData.cs
new file mode 100644
index 00000000000..25f57164a49
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationContextData.cs
@@ -0,0 +1,13 @@
+namespace HotChocolate.ApolloFederation;
+
+internal static class FederationContextData
+{
+ public const string KeyMarker = "HotChocolate.ApolloFederation.Key";
+ public const string ContactMarker = "HotChocolate.ApolloFederation.Contact";
+ public const string ExternalSetter = "HotChocolate.ApolloFederation.ExternalSetter";
+ public const string EntityResolver = "HotChocolate.ApolloFederation.EntityResolver";
+ public const string FederationVersion = "HotChocolate.ApolloFederation.Version";
+ public const string ExportedDirectives = "HotChocolate.ApolloFederation.ExportedDirectives";
+ public const string DataField = "data";
+ public const string TypeField = "__type";
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.DirectiveType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.DirectiveType.cs
deleted file mode 100644
index 2f0966d8200..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.DirectiveType.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.Language;
-using HotChocolate.Types.Introspection;
-using HotChocolate.Utilities.Introspection;
-
-namespace HotChocolate.ApolloFederation;
-
-public static partial class FederationSchemaPrinter
-{
- private static DirectiveDefinitionNode SerializeDirectiveTypeDefinition(
- DirectiveType directiveType,
- Context context)
- {
- var arguments = directiveType.Arguments
- .Select(a => SerializeInputField(a, context))
- .ToList();
-
- var locations = directiveType.Locations
- .AsEnumerable()
- .Select(l => new NameNode(l.MapDirectiveLocation().ToString()))
- .ToList();
-
- return new DirectiveDefinitionNode
- (
- null,
- new NameNode(directiveType.Name),
- SerializeDescription(directiveType.Description),
- directiveType.IsRepeatable,
- arguments,
- locations
- );
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.InputType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.InputType.cs
deleted file mode 100644
index 5758dc1ff9f..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.InputType.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Linq;
-using HotChocolate.Language;
-
-namespace HotChocolate.ApolloFederation;
-
-public static partial class FederationSchemaPrinter
-{
- private static InputObjectTypeDefinitionNode SerializeInputObjectType(
- InputObjectType inputObjectType,
- Context context)
- {
- var directives = SerializeDirectives(inputObjectType.Directives, context);
-
- var fields = inputObjectType.Fields
- .Select(t => SerializeInputField(t, context))
- .ToList();
-
- return new InputObjectTypeDefinitionNode(
- null,
- new NameNode(inputObjectType.Name),
- SerializeDescription(inputObjectType.Description),
- directives,
- fields);
- }
-
- private static InputValueDefinitionNode SerializeInputField(
- IInputField inputValue,
- Context context)
- {
- var directives = SerializeDirectives(inputValue.Directives, context);
-
- return new InputValueDefinitionNode(
- null,
- new NameNode(inputValue.Name),
- SerializeDescription(inputValue.Description),
- SerializeType(inputValue.Type, context),
- inputValue.DefaultValue,
- directives);
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.LeafType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.LeafType.cs
deleted file mode 100644
index 1abddf1993f..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.LeafType.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using HotChocolate.Language;
-using static HotChocolate.Types.SpecifiedByDirectiveType.Names;
-
-namespace HotChocolate.ApolloFederation;
-
-public static partial class FederationSchemaPrinter
-{
- private static EnumTypeDefinitionNode SerializeEnumType(
- EnumType enumType,
- Context context)
- {
- var directives = SerializeDirectives(enumType.Directives, context);
-
- var values = enumType.Values
- .Select(t => SerializeEnumValue(t, context))
- .ToList();
-
- return new EnumTypeDefinitionNode(
- null,
- new NameNode(enumType.Name),
- SerializeDescription(enumType.Description),
- directives,
- values);
- }
-
- private static EnumValueDefinitionNode SerializeEnumValue(
- IEnumValue enumValue,
- Context context)
- {
- var directives = SerializeDirectives(enumValue.Directives, context);
-
- if (enumValue.IsDeprecated)
- {
- var deprecateDirective = DeprecatedDirective.CreateNode(enumValue.DeprecationReason);
-
- if(directives.Count == 0)
- {
- directives = new List { deprecateDirective };
- }
- else
- {
- var temp = directives.ToList();
- temp.Add(deprecateDirective);
- directives = temp;
- }
- }
-
- return new EnumValueDefinitionNode(
- null,
- new NameNode(enumValue.Name),
- SerializeDescription(enumValue.Description),
- directives);
- }
-
- private static ScalarTypeDefinitionNode SerializeScalarType(
- ScalarType scalarType,
- Context context)
- {
- var directives = SerializeDirectives(scalarType.Directives, context);
-
- if (scalarType.SpecifiedBy is not null)
- {
- var copy = directives as List ?? directives.ToList();
- directives = copy;
- copy.Add(
- new DirectiveNode(
- SpecifiedBy,
- new ArgumentNode(
- Url,
- new StringValueNode(scalarType.SpecifiedBy.ToString()))));
- }
-
- return new(
- null,
- new NameNode(scalarType.Name),
- SerializeDescription(scalarType.Description),
- directives);
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.OutputTypes.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.OutputTypes.cs
deleted file mode 100644
index f54e6d4d805..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.OutputTypes.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using System.Linq;
-using HotChocolate.Language;
-
-namespace HotChocolate.ApolloFederation;
-
-public static partial class FederationSchemaPrinter
-{
- private static IDefinitionNode? SerializeObjectType(
- ObjectType objectType,
- Context context)
- {
- var fields = objectType.Fields
- .Where(IncludeField)
- .Select(t => SerializeObjectField(t, context))
- .ToList();
-
- if (fields.Count == 0)
- {
- return null;
- }
-
- var directives = SerializeDirectives(objectType.Directives, context);
-
- var interfaces = objectType.Implements
- .Select(t => SerializeNamedType(t, context))
- .ToList();
-
- if (objectType.ContextData.ContainsKey(Constants.WellKnownContextData.ExtendMarker))
- {
- return new ObjectTypeExtensionNode(
- null,
- new NameNode(objectType.Name),
- directives,
- interfaces,
- fields);
- }
-
- return new ObjectTypeDefinitionNode(
- null,
- new NameNode(objectType.Name),
- SerializeDescription(objectType.Description),
- directives,
- interfaces,
- fields);
- }
-
- private static InterfaceTypeDefinitionNode SerializeInterfaceType(
- InterfaceType interfaceType,
- Context context)
- {
- var directives = SerializeDirectives(interfaceType.Directives, context);
-
- var fields = interfaceType.Fields
- .Select(t => SerializeObjectField(t, context))
- .ToList();
-
- return new InterfaceTypeDefinitionNode(
- null,
- new NameNode(interfaceType.Name),
- SerializeDescription(interfaceType.Description),
- directives,
- Array.Empty(),
- fields);
- }
-
- private static UnionTypeDefinitionNode SerializeUnionType(
- UnionType unionType,
- Context context)
- {
- var directives = SerializeDirectives(unionType.Directives, context);
-
- var types = unionType.Types.Values
- .Select(t => SerializeNamedType(t, context))
- .ToList();
-
- return new UnionTypeDefinitionNode(
- null,
- new NameNode(unionType.Name),
- SerializeDescription(unionType.Description),
- directives,
- types);
- }
-
- private static FieldDefinitionNode SerializeObjectField(
- IOutputField field,
- Context context)
- {
- var arguments = field.Arguments
- .Select(t => SerializeInputField(t, context))
- .ToList();
-
- var directives = SerializeDirectives(field.Directives, context);
-
- if (field.IsDeprecated)
- {
- var deprecateDirective = DeprecatedDirective.CreateNode(field.DeprecationReason);
-
- if(directives.Count == 0)
- {
- directives = new[] { deprecateDirective };
- }
- else
- {
- var temp = directives.ToList();
- temp.Add(deprecateDirective);
- directives = temp;
- }
- }
-
- return new FieldDefinitionNode(
- null,
- new NameNode(field.Name),
- SerializeDescription(field.Description),
- arguments,
- SerializeType(field.Type, context),
- directives);
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.cs
deleted file mode 100644
index 1555925e3dd..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationSchemaPrinter.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.Language;
-using HotChocolate.Types.Introspection;
-using HotChocolate.Utilities;
-using HotChocolate.Utilities.Introspection;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The apollo federation schema printer.
-///
-public static partial class FederationSchemaPrinter
-{
- private static readonly HashSet _builtInDirectives =
- [
- WellKnownTypeNames.External,
- WellKnownTypeNames.Requires,
- WellKnownTypeNames.Provides,
- WellKnownTypeNames.Key,
- WellKnownDirectives.Defer,
- WellKnownDirectives.Stream,
- WellKnownDirectives.Skip,
- WellKnownDirectives.Include,
- WellKnownDirectives.Deprecated,
- SpecifiedByDirectiveType.Names.SpecifiedBy,
- ];
-
- ///
- /// Creates a representation of the given
- /// .
- ///
- ///
- /// The schema object.
- ///
- ///
- /// Returns the representation of the given
- /// .
- ///
- public static string Print(ISchema schema)
- {
- if (schema is null)
- {
- throw new ArgumentNullException(nameof(schema));
- }
-
- return SerializeSchema(schema).ToString();
- }
-
- private static DocumentNode SerializeSchema(ISchema schema)
- {
- var context = new Context();
- var definitionNodes = new List();
-
- foreach (var directive in schema.DirectiveTypes)
- {
- if (directive.IsPublic)
- {
- context.DirectiveNames.Add(directive.Name);
- }
- }
-
- foreach (var namedType in GetRelevantTypes(schema))
- {
- if (TrySerializeType(namedType, context, out var definitionNode))
- {
- definitionNodes.Add(definitionNode);
- }
- }
-
- foreach (var directive in schema.DirectiveTypes)
- {
- if (!_builtInDirectives.Contains(directive.Name) && directive.IsPublic)
- {
- definitionNodes.Add(SerializeDirectiveTypeDefinition(directive, context));
- }
- }
-
- return new DocumentNode(null, definitionNodes);
- }
-
- private static IEnumerable GetRelevantTypes(ISchema schema)
- => schema.Types
- .Where(IncludeType)
- .OrderBy(t => t.Name, StringComparer.Ordinal);
-
- private static bool TrySerializeType(
- INamedType namedType,
- Context context,
- [NotNullWhen(true)] out IDefinitionNode? definitionNode)
- {
- definitionNode = namedType switch
- {
- ObjectType type => SerializeObjectType(type, context),
- InterfaceType type => SerializeInterfaceType(type, context),
- InputObjectType type => SerializeInputObjectType(type, context),
- UnionType type => SerializeUnionType(type, context),
- EnumType type => SerializeEnumType(type, context),
- ScalarType type => SerializeScalarType(type, context),
- _ => throw new NotSupportedException(),
- };
- return definitionNode is not null;
- }
-
- private static ITypeNode SerializeType(
- IType type,
- Context context)
- {
- return type switch
- {
- NonNullType nt => new NonNullTypeNode(
- (INullableTypeNode)SerializeType(nt.Type, context)),
- ListType lt => new ListTypeNode(SerializeType(lt.ElementType, context)),
- INamedType namedType => SerializeNamedType(namedType, context),
- _ => throw new NotSupportedException(),
- };
- }
-
- private static NamedTypeNode SerializeNamedType(
- INamedType namedType,
- Context context)
- {
- context.TypeNames.Add(namedType.Name);
- return new NamedTypeNode(null, new NameNode(namedType.Name));
- }
-
- private static IReadOnlyList SerializeDirectives(
- IReadOnlyCollection directives,
- Context context)
- {
- if (directives.Count == 0)
- {
- return Array.Empty();
- }
-
- List? directiveNodes = null;
-
- foreach (var directive in directives)
- {
- if (context.DirectiveNames.Contains(directive.Type.Name))
- {
- (directiveNodes ??= new()).Add(directive.AsSyntaxNode(true));
- }
- }
-
- if (directiveNodes is not null)
- {
- return directiveNodes;
- }
-
- return Array.Empty();
- }
-
- private static StringValueNode? SerializeDescription(string? description)
- => description is { Length: > 0 }
- ? new StringValueNode(description)
- : null;
-
- private static bool IncludeType(INamedType type)
- => !IsBuiltInType(type) &&
- !IsApolloFederationType(type);
-
- private static bool IncludeField(IOutputField field)
- => !field.IsIntrospectionField &&
- !IsApolloFederationType(field.Type.NamedType());
-
- private static bool IsApolloFederationType(INamedType type)
- => type is EntityType or ServiceType ||
- type.Name.EqualsOrdinal(WellKnownTypeNames.Any) ||
- type.Name.EqualsOrdinal(WellKnownTypeNames.FieldSet);
-
- private static bool IsBuiltInType(INamedType type) =>
- IntrospectionTypes.IsIntrospectionType(type.Name) ||
- BuiltInTypes.IsBuiltInType(type.Name);
-
- private sealed class Context
- {
- // ReSharper disable once CollectionNeverQueried.Local
- public HashSet TypeNames { get; } = new();
- public HashSet DirectiveNames { get; } = new(_builtInDirectives);
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeInterceptor.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeInterceptor.cs
index 1a26cfe0302..fbaa5d91094 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeInterceptor.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeInterceptor.cs
@@ -4,24 +4,22 @@
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Descriptors;
-using HotChocolate.ApolloFederation.Helpers;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Configuration;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
+using HotChocolate.Types.Helpers;
using static HotChocolate.ApolloFederation.ThrowHelper;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
+using static HotChocolate.ApolloFederation.FederationContextData;
using static HotChocolate.Types.TagHelper;
namespace HotChocolate.ApolloFederation;
internal sealed class FederationTypeInterceptor : TypeInterceptor
{
- private static readonly object _empty = new();
-
private static readonly MethodInfo _matches =
typeof(ReferenceResolverHelper)
.GetMethod(
@@ -41,9 +39,15 @@ internal sealed class FederationTypeInterceptor : TypeInterceptor
BindingFlags.Static | BindingFlags.Public)!;
private readonly List _entityTypes = new();
+ private readonly Dictionary> _imports = new();
private IDescriptorContext _context = default!;
private ITypeInspector _typeInspector = default!;
+ private TypeRegistry _typeRegistry = default!;
private ObjectType _queryType = default!;
+ private ExtendedTypeDirectiveReference _keyDirectiveReference = default!;
+ private SchemaTypeDefinition _schemaTypeDefinition = default!;
+ private RegisteredType _schemaType = default!;
+ private bool _registeredTypes;
internal override void InitializeContext(
IDescriptorContext context,
@@ -52,8 +56,10 @@ internal override void InitializeContext(
TypeLookup typeLookup,
TypeReferenceResolver typeReferenceResolver)
{
- _context = context;
_typeInspector = context.TypeInspector;
+ _context = context;
+ _typeRegistry = typeRegistry;
+ _keyDirectiveReference = new ExtendedTypeDirectiveReference(_typeInspector.GetType(typeof(KeyDirective)));
ModifyOptions(context, o => o.Mode = TagMode.ApolloFederation);
}
@@ -79,11 +85,190 @@ public override void OnAfterInitialize(
}
}
- public override void OnTypesInitialized()
+ public override IEnumerable RegisterMoreTypes(
+ IReadOnlyCollection discoveryContexts)
{
- if (_entityTypes.Count == 0)
+ if (_registeredTypes)
{
- throw EntityType_NoEntities();
+ yield break;
+ }
+
+ _registeredTypes = true;
+ yield return _typeInspector.GetTypeRef(typeof(_Service));
+ yield return _typeInspector.GetTypeRef(typeof(_EntityType));
+ yield return _typeInspector.GetTypeRef(typeof(_AnyType));
+ yield return _typeInspector.GetTypeRef(typeof(FieldSetType));
+
+ if (_context.GetFederationVersion() > FederationVersion.Federation10)
+ {
+ yield return _typeInspector.GetTypeRef(typeof(LinkDirective));
+ }
+ }
+
+ public override void OnBeforeCompleteName(
+ ITypeCompletionContext completionContext,
+ DefinitionBase definition)
+ {
+ if (definition is SchemaTypeDefinition schemaDef)
+ {
+ _schemaType = (RegisteredType)completionContext;
+ _schemaTypeDefinition = schemaDef;
+ }
+ }
+
+ public override void OnAfterCompleteName(
+ ITypeCompletionContext completionContext,
+ DefinitionBase definition)
+ {
+ if (definition is not ITypeDefinition and not DirectiveTypeDefinition)
+ {
+ return;
+ }
+
+ var hasRuntimeType = (IHasRuntimeType)definition;
+ var type = hasRuntimeType.RuntimeType;
+
+ if (type != typeof(object) &&
+ type.IsDefined(typeof(PackageAttribute)))
+ {
+ RegisterImport(type);
+ return;
+ }
+
+ type = completionContext.Type.GetType();
+
+ if (type.IsDefined(typeof(PackageAttribute)))
+ {
+ RegisterImport(type);
+ }
+ return;
+
+ void RegisterImport(MemberInfo element)
+ {
+ var package = element.GetCustomAttribute();
+
+ if (package is null)
+ {
+ return;
+ }
+
+ if (!_imports.TryGetValue(package.Url, out var types))
+ {
+ types = new HashSet();
+ _imports[package.Url] = types;
+ }
+
+ if (completionContext.Type is DirectiveType)
+ {
+ types.Add($"@{completionContext.Type.Name}");
+ }
+ else
+ {
+ types.Add(completionContext.Type.Name);
+ }
+ }
+ }
+
+ public override void OnTypesCompletedName()
+ {
+ RegisterExportedDirectives();
+ RegisterImports();
+ }
+
+ private void RegisterImports()
+ {
+ if (_imports.Count == 0)
+ {
+ return;
+ }
+
+ var version = _context.GetFederationVersion();
+ var federationTypes = new HashSet();
+
+ foreach (var import in _imports)
+ {
+ if (!import.Key.TryToVersion(out var importVersion))
+ {
+ continue;
+ }
+
+ if (importVersion > version)
+ {
+ // todo: throw helper
+ throw new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ "The following federation types were used and are not supported by " +
+ "the current federation version: {0}",
+ string.Join(", ", import.Value))
+ .Build());
+ }
+
+ federationTypes.UnionWith(import.Value);
+ }
+
+ if (version == FederationVersion.Federation10)
+ {
+ return;
+ }
+
+ var dependency = new TypeDependency(
+ _typeInspector.GetTypeRef(typeof(LinkDirective)),
+ TypeDependencyFulfilled.Completed);
+ _schemaType.Dependencies.Add(dependency);
+
+ _schemaTypeDefinition
+ .GetLegacyDefinition()
+ .AddDirective(
+ new LinkDirective(version.ToUrl(), federationTypes),
+ _typeInspector);
+
+ foreach (var import in _imports)
+ {
+ if (import.Key.TryToVersion(out _))
+ {
+ continue;
+ }
+
+ _schemaTypeDefinition
+ .GetLegacyDefinition()
+ .AddDirective(
+ new LinkDirective(import.Key, import.Value),
+ _typeInspector);
+ }
+ }
+
+ private void RegisterExportedDirectives()
+ {
+ if (!_context.ContextData.TryGetValue(ExportedDirectives, out var value) ||
+ value is not List exportedDirectives)
+ {
+ return;
+ }
+
+ var composeDirectives = new List();
+ foreach (var exportedDirective in exportedDirectives)
+ {
+ var typeReference = _typeInspector.GetTypeRef(exportedDirective);
+ if (_typeRegistry.TryGetType(typeReference, out var exportedDirectiveType))
+ {
+ composeDirectives.Add(new ComposeDirective(exportedDirectiveType.Type.Name));
+ }
+ }
+
+ if (composeDirectives.Count > 0)
+ {
+ var dependency = new TypeDependency(
+ _typeInspector.GetTypeRef(typeof(ComposeDirective)),
+ TypeDependencyFulfilled.Completed);
+ _schemaType.Dependencies.Add(dependency);
+
+ foreach (var directive in composeDirectives)
+ {
+ _schemaTypeDefinition
+ .GetLegacyDefinition()
+ .AddDirective(directive, _typeInspector);
+ }
}
}
@@ -94,7 +279,15 @@ internal override void OnAfterResolveRootType(
{
if (operationType is OperationType.Query)
{
- _queryType = (ObjectType) completionContext.Type;
+ _queryType = (ObjectType)completionContext.Type;
+ }
+ }
+
+ public override void OnTypesInitialized()
+ {
+ if (_entityTypes.Count == 0)
+ {
+ throw EntityType_NoEntities();
}
}
@@ -123,49 +316,64 @@ public override void OnAfterCompleteType(
}
}
+ internal override void OnAfterCreateSchemaInternal(IDescriptorContext context, ISchema schema) { }
+
private void CompleteExternalFieldSetters(ObjectType type, ObjectTypeDefinition typeDef)
=> ExternalSetterExpressionHelper.TryAddExternalSetter(type, typeDef);
private void CompleteReferenceResolver(ObjectTypeDefinition typeDef)
{
- if (typeDef.GetContextData().TryGetValue(EntityResolver, out var value) &&
- value is IReadOnlyList resolvers)
+ IReadOnlyList resolvers;
{
- if (resolvers.Count == 1)
+ var contextData = typeDef.GetContextData();
+
+ if (!contextData.TryGetValue(EntityResolver, out var resolversObject))
{
- typeDef.ContextData[EntityResolver] = resolvers[0].Resolver;
+ return;
}
- else
+
+ if (resolversObject is not IReadOnlyList r)
{
- var expressions = new Stack<(Expression Condition, Expression Execute)>();
- var context = Expression.Parameter(typeof(IResolverContext));
+ return;
+ }
- foreach (var resolverDef in resolvers)
- {
- Expression required = Expression.Constant(resolverDef.Required);
- Expression resolver = Expression.Constant(resolverDef.Resolver);
- Expression condition = Expression.Call(_matches, context, required);
- Expression execute = Expression.Call(_execute, context, resolver);
- expressions.Push((condition, execute));
- }
+ resolvers = r;
+ }
- Expression current = Expression.Call(_invalid, context);
- var variable = Expression.Variable(typeof(ValueTask));
+ if (resolvers.Count == 1)
+ {
+ typeDef.ContextData[EntityResolver] = resolvers[0].Resolver;
+ }
+ else
+ {
+ var expressions = new Stack<(Expression Condition, Expression Execute)>();
+ var context = Expression.Parameter(typeof(IResolverContext));
- while (expressions.Count > 0)
- {
- var expression = expressions.Pop();
- current = Expression.IfThenElse(
- expression.Condition,
- Expression.Assign(variable, expression.Execute),
- current);
- }
+ foreach (var resolverDef in resolvers)
+ {
+ Expression required = Expression.Constant(resolverDef.Required);
+ Expression resolver = Expression.Constant(resolverDef.Resolver);
+ Expression condition = Expression.Call(_matches, context, required);
+ Expression execute = Expression.Call(_execute, context, resolver);
+ expressions.Push((condition, execute));
+ }
- current = Expression.Block(new[] { variable }, current, variable);
+ Expression current = Expression.Call(_invalid, context);
+ var variable = Expression.Variable(typeof(ValueTask));
- typeDef.ContextData[EntityResolver] =
- Expression.Lambda(current, context).Compile();
+ while (expressions.Count > 0)
+ {
+ var expression = expressions.Pop();
+ current = Expression.IfThenElse(
+ expression.Condition,
+ Expression.Assign(variable, expression.Execute),
+ current);
}
+
+ current = Expression.Block(new[] { variable }, current, variable);
+
+ typeDef.ContextData[EntityResolver] =
+ Expression.Lambda(current, context).Compile();
}
}
@@ -173,67 +381,59 @@ private void AddServiceTypeToQueryType(
ITypeCompletionContext completionContext,
DefinitionBase? definition)
{
- if (ReferenceEquals(completionContext.Type, _queryType) &&
- definition is ObjectTypeDefinition objectTypeDefinition)
+ if (!ReferenceEquals(completionContext.Type, _queryType))
{
- var serviceFieldDescriptor = ObjectFieldDescriptor.New(
- _context,
- WellKnownFieldNames.Service);
- serviceFieldDescriptor
- .Type>()
- .Resolve(_empty);
- objectTypeDefinition.Fields.Add(serviceFieldDescriptor.CreateDefinition());
-
- var entitiesFieldDescriptor = ObjectFieldDescriptor.New(
- _context,
- WellKnownFieldNames.Entities);
- entitiesFieldDescriptor
- .Type>>()
- .Argument(
- WellKnownArgumentNames.Representations,
- descriptor => descriptor.Type>>>())
- .Resolve(
- c => EntitiesResolver.ResolveAsync(
- c.Schema,
- c.ArgumentValue>(
- WellKnownArgumentNames.Representations),
- c
- ));
- objectTypeDefinition.Fields.Add(entitiesFieldDescriptor.CreateDefinition());
+ return;
}
+
+ var objectTypeDefinition = (ObjectTypeDefinition)definition!;
+ objectTypeDefinition.Fields.Add(ServerFields.CreateServiceField(_context));
+ objectTypeDefinition.Fields.Add(ServerFields.CreateEntitiesField(_context));
}
private void ApplyMethodLevelReferenceResolvers(
ObjectType objectType,
ObjectTypeDefinition objectTypeDefinition)
{
- if (objectType.RuntimeType != typeof(object))
+ if (objectType.RuntimeType == typeof(object))
{
- var descriptor = ObjectTypeDescriptor.From(_context, objectTypeDefinition);
+ return;
+ }
+
+ var descriptor = ObjectTypeDescriptor.From(_context, objectTypeDefinition);
- foreach (var possibleReferenceResolver in
- objectType.RuntimeType.GetMethods(BindingFlags.Static | BindingFlags.Public))
+ // Static methods won't end up in the schema as fields.
+ // The default initialization system only considers instance methods,
+ // so we have to handle the attributes for those manually.
+ var potentiallyUnregisteredReferenceResolvers = objectType.RuntimeType
+ .GetMethods(BindingFlags.Static | BindingFlags.Public);
+
+ foreach (var possibleReferenceResolver in potentiallyUnregisteredReferenceResolvers)
+ {
+ if (!possibleReferenceResolver.IsDefined(typeof(ReferenceResolverAttribute)))
{
- if (possibleReferenceResolver.IsDefined(typeof(ReferenceResolverAttribute)))
+ continue;
+ }
+
+ foreach (var attribute in possibleReferenceResolver.GetCustomAttributes(true))
+ {
+ if (attribute is ReferenceResolverAttribute casted)
{
- _typeInspector.ApplyAttributes(
- _context,
- descriptor,
- possibleReferenceResolver);
+ casted.TryConfigure(_context, descriptor, possibleReferenceResolver);
}
}
-
- descriptor.CreateDefinition();
}
+
+ // This seems to re-detect the entity resolver and save it into the context data.
+ descriptor.CreateDefinition();
}
private void AddToUnionIfHasTypeLevelKeyDirective(
ObjectType objectType,
ObjectTypeDefinition objectTypeDefinition)
{
- if (objectTypeDefinition.Directives.Any(
- d => d.Value is DirectiveNode { Name.Value: WellKnownTypeNames.Key }) ||
- objectTypeDefinition.Fields.Any(f => f.ContextData.ContainsKey(WellKnownTypeNames.Key)))
+ if (objectTypeDefinition.Directives.Any(d => d.Value is KeyDirective) ||
+ objectTypeDefinition.Fields.Any(f => f.ContextData.ContainsKey(KeyMarker)))
{
_entityTypes.Add(objectType);
}
@@ -246,50 +446,56 @@ private void AggregatePropertyLevelKeyDirectives(
{
// if we find key markers on our fields, we need to construct the key directive
// from the annotated fields.
- if (objectTypeDefinition.Fields.Any(f => f.ContextData.ContainsKey(KeyMarker)))
{
- IReadOnlyList fields = objectTypeDefinition.Fields;
- var fieldSet = new StringBuilder();
+ var foundMarkers = objectTypeDefinition.Fields.Any(f => f.ContextData.ContainsKey(KeyMarker));
- foreach (var fieldDefinition in fields)
+ if (!foundMarkers)
{
- if (fieldDefinition.ContextData.ContainsKey(KeyMarker))
- {
- if (fieldSet.Length > 0)
- {
- fieldSet.Append(' ');
- }
-
- fieldSet.Append(fieldDefinition.Name);
- }
+ return;
}
+ }
- // add the key directive with the dynamically generated field set.
- AddKeyDirective(objectTypeDefinition, fieldSet.ToString());
+ IReadOnlyList fields = objectTypeDefinition.Fields;
+ var fieldSet = new StringBuilder();
- // register dependency to the key directive so that it is completed before
- // we complete this type.
- foreach (var directiveDefinition in objectTypeDefinition.Directives)
+ foreach (var fieldDefinition in fields)
+ {
+ if (fieldDefinition.ContextData.ContainsKey(KeyMarker))
{
- discoveryContext.Dependencies.Add(
- new TypeDependency(
- directiveDefinition.Type,
- TypeDependencyFulfilled.Completed));
+ if (fieldSet.Length > 0)
+ {
+ fieldSet.Append(' ');
+ }
- discoveryContext.Dependencies.Add(new(directiveDefinition.Type));
+ fieldSet.Append(fieldDefinition.Name);
}
+ }
- // since this type has now a key directive we also need to add this type to
- // the _Entity union type.
- _entityTypes.Add(objectType);
+ // add the key directive with the dynamically generated field set.
+ AddKeyDirective(objectTypeDefinition, fieldSet.ToString());
+
+ // register dependency to the key directive so that it is completed before
+ // we complete this type.
+ foreach (var directiveDefinition in objectTypeDefinition.Directives)
+ {
+ discoveryContext.Dependencies.Add(
+ new TypeDependency(
+ directiveDefinition.Type,
+ TypeDependencyFulfilled.Completed));
+
+ discoveryContext.Dependencies.Add(new(directiveDefinition.Type));
}
+
+ // since this type has now a key directive we also need to add this type to
+ // the _Entity union type.
+ _entityTypes.Add(objectType);
}
private void AddMemberTypesToTheEntityUnionType(
ITypeCompletionContext completionContext,
DefinitionBase? definition)
{
- if (completionContext.Type is EntityType &&
+ if (completionContext.Type is _EntityType &&
definition is UnionTypeDefinition unionTypeDefinition)
{
foreach (var objectType in _entityTypes)
@@ -299,17 +505,13 @@ private void AddMemberTypesToTheEntityUnionType(
}
}
- private static void AddKeyDirective(
+ private void AddKeyDirective(
ObjectTypeDefinition objectTypeDefinition,
string fieldSet)
{
- var directiveNode = new DirectiveNode(
- WellKnownTypeNames.Key,
- new ArgumentNode(
- WellKnownArgumentNames.Fields,
- fieldSet));
-
objectTypeDefinition.Directives.Add(
- new DirectiveDefinition(directiveNode));
+ new DirectiveDefinition(
+ new KeyDirective(fieldSet),
+ _keyDirectiveReference));
}
}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs
new file mode 100644
index 00000000000..6b9a29c7efb
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs
@@ -0,0 +1,24 @@
+namespace HotChocolate.ApolloFederation;
+
+internal static class FederationTypeNames
+{
+ public const string AuthenticatedDirective_Name = "authenticated";
+ public const string ContactDirective_Name = "contact";
+ public const string ComposeDirective_Name = "composeDirective";
+ public const string ExtendsDirective_Name = "extends";
+ public const string ExternalDirective_Name = "external";
+ public const string InaccessibleDirective_Name = "inaccessible";
+ public const string InterfaceObject_Name = "interfaceObject";
+ public const string KeyDirective_Name = "key";
+ public const string LinkDirective_Name = "link";
+ public const string OverrideDirective_Name = "override";
+ public const string ProvidesDirective_Name = "provides";
+ public const string RequiresDirective_Name = "requires";
+ public const string RequiresScopesDirective_Name = "requiresScopes";
+ public const string ShareableDirective_Name = "shareable";
+ public const string FieldSetType_Name = "FieldSet";
+ public const string ScopeType_Name = "Scope";
+ public const string AnyType_Name = "_Any";
+ public const string EntityType_Name = "_Entity";
+ public const string ServiceType_Name = "_Service";
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersion.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersion.cs
new file mode 100644
index 00000000000..f3626a7c84c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersion.cs
@@ -0,0 +1,18 @@
+namespace HotChocolate.ApolloFederation;
+
+///
+/// Enum defining all supported Apollo Federation v2 versions.
+///
+public enum FederationVersion
+{
+ Unknown = 0,
+ Federation10 = 1_0,
+ Federation20 = 2_0,
+ Federation21 = 2_1,
+ Federation22 = 2_2,
+ Federation23 = 2_3,
+ Federation24 = 2_4,
+ Federation25 = 2_5,
+ // Federation26 = 2_6,
+ Latest = Federation25,
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs
new file mode 100644
index 00000000000..054b96ce3cb
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs
@@ -0,0 +1,11 @@
+namespace HotChocolate.ApolloFederation;
+
+internal static class FederationVersionUrls
+{
+ public const string Federation20 = "https://specs.apollo.dev/federation/v2.0";
+ public const string Federation21 = "https://specs.apollo.dev/federation/v2.1";
+ public const string Federation22 = "https://specs.apollo.dev/federation/v2.2";
+ public const string Federation23 = "https://specs.apollo.dev/federation/v2.3";
+ public const string Federation24 = "https://specs.apollo.dev/federation/v2.4";
+ public const string Federation25 = "https://specs.apollo.dev/federation/v2.5";
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverArgumentExpressionBuilder.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverArgumentExpressionBuilder.cs
deleted file mode 100644
index fdeebcf9e19..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverArgumentExpressionBuilder.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.Collections.Generic;
-using System.Linq.Expressions;
-using System.Reflection;
-using HotChocolate.Internal;
-using HotChocolate.Language;
-using HotChocolate.Resolvers;
-using HotChocolate.Resolvers.Expressions;
-using HotChocolate.Resolvers.Expressions.Parameters;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
-using static HotChocolate.Resolvers.Expressions.Parameters.ParameterExpressionBuilderHelpers;
-
-namespace HotChocolate.ApolloFederation.Helpers;
-
-internal sealed class ReferenceResolverArgumentExpressionBuilder
- : ScopedStateParameterExpressionBuilder
-{
- private readonly MethodInfo _getValue =
- typeof(ArgumentParser).GetMethod(
- nameof(ArgumentParser.GetValue),
- BindingFlags.Static | BindingFlags.Public)!;
-
- public override ArgumentKind Kind => ArgumentKind.LocalState;
-
- protected override PropertyInfo ContextDataProperty { get; } =
- ContextType.GetProperty(nameof(IResolverContext.LocalContextData))!;
-
- protected override MethodInfo SetStateMethod { get; } =
- typeof(ExpressionHelper).GetMethod(nameof(ExpressionHelper.SetLocalState))!;
-
- protected override MethodInfo SetStateGenericMethod { get; } =
- typeof(ExpressionHelper).GetMethod(nameof(ExpressionHelper.SetLocalStateGeneric))!;
-
- public override bool IsDefaultHandler => true;
-
- public IReadOnlyList Required { get; private set; } = Array.Empty();
-
- public override bool CanHandle(ParameterInfo parameter) => true;
-
- protected override string GetKey(ParameterInfo parameter) => DataField;
-
- public override Expression Build(ParameterExpressionBuilderContext context)
- {
- var param = context.Parameter;
- var path = Expression.Constant(GetPath(param), typeof(string[]));
- var dataKey = Expression.Constant(DataField, typeof(string));
- var typeKey = Expression.Constant(TypeField, typeof(string));
- var value = BuildGetter(param, dataKey, context.ResolverContext, typeof(IValueNode));
- var objectType = BuildGetter(param, typeKey, context.ResolverContext, typeof(ObjectType));
- var getValueMethod = _getValue.MakeGenericMethod(param.ParameterType);
- Expression getValue = Expression.Call(getValueMethod, value, objectType, path);
- return getValue;
- }
-
- private string[] GetPath(ParameterInfo parameter)
- {
- var path = parameter.GetCustomAttribute() is { } attr
- ? attr.Path.Split('.')
- : new[] { parameter.Name! };
-
- if (Required.Count == 0)
- {
- Required = new[] { path };
- }
- else if (Required.Count == 1)
- {
- var required = new List(Required) { path };
- Required = required;
- }
- else if (Required is List list)
- {
- list.Add(path);
- }
-
- return path;
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/HotChocolate.ApolloFederation.csproj b/src/HotChocolate/ApolloFederation/src/ApolloFederation/HotChocolate.ApolloFederation.csproj
index 4c46ea457bb..cb595c7a0a1 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/HotChocolate.ApolloFederation.csproj
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/HotChocolate.ApolloFederation.csproj
@@ -23,6 +23,87 @@
+
+ ProvidesDirective.cs
+
+
+ ProvidesDirective.cs
+
+
+ RequiresDirective.cs
+
+
+ RequiresDirective.cs
+
+
+ KeyDirective.cs
+
+
+ KeyDirective.cs
+
+
+ ExternalDirective.cs
+
+
+ ExternalDirective.cs
+
+
+ ExtendServiceTypeDirective.cs
+
+
+ ExtendServiceTypeDirective.cs
+
+
+ ContactDirective.cs
+
+
+ OverrideDirective.cs
+
+
+ OverrideDirective.cs
+
+
+ ShareableDirective.cs
+
+
+ ShareableDirective.cs
+
+
+ InterfaceObjectDirective.cs
+
+
+ InterfaceObjectDirective.cs
+
+
+ KeyDirective.cs
+
+
+ LinkDirective.cs
+
+
+ LinkDirective.cs
+
+
+ InaccessibleDirective.cs
+
+
+ InaccessibleDirective.cs
+
+
+ ComposeDirective.cs
+
+
+ FieldSetType.cs
+
+
+ AuthenticatedDirective.cs
+
+
+ AuthenticatedDirective.cs
+
+
+ RequiresScopesDirective.cs
+
True
True
@@ -32,6 +113,9 @@
ResXFileCodeGenerator
FederationResources.Designer.cs
+
+ RequiresScopesDirective.cs
+
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyAttribute.cs
deleted file mode 100644
index 5832b476c4b..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyAttribute.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Reflection;
-using HotChocolate.Types.Descriptors;
-using static HotChocolate.ApolloFederation.ThrowHelper;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @key directive is used to indicate a combination of fields that
-/// can be used to uniquely identify and fetch an object or interface.
-///
-/// type Product @key(fields: "upc") {
-/// upc: UPC!
-/// name: String
-/// }
-///
-///
-[AttributeUsage(
- AttributeTargets.Class |
- AttributeTargets.Struct |
- AttributeTargets.Interface |
- AttributeTargets.Property |
- AttributeTargets.Method,
- AllowMultiple = true)]
-public sealed class KeyAttribute : DescriptorAttribute
-{
- ///
- /// Initializes a new instance of .
- ///
- ///
- /// The field set that describes the key.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- public KeyAttribute(string? fieldSet = default)
- {
- FieldSet = fieldSet;
- }
-
- ///
- /// Gets the field set that describes the key.
- /// Grammatically, a field set is a selection set minus the braces.
- ///
- public string? FieldSet { get; }
-
- protected internal override void TryConfigure(
- IDescriptorContext context,
- IDescriptor descriptor,
- ICustomAttributeProvider element)
- {
- switch (descriptor)
- {
- case IObjectTypeDescriptor objectTypeDescriptor when element is Type runtimeType:
- {
- if (string.IsNullOrEmpty(FieldSet))
- {
- throw Key_FieldSet_CannotBeEmpty(runtimeType);
- }
-
- objectTypeDescriptor.Key(FieldSet!);
- break;
- }
-
- case IObjectFieldDescriptor objectFieldDescriptor when element is MemberInfo:
- objectFieldDescriptor
- .Extend()
- .OnBeforeCreate(d => d.ContextData[Constants.WellKnownContextData.KeyMarker] = true);
- break;
- }
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyDirectiveType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyDirectiveType.cs
deleted file mode 100644
index 9b8de0c9317..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/KeyDirectiveType.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Properties;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @key directive is used to indicate a combination of fields that
-/// can be used to uniquely identify and fetch an object or interface.
-///
-/// type Product @key(fields: "upc") {
-/// upc: UPC!
-/// name: String
-/// }
-///
-///
-public sealed class KeyDirectiveType : DirectiveType
-{
- protected override void Configure(IDirectiveTypeDescriptor descriptor)
- => descriptor
- .Name(WellKnownTypeNames.Key)
- .Description(FederationResources.KeyDirective_Description)
- .Location(DirectiveLocation.Object | DirectiveLocation.Interface)
- .Repeatable()
- .FieldsArgument();
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs
index a1f8b0ee7a2..64465d4a006 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs
@@ -14,7 +14,7 @@ namespace HotChocolate.ApolloFederation.Properties {
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class FederationResources {
+ internal partial class FederationResources {
private static System.Resources.ResourceManager resourceMan;
@@ -45,39 +45,33 @@ internal static System.Globalization.CultureInfo Culture {
}
}
- internal static string ExternalDirective_Description {
- get {
- return ResourceManager.GetString("ExternalDirective_Description", resourceCulture);
- }
- }
-
internal static string FieldsetType_Description {
get {
return ResourceManager.GetString("FieldsetType_Description", resourceCulture);
}
}
- internal static string KeyDirective_Description {
+ internal static string ScopeType_Description {
get {
- return ResourceManager.GetString("KeyDirective_Description", resourceCulture);
+ return ResourceManager.GetString("ScopeType_Description", resourceCulture);
}
}
- internal static string ProvidesDirective_Description {
+ internal static string TagDirective_Description {
get {
- return ResourceManager.GetString("ProvidesDirective_Description", resourceCulture);
+ return ResourceManager.GetString("TagDirective_Description", resourceCulture);
}
}
- internal static string RequiresDirective_Description {
+ internal static string EntityType_Description {
get {
- return ResourceManager.GetString("RequiresDirective_Description", resourceCulture);
+ return ResourceManager.GetString("EntityType_Description", resourceCulture);
}
}
- internal static string EntityType_Description {
+ internal static string PolicyDirective_Description {
get {
- return ResourceManager.GetString("EntityType_Description", resourceCulture);
+ return ResourceManager.GetString("PolicyDirective_Description", resourceCulture);
}
}
@@ -111,6 +105,30 @@ internal static string ThrowHelper_Requires_FieldSet_CannotBeEmpty {
}
}
+ internal static string ThrowHelper_ComposeDirective_Name_CannotBeEmpty {
+ get {
+ return ResourceManager.GetString("ThrowHelper_ComposeDirective_Name_CannotBeEmpty", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_Link_Url_CannotBeEmpty {
+ get {
+ return ResourceManager.GetString("ThrowHelper_Link_Url_CannotBeEmpty", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_Contact_Name_CannotBeEmpty {
+ get {
+ return ResourceManager.GetString("ThrowHelper_Contact_Name_CannotBeEmpty", resourceCulture);
+ }
+ }
+
+ internal static string ThrowHelper_FederationVersion_Unknown {
+ get {
+ return ResourceManager.GetString("ThrowHelper_FederationVersion_Unknown", resourceCulture);
+ }
+ }
+
internal static string FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty {
get {
return ResourceManager.GetString("FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty", resourceCulture);
@@ -129,15 +147,15 @@ internal static string FieldDescriptorExtensions_Provides_FieldSet_CannotBeNullO
}
}
- internal static string ThrowHelper_EntityType_NoEntities {
+ internal static string FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty {
get {
- return ResourceManager.GetString("ThrowHelper_EntityType_NoEntities", resourceCulture);
+ return ResourceManager.GetString("FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty", resourceCulture);
}
}
- internal static string ServiceType_Description {
+ internal static string ThrowHelper_EntityType_NoEntities {
get {
- return ResourceManager.GetString("ServiceType_Description", resourceCulture);
+ return ResourceManager.GetString("ThrowHelper_EntityType_NoEntities", resourceCulture);
}
}
@@ -159,6 +177,12 @@ internal static string ThrowHelper_Any_HasInvalidFormat {
}
}
+ internal static string ExpressionHelper_GetScopedStateWithDefault_NoDefaultValue {
+ get {
+ return ResourceManager.GetString("ExpressionHelper_GetScopedStateWithDefault_NoDefaultValue", resourceCulture);
+ }
+ }
+
internal static string Any_Description {
get {
return ResourceManager.GetString("Any_Description", resourceCulture);
@@ -170,5 +194,11 @@ internal static string ResolveReference_MustBeMethod {
return ResourceManager.GetString("ResolveReference_MustBeMethod", resourceCulture);
}
}
+
+ internal static string PolicyCollectionType_ParseValue_ExpectedStringArray {
+ get {
+ return ResourceManager.GetString("PolicyCollectionType_ParseValue_ExpectedStringArray", resourceCulture);
+ }
+ }
}
}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.cs
new file mode 100644
index 00000000000..f37f2257f5c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.cs
@@ -0,0 +1,55 @@
+namespace HotChocolate.ApolloFederation.Properties;
+
+internal partial class FederationResources
+{
+ public const string ProvidesDirective_Description =
+ "Used to annotate the expected returned fieldset from a field on a base type " +
+ "that is guaranteed to be selectable by the federation gateway.";
+
+ public const string KeyDirective_Description =
+ "Used to indicate a combination of fields that can be used to uniquely identify " +
+ "and fetch an object or interface.";
+
+ public const string ExternalDirective_Description =
+ "Directive to indicate that a field is owned by another service, " +
+ "for example via Apollo federation.";
+
+ public const string ExtendsDirective_Description =
+ "Directive to indicate that marks target object as extending part of the federated schema.";
+
+ public const string ContactDirective_Description =
+ "Provides contact information of the owner responsible for this subgraph schema.";
+
+ public const string RequiresDirective_Description =
+ "Used to annotate the required input fieldset from a base type for a resolver.";
+
+ public const string OverrideDirective_Description =
+ "Overrides fields resolution logic from other subgraph. " +
+ "Used for migrating fields from one subgraph to another.";
+
+ public const string ShareableDirective_Description =
+ "Indicates that given object and/or field can be resolved by multiple subgraphs.";
+
+ public const string InterfaceObjectDirective_Description =
+ "Provides meta information to the router that this entity type is an interface in the supergraph.";
+
+ public const string InaccessibleDirective_Description =
+ "Marks location within schema as inaccessible from the GraphQL Gateway.";
+
+ public const string ComposeDirective_Description =
+ "Marks underlying custom directive to be included in the Supergraph schema.";
+
+ public const string ServiceType_Description =
+ "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.";
+
+ public const string AuthenticatedDirective_Description =
+ "Indicates to composition that the target element is accessible " +
+ "only to the authenticated supergraph users.";
+
+ public const string RequiresScopesDirective_Description =
+ "Indicates to composition that the target element is accessible only " +
+ "to the authenticated supergraph users with the appropriate JWT scopes.";
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx
index 5edeea66e82..73302292ad2 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx
@@ -117,24 +117,21 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- Directive to indicate that a field is owned by another service, for example via Apollo federation.
-
Scalar representing a set of fields.
-
- Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface.
-
-
- Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway.
+
+ Scalar representing a JWT scope
-
- Used to annotate the required input fieldset from a base type for a resolver.
+
+ Allows users to annotate fields and types with additional metadata information.
Union of all types that key directive applied. This information is needed by the Apollo federation gateway.
+
+ Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor.
+
The fieldset has an invalid format.
@@ -142,7 +139,7 @@
{0} cannot parse the given value of type `{1}`
- The key attribute is used on `{0}` without specifying the the field set.
+ The key attribute is used on `{0}` without specifying the field set.
FieldSet is null or empty on type
@@ -150,6 +147,18 @@
FieldSet is null or empty on type
+
+ The compose directive attribute is used on `{0}` without specifying the name.
+
+
+ The link attribute is used on `{0}` without specifying the url.
+
+
+ The contact attribute is used on `{0}` without specifying the name.
+
+
+ Specified federation version `{0}` is not supported.
+
Value cannot be null or empty.
@@ -159,11 +168,11 @@
Value cannot be null or empty.
-
- The schema has no types with a KeyDirective and therefor no entities. Apollo federation requires at least on entity.
+
+ Value cannot be null or empty.
-
- 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.
+
+ The schema has no types with a KeyDirective and therefore no entities. Apollo federation requires at least one entity.
The apollo gateway tries to resolve an entity for which no EntityResolver method was found.
@@ -174,10 +183,16 @@
The given any representation has an invalid format.
+
+ The specified key `{0}` does not exist on `context.ScopedContextData`.
+
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.
The expression must refer to a method representing the reference resolver.
+
+ Expected a string[][]
+
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesDirectiveType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesDirectiveType.cs
deleted file mode 100644
index caebfc1bc5e..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesDirectiveType.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Properties;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @provides directive is used to annotate the expected returned fieldset
-/// from a field on a base type that is guaranteed to be selectable by the gateway.
-///
-///
-/// type Review @key(fields: "id") {
-/// product: Product @provides(fields: "name")
-/// }
-///
-/// extend type Product @key(fields: "upc") {
-/// upc: String @external
-/// name: String @external
-/// }
-///
-///
-public sealed class ProvidesDirectiveType : DirectiveType
-{
- protected override void Configure(IDirectiveTypeDescriptor descriptor)
- => descriptor
- .Name(WellKnownTypeNames.Provides)
- .Description(FederationResources.ProvidesDirective_Description)
- .Location(DirectiveLocation.FieldDefinition)
- .FieldsArgument();
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs
index ab9c5728146..fe8ae7cef56 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Representation.cs
@@ -20,8 +20,9 @@ public sealed class Representation
///
public Representation(string typeName, ObjectValueNode data)
{
+ ArgumentNullException.ThrowIfNull(data);
TypeName = typeName.EnsureGraphQLName();
- Data = data ?? throw new ArgumentNullException(nameof(data));
+ Data = data;
}
///
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresDirectiveType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresDirectiveType.cs
deleted file mode 100644
index 28ca84decd0..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresDirectiveType.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Properties;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// The @requires directive is used to annotate the required input fieldset
-/// from a base type for a resolver. It is used to develop a query plan
-/// where the required fields may not be needed by the client, but the
-/// service may need additional information from other services.
-///
-///
-/// # extended from the Users service
-/// extend type User @key(fields: "id") {
-/// id: ID! @external
-/// email: String @external
-/// reviews: [Review] @requires(fields: "email")
-/// }
-///
-///
-public sealed class RequiresDirectiveType : DirectiveType
-{
- protected override void Configure(IDirectiveTypeDescriptor descriptor)
- => descriptor
- .Name(WellKnownTypeNames.Requires)
- .Description(FederationResources.RequiresDirective_Description)
- .Location(DirectiveLocation.FieldDefinition)
- .FieldsArgument();
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ArgumentParser.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ArgumentParser.cs
similarity index 93%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ArgumentParser.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ArgumentParser.cs
index 1331d199419..f4a3da4f568 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ArgumentParser.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ArgumentParser.cs
@@ -3,7 +3,7 @@
using HotChocolate.Language;
using HotChocolate.Utilities;
-namespace HotChocolate.ApolloFederation.Helpers;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
/// A helper for getting field values from a representation object.
@@ -21,7 +21,7 @@ public static bool TryGetValue(
IType type,
string[] path,
out T? value)
- => TryGetValue(valueNode, type, path, 0, out value);
+ => TryGetValue(valueNode, type, path, 0, out value);
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static bool TryGetValue(
@@ -35,6 +35,7 @@ private static bool TryGetValue(
switch (valueNode.Kind)
{
case SyntaxKind.ObjectValue:
+ {
var current = path[i];
if (type is not IComplexOutputType complexType ||
@@ -56,15 +57,17 @@ private static bool TryGetValue(
}
}
break;
-
+ }
case SyntaxKind.NullValue:
- value = default(T);
+ {
+ value = default;
return true;
-
+ }
case SyntaxKind.StringValue:
case SyntaxKind.IntValue:
case SyntaxKind.FloatValue:
case SyntaxKind.BooleanValue:
+ {
if (type.NamedType() is not ScalarType scalarType)
{
break;
@@ -72,8 +75,10 @@ private static bool TryGetValue(
value = (T)scalarType.ParseLiteral(valueNode)!;
return true;
+ }
case SyntaxKind.EnumValue:
+ {
if (type.NamedType() is not EnumType enumType)
{
break;
@@ -81,6 +86,7 @@ private static bool TryGetValue(
value = (T)enumType.ParseLiteral(valueNode)!;
return true;
+ }
}
value = default;
@@ -117,6 +123,7 @@ private static bool Matches(IValueNode valueNode, string[] path, int i)
switch (valueNode.Kind)
{
case SyntaxKind.ObjectValue:
+ {
var current = path[i];
foreach (var fieldValue in ((ObjectValueNode)valueNode).Fields)
@@ -131,6 +138,7 @@ private static bool Matches(IValueNode valueNode, string[] path, int i)
}
}
break;
+ }
case SyntaxKind.NullValue:
case SyntaxKind.StringValue:
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/EntitiesResolver.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/EntitiesResolver.cs
similarity index 95%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/EntitiesResolver.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/EntitiesResolver.cs
index 26f241b8a3a..b700eafbc15 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/EntitiesResolver.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/EntitiesResolver.cs
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using HotChocolate.Resolvers;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
+using static HotChocolate.ApolloFederation.FederationContextData;
-namespace HotChocolate.ApolloFederation.Helpers;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
/// This class contains the _entities resolver method.
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ExternalSetterExpressionHelper.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ExternalSetterExpressionHelper.cs
similarity index 86%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ExternalSetterExpressionHelper.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ExternalSetterExpressionHelper.cs
index ad38d811cb1..04de4e3c05f 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ExternalSetterExpressionHelper.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ExternalSetterExpressionHelper.cs
@@ -1,17 +1,16 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
-using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Language;
using HotChocolate.Types.Descriptors.Definitions;
using static System.Linq.Expressions.Expression;
using static System.Reflection.BindingFlags;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
-namespace HotChocolate.ApolloFederation.Helpers;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
-/// This class contains helpers to genereate external field setters.
+/// This class contains helpers to generate external field setters.
///
internal static class ExternalSetterExpressionHelper
{
@@ -32,7 +31,7 @@ public static void TryAddExternalSetter(ObjectType type, ObjectTypeDefinition ty
foreach (var field in type.Fields)
{
- if (field.Directives.ContainsDirective(WellKnownTypeNames.External) &&
+ if (field.Directives.ContainsDirective() &&
field.Member is PropertyInfo { SetMethod: { } } property)
{
var expression = CreateTrySetValue(type.RuntimeType, property, field.Name);
@@ -42,7 +41,7 @@ public static void TryAddExternalSetter(ObjectType type, ObjectTypeDefinition ty
if (block is not null)
{
- typeDef.ContextData[ExternalSetter] =
+ typeDef.ContextData[FederationContextData.ExternalSetter] =
Lambda>(
Block(block), _type, _data, _entity)
.Compile();
@@ -65,7 +64,7 @@ private static Expression CreateSetValue(
PropertyInfo property)
=> (Expression)_createSetValueExpression
.MakeGenericMethod(property.PropertyType)
- .Invoke(null, new object[] { runtimeType, property })!;
+ .Invoke(null, [runtimeType, property])!;
private static Expression CreateSetValueExpression(
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/MapAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/MapAttribute.cs
similarity index 81%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/MapAttribute.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/MapAttribute.cs
index 513555db5da..aebfdec154b 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/MapAttribute.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/MapAttribute.cs
@@ -1,4 +1,4 @@
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
/// Maps an argument to a representation value.
@@ -14,7 +14,8 @@ public class MapAttribute : Attribute
///
public MapAttribute(string path)
{
- Path = path ?? throw new ArgumentNullException(nameof(path));
+ ArgumentNullException.ThrowIfNull(path);
+ Path = path;
}
///
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverArgumentExpressionBuilder.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverArgumentExpressionBuilder.cs
new file mode 100644
index 00000000000..4ae12c7919a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverArgumentExpressionBuilder.cs
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
+using HotChocolate.Internal;
+using HotChocolate.Language;
+using HotChocolate.Resolvers.Expressions.Parameters;
+
+namespace HotChocolate.ApolloFederation.Resolvers;
+
+internal sealed class ReferenceResolverArgumentExpressionBuilder :
+ LocalStateParameterExpressionBuilder
+{
+ private readonly MethodInfo _getValue =
+ typeof(ArgumentParser).GetMethod(
+ nameof(ArgumentParser.GetValue),
+ BindingFlags.Static | BindingFlags.Public)!;
+
+ public override Expression Build(ParameterExpressionBuilderContext context)
+ {
+ var param = context.Parameter;
+ var path = Expression.Constant(
+ RequirePathAndGetSeparatedPath(param),
+ typeof(string[]));
+ var dataKey = Expression.Constant(
+ FederationContextData.DataField,
+ typeof(string));
+ var typeKey = Expression.Constant(
+ FederationContextData.TypeField,
+ typeof(string));
+ var value = BuildGetter(
+ param,
+ dataKey,
+ context.ResolverContext,
+ typeof(IValueNode));
+ var objectType = BuildGetter(
+ param,
+ typeKey,
+ context.ResolverContext,
+ typeof(ObjectType));
+ var getValueMethod = _getValue.MakeGenericMethod(param.ParameterType);
+ var getValue = Expression.Call(
+ getValueMethod,
+ value,
+ objectType,
+ path);
+ return getValue;
+ }
+
+ // NOTE: It will use the default handler without these two.
+ public override bool IsDefaultHandler => true;
+
+ public override bool CanHandle(ParameterInfo parameter) => true;
+
+ private string[] RequirePathAndGetSeparatedPath(ParameterInfo parameter)
+ {
+ var path = parameter.GetCustomAttribute() is { } attr
+ ? attr.Path.Split('.')
+ : [parameter.Name!];
+
+ _requiredPaths.Add(path);
+
+ return path;
+ }
+
+ private readonly List _requiredPaths = [];
+
+ public IReadOnlyList Required => _requiredPaths;
+
+ protected override bool ResolveDefaultIfNotExistsParameterValue(
+ Type targetType,
+ ParameterInfo parameter)
+ => false;
+
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ReferenceResolverAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverAttribute.cs
similarity index 90%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/ReferenceResolverAttribute.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverAttribute.cs
index f7444142df3..4d3e56d1f82 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ReferenceResolverAttribute.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverAttribute.cs
@@ -1,16 +1,18 @@
using System.Reflection;
-using HotChocolate.ApolloFederation.Descriptors;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types.Descriptors;
using static HotChocolate.ApolloFederation.ThrowHelper;
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
/// The reference resolver enables your gateway's query planner to resolve a particular
/// entity by whatever unique identifier your other subgraphs use to reference it.
///
[AttributeUsage(
- AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method,
+ AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Method,
AllowMultiple = true)]
public class ReferenceResolverAttribute : DescriptorAttribute
{
@@ -81,7 +83,7 @@ private void OnConfigure(IObjectTypeDescriptor descriptor, Type type)
}
}
- private void OnConfigure(IObjectTypeDescriptor descriptor, MethodInfo method)
+ private static void OnConfigure(IObjectTypeDescriptor descriptor, MethodInfo method)
{
var entityResolverDescriptor = new EntityResolverDescriptor(descriptor);
entityResolverDescriptor.ResolveReferenceWith(method);
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverHelper.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverHelper.cs
similarity index 92%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverHelper.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverHelper.cs
index 6e257fb14d7..69d97be3ac6 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Helpers/ReferenceResolverHelper.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Resolvers/ReferenceResolverHelper.cs
@@ -2,9 +2,9 @@
using System.Threading.Tasks;
using HotChocolate.Language;
using HotChocolate.Resolvers;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
+using static HotChocolate.ApolloFederation.FederationContextData;
-namespace HotChocolate.ApolloFederation.Helpers;
+namespace HotChocolate.ApolloFederation.Resolvers;
///
/// This class provides helpers for the reference resolver expression generators.
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ServiceType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ServiceType.cs
deleted file mode 100644
index 64a1e86fb66..00000000000
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ServiceType.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using HotChocolate.ApolloFederation.Constants;
-using HotChocolate.ApolloFederation.Properties;
-
-namespace HotChocolate.ApolloFederation;
-
-///
-/// A new object type called _Service must be created.
-/// This type must have an sdl: String! field 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.
-///
-public class ServiceType : ObjectType
-{
- protected override void Configure(IObjectTypeDescriptor descriptor)
- => descriptor
- .Name(WellKnownTypeNames.Service)
- .Description(FederationResources.ServiceType_Description)
- .Field(WellKnownFieldNames.Sdl)
- .Type>()
- .Resolve(resolver => FederationSchemaPrinter.Print(resolver.Schema));
-}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ThrowHelper.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ThrowHelper.cs
index 2804e2a6f2a..da07894b425 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ThrowHelper.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/ThrowHelper.cs
@@ -1,4 +1,5 @@
using System.Reflection;
+using HotChocolate.ApolloFederation.Types;
using static HotChocolate.ApolloFederation.Properties.FederationResources;
namespace HotChocolate.ApolloFederation;
@@ -27,7 +28,7 @@ public static SerializationException FieldSet_InvalidFormat(
/// node value has an invalid format.
///
public static SerializationException Any_InvalidFormat(
- AnyType anyType) =>
+ _AnyType anyType) =>
new SerializationException(
ErrorBuilder.New()
.SetMessage(ThrowHelper_Any_HasInvalidFormat)
@@ -41,14 +42,16 @@ public static SerializationException Any_InvalidFormat(
/// of the given type.
///
public static SchemaException ExternalAttribute_InvalidTarget(
- Type type, MemberInfo? member) =>
- new SchemaException(SchemaErrorBuilder.New()
- .SetMessage(
- "The specified external attribute was applied to " +
- "the member `{0}` of `{1}`, which is not a property.",
- member?.Name,
- type.FullName ?? type.Name)
- .Build());
+ Type type,
+ MemberInfo? member) =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ "The specified external attribute was applied to " +
+ "the member `{0}` of `{1}`, which is not a property.",
+ member?.Name,
+ type.FullName ?? type.Name)
+ .Build());
///
/// No ReferenceResolver was found for a type that is decorated with a ReferenceResolverAttribute.
@@ -56,12 +59,13 @@ public static SchemaException ExternalAttribute_InvalidTarget(
public static SchemaException ReferenceResolverAttribute_EntityResolverNotFound(
Type type,
string referenceResolver) =>
- new SchemaException(SchemaErrorBuilder.New()
- .SetMessage(
- "The specified ReferenceResolver `{0}` does not exist on `{1}`.",
- type.FullName ?? type.Name,
- referenceResolver)
- .Build());
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ "The specified ReferenceResolver `{0}` does not exist on `{1}`.",
+ type.FullName ?? type.Name,
+ referenceResolver)
+ .Build());
///
/// The runtime type is not supported by the scalars ParseValue method.
@@ -103,8 +107,7 @@ public static SchemaException EntityResolver_NoResolverFound() =>
.Build());
///
- /// The key attribute is used on the type level without specifying the the
- /// fieldset.
+ /// The key attribute is used on the type level without specifying the fieldset.
///
public static SchemaException Key_FieldSet_CannotBeEmpty(
Type type) =>
@@ -116,6 +119,23 @@ public static SchemaException Key_FieldSet_CannotBeEmpty(
// .SetCode(ErrorCodes.ApolloFederation.KeyFieldSetNullOrEmpty)
.Build());
+ ///
+ /// The key attribute is used on the type level without specifying the fieldset.
+ ///
+ public static SchemaException Key_FieldSet_MustBeEmpty(
+ MemberInfo member)
+ {
+ var type = member.ReflectedType ?? member.DeclaringType!;
+
+ return new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage("The specified key attribute must not specify a fieldset when annotated to a field.")
+ .SetExtension("type", type.FullName ?? type.Name)
+ .SetExtension("member", member.Name)
+ // .SetCode(ErrorCodes.ApolloFederation.KeyFieldSetNullOrEmpty)
+ .Build());
+ }
+
///
/// The provides attribute is used and the fieldset is set to null or
/// .
@@ -141,4 +161,58 @@ public static SchemaException Requires_FieldSet_CannotBeEmpty(
// .SetCode(ErrorCodes.ApolloFederation.RequiresFieldSetNullOrEmpty)
.SetExtension(nameof(member), member)
.Build());
-}
+
+ ///
+ /// The compose directive attribute is used on the type level without specifying the name.
+ ///
+ public static SchemaException ComposeDirective_Name_CannotBeEmpty(
+ Type type) =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ ThrowHelper_ComposeDirective_Name_CannotBeEmpty,
+ type.FullName ?? type.Name)
+ .Build());
+
+ ///
+ /// The link attribute is used on the schema without specifying the url.
+ ///
+ public static SchemaException Link_Url_CannotBeEmpty(
+ Type type) =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ ThrowHelper_Link_Url_CannotBeEmpty,
+ type.FullName ?? type.Name)
+ .Build());
+
+ ///
+ /// The contact attribute is used on the schema without specifying the name.
+ ///
+ public static SchemaException Contact_Name_CannotBeEmpty(
+ Type type) =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ ThrowHelper_Contact_Name_CannotBeEmpty,
+ type.FullName ?? type.Name)
+ .Build());
+
+ ///
+ /// Specified federation version is not supported.
+ ///
+ public static SchemaException FederationVersion_Unknown(
+ FederationVersion version) =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage(
+ ThrowHelper_FederationVersion_Unknown,
+ version)
+ .Build());
+
+ public static SchemaException Contact_Not_Repeatable() =>
+ new SchemaException(
+ SchemaErrorBuilder.New()
+ .SetMessage("The @contact directive is not repeatable and can.")
+ .Build());
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDefinition.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDefinition.cs
similarity index 90%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDefinition.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDefinition.cs
index 77982e63982..5d0f5a33238 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDefinition.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDefinition.cs
@@ -1,6 +1,6 @@
using HotChocolate.Types.Descriptors.Definitions;
-namespace HotChocolate.ApolloFederation.Descriptors;
+namespace HotChocolate.ApolloFederation.Types;
///
/// The entity definition allows to specify a reference resolver.
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDescriptor.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDescriptor.cs
similarity index 83%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDescriptor.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDescriptor.cs
index 98adae9855e..00d2c9ca79e 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/EntityResolverDescriptor.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/EntityResolverDescriptor.cs
@@ -1,16 +1,16 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
-using HotChocolate.ApolloFederation.Helpers;
using HotChocolate.ApolloFederation.Properties;
+using HotChocolate.ApolloFederation.Resolvers;
using HotChocolate.Internal;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using HotChocolate.Utilities;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
+using static HotChocolate.ApolloFederation.FederationContextData;
-namespace HotChocolate.ApolloFederation.Descriptors;
+namespace HotChocolate.ApolloFederation.Types;
///
/// The entity descriptor allows to specify a reference resolver.
@@ -63,31 +63,11 @@ private void OnCompleteDefinition(ObjectTypeDefinition definition)
}
}
- protected internal override EntityResolverDefinition Definition { get; protected set; } = new();
-
///
public IObjectTypeDescriptor ResolveReference(
FieldResolverDelegate fieldResolver)
=> ResolveReference(fieldResolver, Array.Empty());
- private IObjectTypeDescriptor ResolveReference(
- FieldResolverDelegate fieldResolver,
- IReadOnlyList required)
- {
- if (fieldResolver is null)
- {
- throw new ArgumentNullException(nameof(fieldResolver));
- }
-
- if (required is null)
- {
- throw new ArgumentNullException(nameof(required));
- }
-
- Definition.ResolverDefinition = new(fieldResolver, required);
- return _typeDescriptor;
- }
-
///
public IObjectTypeDescriptor ResolveReferenceWith(
Expression> method)
@@ -97,10 +77,7 @@ public IObjectTypeDescriptor ResolveReferenceWith(
public IObjectTypeDescriptor ResolveReferenceWith(
Expression> method)
{
- if (method is null)
- {
- throw new ArgumentNullException(nameof(method));
- }
+ ArgumentNullException.ThrowIfNull(method);
var member = method.TryExtractMember(true);
@@ -114,20 +91,10 @@ public IObjectTypeDescriptor ResolveReferenceWith(
nameof(member));
}
- ///
- public IObjectTypeDescriptor ResolveReferenceWith()
- => ResolveReferenceWith(
- Context.TypeInspector.GetNodeResolverMethod(
- Definition.EntityType ?? typeof(TResolver),
- typeof(TResolver))!);
-
///
public IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method)
{
- if (method is null)
- {
- throw new ArgumentNullException(nameof(method));
- }
+ ArgumentNullException.ThrowIfNull(method);
var argumentBuilder = new ReferenceResolverArgumentExpressionBuilder();
@@ -141,10 +108,28 @@ public IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method)
return ResolveReference(resolver.Resolver!, argumentBuilder.Required);
}
+ ///
+ public IObjectTypeDescriptor ResolveReferenceWith()
+ => ResolveReferenceWith(typeof(TResolver));
+
///
public IObjectTypeDescriptor ResolveReferenceWith(Type type)
=> ResolveReferenceWith(
Context.TypeInspector.GetNodeResolverMethod(
Definition.EntityType ?? type,
type)!);
+
+ private IObjectTypeDescriptor ResolveReference(
+ FieldResolverDelegate fieldResolver,
+ IReadOnlyList required)
+ {
+ ArgumentNullException.ThrowIfNull(fieldResolver);
+
+ ArgumentNullException.ThrowIfNull(required);
+
+ Definition.ResolverDefinition = new(fieldResolver, required);
+ return _typeDescriptor;
+ }
+
+ protected internal override EntityResolverDefinition Definition { get; protected set; } = new();
}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/IEntityResolverDescriptor.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/IEntityResolverDescriptor.cs
new file mode 100644
index 00000000000..fe91fb190f6
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/IEntityResolverDescriptor.cs
@@ -0,0 +1,50 @@
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// The entity descriptor allows to specify a reference resolver.
+///
+public interface IEntityResolverDescriptor
+{
+ ///
+ /// Resolve an entity from its representation.
+ ///
+ ///
+ /// The reference resolver.
+ ///
+ ///
+ /// Returns the descriptor for configuration chaining.
+ ///
+ IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method);
+}
+
+///
+/// The entity descriptor allows to specify a reference resolver.
+///
+public interface IEntityResolverDescriptor
+{
+ ///
+ /// Resolve an entity from its representation.
+ ///
+ ///
+ /// The reference resolver selector.
+ ///
+ ///
+ /// Returns the descriptor for configuration chaining.
+ ///
+ IObjectTypeDescriptor ResolveReferenceWith(
+ Expression> method);
+
+ ///
+ /// Resolve an entity from its representation.
+ ///
+ ///
+ /// The reference resolver.
+ ///
+ ///
+ /// Returns the descriptor for configuration chaining.
+ ///
+ IObjectTypeDescriptor ResolveReferenceWith(MethodInfo method);
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/ReferenceResolverDefinition.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/ReferenceResolverDefinition.cs
similarity index 89%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/ReferenceResolverDefinition.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/ReferenceResolverDefinition.cs
index 00950a880df..fbd21a77c51 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Descriptors/ReferenceResolverDefinition.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Descriptors/ReferenceResolverDefinition.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using HotChocolate.Resolvers;
-namespace HotChocolate.ApolloFederation.Descriptors;
+namespace HotChocolate.ApolloFederation.Types;
///
/// A reference resolver definition.
@@ -24,7 +24,8 @@ public ReferenceResolverDefinition(
FieldResolverDelegate resolver,
IReadOnlyList? required = default)
{
- Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
+ ArgumentNullException.ThrowIfNull(resolver);
+ Resolver = resolver;
Required = required ?? Array.Empty();
}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedAttribute.cs
new file mode 100644
index 00000000000..9df105553d9
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedAttribute.cs
@@ -0,0 +1,72 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @authenticated on
+/// ENUM
+/// | FIELD_DEFINITION
+/// | INTERFACE
+/// | OBJECT
+/// | SCALAR
+///
+///
+/// The @authenticated directive is used to indicate that the target element is accessible only to the
+/// authenticated supergraph users. For more granular access control, see the
+/// RequiresScopeDirectiveType directive usage.
+/// Refer to the
+/// Apollo Router article for additional details.
+///
+///
+/// type Foo @key(fields: "id") {
+/// id: ID
+/// description: String @authenticated
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class |
+ AttributeTargets.Enum |
+ AttributeTargets.Interface |
+ AttributeTargets.Method |
+ AttributeTargets.Property |
+ AttributeTargets.Struct)]
+public sealed class AuthenticatedAttribute : DescriptorAttribute
+{
+ protected internal override void TryConfigure(
+ IDescriptorContext context,
+ IDescriptor descriptor,
+ ICustomAttributeProvider element)
+ {
+ switch (descriptor)
+ {
+ case IEnumTypeDescriptor enumTypeDescriptor:
+ {
+ enumTypeDescriptor.Authenticated();
+ break;
+ }
+ case IObjectTypeDescriptor objectFieldDescriptor:
+ {
+ objectFieldDescriptor.Authenticated();
+ break;
+ }
+ case IObjectFieldDescriptor objectFieldDescriptor:
+ {
+ objectFieldDescriptor.Authenticated();
+ break;
+ }
+ case IInterfaceTypeDescriptor interfaceTypeDescriptor:
+ {
+ interfaceTypeDescriptor.Authenticated();
+ break;
+ }
+ case IInterfaceFieldDescriptor interfaceFieldDescriptor:
+ {
+ interfaceFieldDescriptor.Authenticated();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDescriptionExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDescriptionExtensions.cs
new file mode 100644
index 00000000000..cdf22caef1b
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDescriptionExtensions.cs
@@ -0,0 +1,59 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// Provides extensions for applying @authenticated directive on type system descriptors.
+///
+public static class AuthenticatedDescriptionExtensions
+{
+ ///
+ /// Applies @authenticated directive to indicate that the target element is accessible only to the authenticated supergraph users.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID
+ /// description: String @authenticated
+ /// }
+ ///
+ ///
+ ///
+ /// The type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the type descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IEnumTypeDescriptor Authenticated(this IEnumTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(AuthenticatedDirective.Default);
+ }
+
+ ///
+ public static IInterfaceFieldDescriptor Authenticated(this IInterfaceFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(AuthenticatedDirective.Default);
+ }
+
+ ///
+ public static IInterfaceTypeDescriptor Authenticated(this IInterfaceTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(AuthenticatedDirective.Default);
+ }
+
+ ///
+ public static IObjectFieldDescriptor Authenticated(this IObjectFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(AuthenticatedDirective.Default);
+ }
+
+ ///
+ public static IObjectTypeDescriptor Authenticated(this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(AuthenticatedDirective.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs
new file mode 100644
index 00000000000..d5ee8b5ee29
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs
@@ -0,0 +1,40 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @authenticated on
+/// ENUM
+/// | FIELD_DEFINITION
+/// | INTERFACE
+/// | OBJECT
+/// | SCALAR
+///
+///
+/// The @authenticated directive is used to indicate that the target element is accessible only to the authenticated supergraph users.
+/// For more granular access control, see the RequiresScopeDirectiveType directive usage.
+/// Refer to the Apollo Router article
+/// for additional details.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID
+/// description: String @authenticated
+/// }
+///
+///
+[Package(Federation24)]
+[DirectiveType(
+ AuthenticatedDirective_Name,
+ DirectiveLocation.Enum |
+ DirectiveLocation.FieldDefinition |
+ DirectiveLocation.Interface |
+ DirectiveLocation.Object |
+ DirectiveLocation.Scalar)]
+[GraphQLDescription(AuthenticatedDirective_Description)]
+public sealed class AuthenticatedDirective
+{
+ public static AuthenticatedDirective Default { get; } = new();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs
new file mode 100644
index 00000000000..e76aa35145a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs
@@ -0,0 +1,33 @@
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @composeDirective(name: String!) repeatable on SCHEMA
+///
+///
+/// By default, Supergraph schema excludes all custom directives. The @composeDirective is used to specify
+/// custom directives that should be exposed in the Supergraph schema.
+///
+///
+/// extend schema @composeDirective(name: "@custom")
+/// @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"])
+/// @link(url: "https://myspecs.dev/custom/v1.0", import: ["@custom"])
+///
+/// directive @custom on FIELD_DEFINITION
+///
+/// type Query {
+/// helloWorld: String! @custom
+/// }
+///
+///
+[Package(Federation25)]
+[DirectiveType(ComposeDirective_Name, DirectiveLocation.Schema, IsRepeatable = true)]
+[GraphQLDescription(ComposeDirective_Description)]
+public sealed class ComposeDirective(string name)
+{
+ public string Name { get; } = name;
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirectiveDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirectiveDescriptorExtensions.cs
new file mode 100644
index 00000000000..a0767f1ec57
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirectiveDescriptorExtensions.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using HotChocolate.Execution.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using static HotChocolate.ApolloFederation.FederationContextData;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ComposeDirectiveDescriptorExtensions
+{
+ ///
+ /// Applies @composeDirective which is used to specify custom directives that should be exposed in the
+ /// Supergraph schema. If not specified, by default, Supergraph schema excludes all custom directives.
+ ///
+ /// extend schema @composeDirective(name: "@custom")
+ /// @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"])
+ /// @link(url: "https://myspecs.dev/custom/v1.0", import: ["@custom"])
+ ///
+ /// directive @custom on FIELD_DEFINITION
+ ///
+ /// type Query {
+ /// helloWorld: String! @custom
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// Name of the directive that should be preserved in the supergraph composition.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder ExportDirective(
+ this IRequestExecutorBuilder builder,
+ string directiveName)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(directiveName);
+
+ builder.ConfigureSchema(
+ sb =>
+ {
+ sb.AddSchemaConfiguration(
+ d => d.Directive(new ComposeDirective(directiveName)));
+ });
+
+ return builder;
+ }
+
+ public static IRequestExecutorBuilder ExportDirective(
+ this IRequestExecutorBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.AddType();
+ builder.AddType();
+
+ builder.ConfigureSchema(
+ sb =>
+ {
+ if(!sb.ContextData.TryGetValue(ExportedDirectives, out var directiveTypes))
+ {
+ directiveTypes = new List();
+ sb.ContextData[ExportedDirectives] = directiveTypes;
+ }
+
+ ((List)directiveTypes!).Add(typeof(T));
+ });
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDescriptorExtensions.cs
new file mode 100644
index 00000000000..555b5894e25
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDescriptorExtensions.cs
@@ -0,0 +1,183 @@
+using System.Collections.Generic;
+using HotChocolate.Execution.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using static HotChocolate.ApolloFederation.FederationContextData;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ContactDescriptorExtensions
+{
+ ///
+ /// Applies @contact directive which can be used to prpvode team contact information to your subgraph schema.
+ /// This information is automatically parsed and displayed by Apollo Studio. See
+ ///
+ /// Subgraph Contact Information
+ /// for additional details.
+ ///
+ ///
+ /// 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") {
+ /// query: Query
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// Contact title of the subgraph owner
+ ///
+ ///
+ /// URL where the subgraph's owner can be reached
+ ///
+ ///
+ /// Other relevant contact notes; supports markdown links
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder AddContact(
+ this IRequestExecutorBuilder builder,
+ string name,
+ string? url = null,
+ string? description = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ builder.ConfigureSchema(
+ sb =>
+ {
+ if (!sb.ContextData.TryAdd(ContactMarker, 1))
+ {
+ throw ThrowHelper.Contact_Not_Repeatable();
+ }
+
+ sb.AddSchemaConfiguration(d => d.Directive(new ContactDirective(name, url, description)));
+ });
+
+ return builder;
+ }
+
+ ///
+ /// Applies @contact directive which can be used to prpvode team contact information to your subgraph schema.
+ /// This information is automatically parsed and displayed by Apollo Studio. See
+ ///
+ /// Subgraph Contact Information
+ /// for additional details.
+ ///
+ ///
+ /// 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") {
+ /// query: Query
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// The contact.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder AddContact(
+ this IRequestExecutorBuilder builder,
+ ContactDirective contact)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(contact);
+
+ builder.ConfigureSchema(
+ sb =>
+ {
+ if (!sb.ContextData.TryAdd(ContactMarker, 1))
+ {
+ throw ThrowHelper.Contact_Not_Repeatable();
+ }
+
+ sb.AddSchemaConfiguration(d => d.Directive(contact));
+ });
+
+ return builder;
+ }
+
+ ///
+ /// Applies @contact directive which can be used to prpvode team contact information to your subgraph schema.
+ /// This information is automatically parsed and displayed by Apollo Studio. See
+ ///
+ /// Subgraph Contact Information
+ /// for additional details.
+ ///
+ ///
+ /// 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") {
+ /// query: Query
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// A delegate to resolve the contact details from the DI or from the configuration.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder AddContact(
+ this IRequestExecutorBuilder builder,
+ Func contactResolver)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(contactResolver);
+
+ builder.ConfigureSchema(
+ (sp, sb) =>
+ {
+ var contact = contactResolver(sp.GetApplicationServices());
+
+ if (contact is null)
+ {
+ return;
+ }
+
+ if (!sb.ContextData.TryAdd(ContactMarker, 1))
+ {
+ throw ThrowHelper.Contact_Not_Repeatable();
+ }
+
+ sb.AddSchemaConfiguration(d => d.Directive(contact));
+ });
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDirective.cs
new file mode 100644
index 00000000000..d7e63791fd8
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ContactDirective.cs
@@ -0,0 +1,74 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @contact(
+/// "Contact title of the subgraph owner"
+/// name: String!
+/// "URL where the subgraph's owner can be reached"
+/// url: String
+/// "Other relevant notes can be included here; supports markdown links"
+/// description: String
+/// ) on SCHEMA
+///
+///
+/// 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.
+/// See
+/// Subgraph Contact Information for additional details.
+///
+///
+///
+///
+/// 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") {
+/// query: Query
+/// }
+///
+///
+///
+[DirectiveType(ContactDirective_Name, DirectiveLocation.Schema)]
+[GraphQLDescription(ContactDirective_Description)]
+public sealed class ContactDirective
+{
+ ///
+ /// Initializes new instance of
+ ///
+ ///
+ /// Contact title of the subgraph owner
+ ///
+ ///
+ /// URL where the subgraph's owner can be reached
+ ///
+ ///
+ /// Other relevant notes can be included here; supports markdown links
+ ///
+ public ContactDirective(string name, string? url, string? description)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ Name = name;
+ Url = url;
+ Description = description;
+ }
+
+ ///
+ /// Gets the contact title of the subgraph owner.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the url where the subgraph's owner can be reached.
+ ///
+ public string? Url { get; }
+
+ ///
+ /// Gets other relevant notes about subgraph contact information. Can include markdown links.
+ ///
+ public string? Description { get; }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeAttribute.cs
new file mode 100644
index 00000000000..adf370e27c5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeAttribute.cs
@@ -0,0 +1,31 @@
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @extends on OBJECT | INTERFACE
+///
+///
+/// The @extends directive is used to represent type extensions in the schema. Federated extended types should have
+/// corresponding @key directive defined that specifies primary key required to fetch the underlying object.
+///
+/// # extended from the Users service
+/// type Foo @extends @key(fields: "id") {
+/// id: ID
+/// description: String
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Interface)]
+public sealed class ExtendServiceTypeAttribute : ObjectTypeDescriptorAttribute
+{
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IObjectTypeDescriptor descriptor,
+ Type type)
+ => descriptor.ExtendServiceType();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDescriptorExtensions.cs
new file mode 100644
index 00000000000..5c367a91ab5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDescriptorExtensions.cs
@@ -0,0 +1,76 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ExtendServiceTypeDescriptorExtensions
+{
+ ///
+ /// Applies @extends directive which is used to represent type extensions in the schema. Federated extended types should have
+ /// corresponding @key directive defined that specifies primary key required to fetch the underlying object.
+ ///
+ /// NOTE: Federation v2 no longer requires `@extends` directive due to the smart entity type merging. All usage of @extends
+ /// directive should be removed from your Federation v2 schemas.
+ ///
+ /// # extended from the Users service
+ /// type Foo @extends @key(fields: "id") {
+ /// id: ID
+ /// description: String
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ public static IObjectTypeDescriptor ExtendServiceType(
+ this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ if (descriptor.Extend().Context.GetFederationVersion() == FederationVersion.Federation10)
+ {
+ descriptor.Directive(ExtendServiceTypeDirective.Default);
+ }
+
+ return descriptor;
+ }
+
+ ///
+ /// Applies @extends directive which is used to represent type extensions in the schema. Federated extended types should have
+ /// corresponding @key directive defined that specifies primary key required to fetch the underlying object.
+ ///
+ /// NOTE: Federation v2 no longer requires `@extends` directive due to the smart entity type merging. All usage of @extends
+ /// directive should be removed from your Federation v2 schemas.
+ ///
+ /// # extended from the Users service
+ /// type Foo @extends @key(fields: "id") {
+ /// id: ID
+ /// description: String
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ public static IObjectTypeDescriptor ExtendServiceType(
+ this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ if (descriptor.Extend().Context.GetFederationVersion() == FederationVersion.Federation10)
+ {
+ descriptor.Directive(ExtendServiceTypeDirective.Default);
+ }
+
+ return descriptor;
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDirective.cs
new file mode 100644
index 00000000000..cfdf38cdee8
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExtendServiceTypeDirective.cs
@@ -0,0 +1,31 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @extends on OBJECT | INTERFACE
+///
+///
+/// The @extends directive is used to represent type extensions in the schema. Federated extended types should have
+/// corresponding @key directive defined that specifies primary key required to fetch the underlying object.
+///
+/// # extended from the Users service
+/// type Foo @extends @key(fields: "id") {
+/// id: ID
+/// description: String
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(
+ ExtendsDirective_Name,
+ DirectiveLocation.Object |
+ DirectiveLocation.Interface)]
+[GraphQLDescription(ExtendsDirective_Description)]
+public sealed class ExtendServiceTypeDirective
+{
+ public static ExtendServiceTypeDirective Default { get; } = new();
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalAttribute.cs
new file mode 100644
index 00000000000..f4fa6037d27
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalAttribute.cs
@@ -0,0 +1,39 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// # federation v1 definition
+/// directive @external on FIELD_DEFINITION
+///
+/// # federation v2 definition
+/// directive @external on OBJECT | FIELD_DEFINITION
+///
+///
+/// The @external directive is used to mark a field as owned by another service.
+/// This allows service A to use fields from service B while also knowing at runtime
+/// the types of that field. All the external fields should either be referenced from the @key,
+/// @requires or @provides directives field sets.
+///
+/// Due to the smart merging of entity types, Federation v2 no longer requires @external directive
+/// on @key fields and can be safely omitted from the schema. @external directive is only required
+/// on fields referenced by the @requires and @provides directive.
+///
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// remoteField: String @external
+/// localField: String @requires(fields: "remoteField")
+/// }
+///
+///
+public sealed class ExternalAttribute : ObjectFieldDescriptorAttribute
+{
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IObjectFieldDescriptor descriptor,
+ MemberInfo member)
+ => descriptor.External();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDescriptorExtensions.cs
new file mode 100644
index 00000000000..26cbf1749f5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDescriptorExtensions.cs
@@ -0,0 +1,39 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ExternalDescriptorExtensions
+{
+ ///
+ /// Applies the @external directive which is used to mark a field as owned by another service.
+ /// This allows service A to use fields from service B while also knowing at runtime
+ /// the types of that field. All the external fields should either be referenced from the @key,
+ /// @requires or @provides directives field sets.
+ ///
+ /// Due to the smart merging of entity types, Federation v2 no longer requires @external directive
+ /// on @key fields and can be safely omitted from the schema. @external directive is only required
+ /// on fields referenced by the @requires and @provides directive.
+ ///
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// remoteField: String @external
+ /// localField: String @requires(fields: "remoteField")
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IObjectFieldDescriptor External(
+ this IObjectFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(ExternalDirective.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs
new file mode 100644
index 00000000000..3118084014e
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs
@@ -0,0 +1,35 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @external on FIELD_DEFINITION
+///
+///
+/// The @external directive is used to mark a field as owned by another service.
+/// This allows service A to use fields from service B while also knowing at runtime
+/// the types of that field. All the external fields should either be referenced from the @key,
+/// @requires or @provides directives field sets.
+///
+/// Due to the smart merging of entity types, Federation v2 no longer requires @external directive
+/// on @key fields and can be safely omitted from the schema. @external directive is only required
+/// on fields referenced by the @requires and @provides directive.
+///
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// remoteField: String @external
+/// localField: String @requires(fields: "remoteField")
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(ExternalDirective_Name, DirectiveLocation.FieldDefinition)]
+[GraphQLDescription(ExternalDirective_Description)]
+public sealed class ExternalDirective
+{
+ public static ExternalDirective Default { get; } = new();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleAttribute.cs
new file mode 100644
index 00000000000..9d05e1d7b70
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleAttribute.cs
@@ -0,0 +1,84 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @inaccessible on FIELD_DEFINITION
+/// | OBJECT
+/// | INTERFACE
+/// | UNION
+/// | ENUM
+/// | ENUM_VALUE
+/// | SCALAR
+/// | INPUT_OBJECT
+/// | INPUT_FIELD_DEFINITION
+/// | ARGUMENT_DEFINITION
+///
+///
+/// The @inaccessible directive is used to mark location within schema as inaccessible
+/// from the GraphQL Router. Applying @inaccessible directive on a type is equivalent of applying
+/// it on all type fields.
+///
+/// While @inaccessible fields are not exposed by the router to the clients, they are still available
+/// for query plans and can be referenced from @key and @requires directives. This allows you to not
+/// expose sensitive fields to your clients but still make them available for computations.
+/// Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple
+/// subgraphs without breaking composition.
+///
+///
+/// type Foo @inaccessible {
+/// hiddenId: ID!
+/// hiddenField: String
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Enum
+ | AttributeTargets.Field
+ | AttributeTargets.Interface
+ | AttributeTargets.Method
+ | AttributeTargets.Parameter
+ | AttributeTargets.Property
+ | AttributeTargets.Struct)]
+public sealed class InaccessibleAttribute : DescriptorAttribute
+{
+ protected internal override void TryConfigure(
+ IDescriptorContext context,
+ IDescriptor descriptor,
+ ICustomAttributeProvider element)
+ {
+ switch (descriptor)
+ {
+ case IEnumTypeDescriptor enumTypeDescriptor:
+ enumTypeDescriptor.Inaccessible();
+ break;
+ case IObjectTypeDescriptor objectFieldDescriptor:
+ objectFieldDescriptor.Inaccessible();
+ break;
+ case IObjectFieldDescriptor objectFieldDescriptor:
+ objectFieldDescriptor.Inaccessible();
+ break;
+ case IInterfaceTypeDescriptor interfaceTypeDescriptor:
+ interfaceTypeDescriptor.Inaccessible();
+ break;
+ case IInterfaceFieldDescriptor interfaceFieldDescriptor:
+ interfaceFieldDescriptor.Inaccessible();
+ break;
+ case IInputObjectTypeDescriptor inputObjectTypeDescriptor:
+ inputObjectTypeDescriptor.Inaccessible();
+ break;
+ case IInputFieldDescriptor inputFieldDescriptor:
+ inputFieldDescriptor.Inaccessible();
+ break;
+ case IUnionTypeDescriptor unionTypeDescriptor:
+ unionTypeDescriptor.Inaccessible();
+ break;
+ case IEnumValueDescriptor enumValueDescriptor:
+ enumValueDescriptor.Inaccessible();
+ break;
+ }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDescriptorExtensions.cs
new file mode 100644
index 00000000000..d4ff26078f4
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDescriptorExtensions.cs
@@ -0,0 +1,98 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class InaccessibleDescriptorExtensions
+{
+ ///
+ /// Applies the @inaccessible directive which is used to mark location within schema as inaccessible
+ /// from the GraphQL Router. While @inaccessible fields are not exposed by the router to the clients,
+ /// they are still available for query plans and can be referenced from @key and @requires directives.
+ /// This allows you to not expose sensitive fields to your clients but still make them available for
+ /// computations. Inaccessible can also be used to incrementally add schema elements (e.g. fields) to
+ /// multiple subgraphs without breaking composition.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// hidden: String @inaccessible
+ /// }
+ ///
+ ///
+ ///
+ /// The type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the type descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IEnumTypeDescriptor Inaccessible(
+ this IEnumTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IEnumValueDescriptor Inaccessible(
+ this IEnumValueDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IInterfaceFieldDescriptor Inaccessible(
+ this IInterfaceFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IInterfaceTypeDescriptor Inaccessible(
+ this IInterfaceTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IInputObjectTypeDescriptor Inaccessible(
+ this IInputObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IInputFieldDescriptor Inaccessible(
+ this IInputFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IObjectFieldDescriptor Inaccessible(
+ this IObjectFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IObjectTypeDescriptor Inaccessible(
+ this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+
+ ///
+ public static IUnionTypeDescriptor Inaccessible(
+ this IUnionTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ return descriptor.Directive(InaccessibleDirective.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDirective.cs
new file mode 100644
index 00000000000..02070ade87c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InaccessibleDirective.cs
@@ -0,0 +1,54 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @inaccessible on FIELD_DEFINITION
+/// | OBJECT
+/// | INTERFACE
+/// | UNION
+/// | ENUM
+/// | ENUM_VALUE
+/// | SCALAR
+/// | INPUT_OBJECT
+/// | INPUT_FIELD_DEFINITION
+/// | ARGUMENT_DEFINITION
+///
+///
+/// The @inaccessible directive is used to mark location within schema as inaccessible
+/// from the GraphQL Router. Applying @inaccessible directive on a type is equivalent of applying
+/// it on all type fields.
+///
+/// While @inaccessible fields are not exposed by the router to the clients, they are still available
+/// for query plans and can be referenced from @key and @requires directives. This allows you to not
+/// expose sensitive fields to your clients but still make them available for computations.
+/// Inaccessible can also be used to incrementally add schema elements (e.g. fields) to multiple
+/// subgraphs without breaking composition.
+///
+/// type Foo @inaccessible {
+/// hiddenId: ID!
+/// hiddenField: String
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(
+ InaccessibleDirective_Name,
+ DirectiveLocation.FieldDefinition |
+ DirectiveLocation.Object |
+ DirectiveLocation.Interface |
+ DirectiveLocation.Union |
+ DirectiveLocation.Enum |
+ DirectiveLocation.EnumValue |
+ DirectiveLocation.Scalar |
+ DirectiveLocation.InputObject |
+ DirectiveLocation.InputFieldDefinition |
+ DirectiveLocation.ArgumentDefinition)]
+[GraphQLDescription(InaccessibleDirective_Description)]
+public sealed class InaccessibleDirective
+{
+ public static InaccessibleDirective Default { get; } = new();
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectAttribute.cs
new file mode 100644
index 00000000000..832f76c49d5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectAttribute.cs
@@ -0,0 +1,29 @@
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @interfaceObject on OBJECT
+///
+///
+/// The @interfaceObject directive provides meta information to the router that this entity
+/// type defined within this subgraph is an interface in the supergraph.
+/// This allows you to extend functionality of an interface across the supergraph
+/// without having to implement (or even be aware of) all its implementing types.
+///
+///
+/// type Foo @interfaceObject @key(fields: "ids") {
+/// id: ID!
+/// newCommonField: String
+/// }
+///
+///
+public sealed class InterfaceObjectAttribute : ObjectTypeDescriptorAttribute
+{
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IObjectTypeDescriptor descriptor,
+ Type type)
+ => descriptor.InterfaceObject();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDescriptorExtensions.cs
new file mode 100644
index 00000000000..188598ec6cb
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDescriptorExtensions.cs
@@ -0,0 +1,58 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class InterfaceObjectDescriptorExtensions
+{
+ ///
+ /// Applies the @interfaceObject directive which provides meta information to the router that this entity
+ /// type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality
+ /// of an interface across the supergraph without having to implement (or even be aware of) all its implementing types.
+ ///
+ /// type Foo @interfaceObject @key(fields: "ids") {
+ /// id: ID!
+ /// newCommonField: String
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IObjectTypeDescriptor InterfaceObject(this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(InterfaceObjectDirective.Default);
+ }
+
+ ///
+ /// Applies the @interfaceObject directive which provides meta information to the router that this entity
+ /// type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality
+ /// of an interface across the supergraph without having to implement (or even be aware of) all its implementing types.
+ ///
+ /// type Foo @interfaceObject @key(fields: "ids") {
+ /// id: ID!
+ /// newCommonField: String
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IObjectTypeDescriptor InterfaceObject(this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(InterfaceObjectDirective.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs
new file mode 100644
index 00000000000..0d76dfc1daa
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs
@@ -0,0 +1,28 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @interfaceObject on OBJECT
+///
+///
+/// The @interfaceObject directive provides meta information to the router that this entity
+/// type defined within this subgraph is an interface in the supergraph. This allows you to extend functionality
+/// of an interface across the supergraph without having to implement (or even be aware of) all its implementing types.
+///
+/// type Foo @interfaceObject @key(fields: "ids") {
+/// id: ID!
+/// newCommonField: String
+/// }
+///
+///
+[Package(Federation22)]
+[DirectiveType(InterfaceObject_Name, DirectiveLocation.Object)]
+[GraphQLDescription(InterfaceObjectDirective_Description)]
+public sealed class InterfaceObjectDirective
+{
+ public static InterfaceObjectDirective Default { get; } = new();
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyAttribute.cs
new file mode 100644
index 00000000000..5b64eeacc86
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyAttribute.cs
@@ -0,0 +1,138 @@
+using System.Collections.Generic;
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+using static System.Reflection.MemberTypes;
+using static HotChocolate.ApolloFederation.FederationContextData;
+using static HotChocolate.ApolloFederation.ThrowHelper;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// # federation v1 definition
+/// directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+///
+/// # federation v2 definition
+/// directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+///
+///
+/// The @key directive is used to indicate a combination of fields that can be used to uniquely
+/// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"),
+/// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can
+/// be specified on a target type.
+///
+/// Keys can also be marked as non-resolvable which indicates to router that given entity should never be
+/// resolved within given subgraph. This allows your subgraph to still reference target entity without
+/// contributing any fields to it.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// field: String
+/// bars: [Bar!]!
+/// }
+///
+/// type Bar @key(fields: "id", resolvable: false) {
+/// id: ID!
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class |
+ AttributeTargets.Property,
+ AllowMultiple = true)]
+public sealed class KeyAttribute : DescriptorAttribute
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public KeyAttribute()
+ {
+ Resolvable = true;
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ /// The field set that describes the key.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Indicates whether the key is resolvable.
+ ///
+ public KeyAttribute(string fieldSet, bool resolvable = true)
+ {
+ FieldSet = fieldSet;
+ Resolvable = resolvable;
+ }
+
+ ///
+ /// Gets the field set that describes the key.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ public string? FieldSet { get; }
+
+ ///
+ /// Gets a value that indicates whether the key is resolvable.
+ ///
+ public bool Resolvable { get; }
+
+ protected internal override void TryConfigure(
+ IDescriptorContext context,
+ IDescriptor descriptor,
+ ICustomAttributeProvider element)
+ {
+ switch (element)
+ {
+ case Type type:
+ ConfigureType(type, descriptor);
+ break;
+
+ case PropertyInfo member:
+ ConfigureField(member, descriptor);
+ break;
+
+ case MethodInfo member:
+ ConfigureField(member, descriptor);
+ break;
+ }
+ }
+
+ private void ConfigureType(Type type, IDescriptor descriptor)
+ {
+ if (string.IsNullOrEmpty(FieldSet))
+ {
+ throw Key_FieldSet_CannotBeEmpty(type);
+ }
+
+ switch (descriptor)
+ {
+ case IObjectTypeDescriptor typeDesc:
+ typeDesc.Key(FieldSet, Resolvable);
+ break;
+
+ case IInterfaceTypeDescriptor interfaceDesc:
+ interfaceDesc.Key(FieldSet, Resolvable);
+ break;
+ }
+ }
+
+ private void ConfigureField(MemberInfo member, IDescriptor descriptor)
+ {
+ if (!string.IsNullOrEmpty(FieldSet))
+ {
+ throw Key_FieldSet_MustBeEmpty(member);
+ }
+
+ switch (descriptor)
+ {
+ case IObjectFieldDescriptor fieldDesc:
+ fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, true);
+ break;
+
+ case IInterfaceFieldDescriptor fieldDesc:
+ fieldDesc.Extend().Definition.ContextData.TryAdd(KeyMarker, true);
+ break;
+ }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs
new file mode 100644
index 00000000000..a6d1c6e68b4
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDescriptorExtensions.cs
@@ -0,0 +1,145 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class KeyDescriptorExtensions
+{
+ ///
+ /// Applies the @key directive which is used to indicate a combination of fields that can be used to uniquely
+ /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"),
+ /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can
+ /// be specified on a target type.
+ ///
+ /// Keys can be marked as non-resolvable which indicates to router that given entity should never be
+ /// resolved within given subgraph. This allows your subgraph to still reference target entity without
+ /// contributing any fields to it.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// field: String
+ /// bars: [Bar!]!
+ /// }
+ ///
+ /// type Bar @key(fields: "id", resolvable: false) {
+ /// id: ID!
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// The field set that describes the key.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Boolean flag to indicate whether this entity is resolvable locally.
+ ///
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IEntityResolverDescriptor Key(
+ this IObjectTypeDescriptor descriptor,
+ string fieldSet,
+ bool resolvable = true)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(fieldSet);
+
+ descriptor.Directive(new KeyDirective(fieldSet, resolvable));
+ return new EntityResolverDescriptor(descriptor);
+ }
+
+ ///
+ /// Adds the @key directive which is used to indicate a combination of fields that can be used to uniquely
+ /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"),
+ /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can
+ /// be specified on a target type.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// field: String
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// The field set that describes the key.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Boolean flag to indicate whether this entity is resolvable locally.
+ ///
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IEntityResolverDescriptor Key(
+ this IObjectTypeDescriptor descriptor,
+ string fieldSet,
+ bool resolvable = true)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(fieldSet);
+
+ descriptor.Directive(new KeyDirective(fieldSet, resolvable));
+
+ return new EntityResolverDescriptor(descriptor);
+ }
+
+ ///
+ /// Applies the @key directive which is used to indicate a combination of fields that can be used to uniquely
+ /// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"),
+ /// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can
+ /// be specified on a target type.
+ ///
+ /// Keys can be marked as non-resolvable which indicates to router that given entity should never be
+ /// resolved within given subgraph. This allows your subgraph to still reference target entity without
+ /// contributing any fields to it.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// field: String
+ /// bars: [Bar!]!
+ /// }
+ ///
+ /// type Bar @key(fields: "id", resolvable: true) {
+ /// id: ID!
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// The field set that describes the key.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Boolean flag to indicate whether this entity is resolvable locally.
+ ///
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IInterfaceTypeDescriptor Key(
+ this IInterfaceTypeDescriptor descriptor,
+ string fieldSet,
+ bool resolvable = true)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(fieldSet);
+
+ return descriptor.Directive(new KeyDirective(fieldSet, resolvable));
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDirective.cs
new file mode 100644
index 00000000000..076a7fbce9a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyDirective.cs
@@ -0,0 +1,55 @@
+using HotChocolate.Language;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+using DirectiveLocation = HotChocolate.Types.DirectiveLocation;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+///
+///
+/// The @key directive is used to indicate a combination of fields that can be used to uniquely
+/// identify and fetch an object or interface. The specified field set can represent single field (e.g. "id"),
+/// multiple fields (e.g. "id name") or nested selection sets (e.g. "id user { name }"). Multiple keys can
+/// be specified on a target type.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// field: String
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(
+ KeyDirective_Name,
+ DirectiveLocation.Object |
+ DirectiveLocation.Interface,
+ IsRepeatable = true)]
+[GraphQLDescription(KeyDirective_Description)]
+[KeyLegacySupport]
+public sealed class KeyDirective
+{
+ public KeyDirective(string fields, bool resolvable = true)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(fields);
+ Fields = FieldSetType.ParseSelectionSet(fields);
+ Resolvable = resolvable;
+ }
+
+ public KeyDirective(SelectionSetNode fields, bool resolvable = true)
+ {
+ ArgumentNullException.ThrowIfNull(fields);
+ Fields = fields;
+ Resolvable = resolvable;
+ }
+
+ [FieldSet]
+ public SelectionSetNode Fields { get; }
+
+ [GraphQLType]
+ [DefaultValue(true)]
+ public bool Resolvable { get; }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs
new file mode 100644
index 00000000000..8aa10f9d292
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs
@@ -0,0 +1,19 @@
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+internal sealed class KeyLegacySupportAttribute : DirectiveTypeDescriptorAttribute
+{
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IDirectiveTypeDescriptor descriptor,
+ Type type)
+ {
+ if (descriptor.GetFederationVersion() == FederationVersion.Federation10)
+ {
+ var desc = (IDirectiveTypeDescriptor)descriptor;
+ desc.BindArgumentsExplicitly();
+ desc.Argument(t => t.Fields);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDescriptorExtensions.cs
new file mode 100644
index 00000000000..abf47d2fc09
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDescriptorExtensions.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using System.Linq;
+using HotChocolate.Execution.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class LinkDescriptorExtensions
+{
+ ///
+ /// Applies @link directive definitions to link the document to external schemas.
+ /// External schemas are identified by their url, which optionally ends with a name and version with
+ /// the following format: `{NAME}/v{MAJOR}.{MINOR}`
+ ///
+ /// By default, external types should be namespaced (prefixed with namespace__, e.g. key directive
+ /// should be namespaced as federation__key) unless they are explicitly imported. We automatically
+ /// import ALL federation directives to avoid the need for namespacing.
+ ///
+ /// NOTE: We currently DO NOT support full @link directive capability as it requires support for
+ /// namespacing and renaming imports. This functionality may be added in the future releases.
+ /// See @link specification for details.
+ ///
+ ///
+ /// extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"])
+ ///
+ /// type Query {
+ /// foo: Foo!
+ /// }
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// name: String
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// Url of specification to be imported
+ ///
+ ///
+ /// Optional list of imported elements.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder AddLink(
+ this IRequestExecutorBuilder builder,
+ string url,
+ IReadOnlyList? imports)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(url);
+ return AddLink(builder, new Uri(url), imports);
+ }
+
+ ///
+ /// Applies @link directive definitions to link the document to external schemas.
+ /// External schemas are identified by their url, which optionally ends with a name and version with
+ /// the following format: `{NAME}/v{MAJOR}.{MINOR}`
+ ///
+ /// By default, external types should be namespaced (prefixed with namespace__, e.g. key directive
+ /// should be namespaced as federation__key) unless they are explicitly imported. We automatically
+ /// import ALL federation directives to avoid the need for namespacing.
+ ///
+ /// NOTE: We currently DO NOT support full @link directive capability as it requires support for
+ /// namespacing and renaming imports. This functionality may be added in the future releases.
+ /// See @link specification for details.
+ ///
+ ///
+ /// extend schema @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"])
+ ///
+ /// type Query {
+ /// foo: Foo!
+ /// }
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// name: String
+ /// }
+ ///
+ ///
+ ///
+ /// The GraphQL request executor builder.
+ ///
+ ///
+ /// Url of specification to be imported
+ ///
+ ///
+ /// Optional list of imported elements.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null .
+ ///
+ public static IRequestExecutorBuilder AddLink(
+ this IRequestExecutorBuilder builder,
+ Uri url,
+ IReadOnlyList? imports)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(url);
+
+ builder.ConfigureSchema(
+ sb =>
+ {
+ sb.AddSchemaConfiguration(d => d.Directive(new LinkDirective(url, imports?.ToHashSet())));
+ });
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDirective.cs
new file mode 100644
index 00000000000..88df3bd476a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDirective.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// Object representation of @link directive.
+///
+[DirectiveType(LinkDirective_Name, DirectiveLocation.Schema, IsRepeatable = true)]
+public sealed class LinkDirective
+{
+ ///
+ /// Initializes new instance of
+ ///
+ ///
+ /// Url of specification to be imported
+ ///
+ ///
+ /// Optional list of imported elements.
+ ///
+ public LinkDirective(Uri url, IReadOnlySet? import)
+ {
+ Url = url;
+ Import = import;
+ }
+
+ ///
+ /// Gets imported specification url.
+ ///
+ [GraphQLType>]
+ public Uri Url { get; }
+
+ ///
+ /// Gets optional list of imported element names.
+ ///
+ public IReadOnlySet? Import { get; }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideAttribute.cs
new file mode 100644
index 00000000000..020f4243acc
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideAttribute.cs
@@ -0,0 +1,49 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @override(from: String!) on FIELD_DEFINITION
+///
+///
+/// The @override directive is used to indicate that the current subgraph is taking
+/// responsibility for resolving the marked field away from the subgraph specified in the from
+/// argument. Name of the subgraph to be overridden has to match the name of the subgraph that
+/// was used to publish their schema.
+///
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// description: String @override(from: "BarSubgraph")
+/// }
+///
+///
+public sealed class OverrideAttribute : ObjectFieldDescriptorAttribute
+{
+
+ ///
+ /// Initializes new instance of
+ ///
+ ///
+ /// Name of the subgraph to be overridden
+ ///
+ public OverrideAttribute(string from)
+ {
+ From = from;
+ }
+
+ ///
+ /// Get name of the subgraph to be overridden.
+ ///
+ public string From { get; }
+
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IObjectFieldDescriptor descriptor,
+ MemberInfo member)
+ {
+ descriptor.Override(From);
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs
new file mode 100644
index 00000000000..f50c150e48a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs
@@ -0,0 +1,41 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class OverrideDescriptorExtensions
+{
+ ///
+ /// Applies the @override directive which is used to indicate that the current subgraph is taking
+ /// responsibility for resolving the marked field away from the subgraph specified in the from
+ /// argument. Name of the subgraph to be overridden has to match the name of the subgraph that
+ /// was used to publish their schema.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// description: String @override(from: "BarSubgraph")
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Name of the subgraph to be overridden.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IObjectFieldDescriptor Override(
+ this IObjectFieldDescriptor descriptor,
+ string from)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(from);
+
+ return descriptor.Directive(new OverrideDirective(from));
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDirective.cs
new file mode 100644
index 00000000000..5c5a1943cb4
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDirective.cs
@@ -0,0 +1,29 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @override(from: String!) on FIELD_DEFINITION
+///
+///
+/// The @override directive is used to indicate that the current subgraph is taking
+/// responsibility for resolving the marked field away from the subgraph specified in the from
+/// argument. Name of the subgraph to be overridden has to match the name of the subgraph that
+/// was used to publish their schema.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// description: String @override(from: "BarSubgraph")
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(OverrideDirective_Name, DirectiveLocation.FieldDefinition)]
+[GraphQLDescription(OverrideDirective_Description)]
+public sealed class OverrideDirective(string from)
+{
+ public string From { get; } = from;
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PackageAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PackageAttribute.cs
new file mode 100644
index 00000000000..2e0b4c499b8
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PackageAttribute.cs
@@ -0,0 +1,20 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct)]
+public sealed class PackageAttribute : Attribute
+{
+ internal PackageAttribute(string url, bool isFederationType = false)
+ {
+ Url = new(url);
+ IsFederationType = isFederationType;
+ }
+
+ public PackageAttribute(string url)
+ {
+ Url = new(url);
+ }
+
+ public Uri Url { get; }
+
+ internal bool IsFederationType { get; }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs
similarity index 53%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesAttribute.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs
index e1cba395a76..606457193e1 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/ProvidesAttribute.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs
@@ -2,20 +2,32 @@
using HotChocolate.Types.Descriptors;
using static HotChocolate.ApolloFederation.ThrowHelper;
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Types;
///
-/// The @provides directive is used to annotate the expected returned fieldset
-/// from a field on a base type that is guaranteed to be selectable by the gateway.
+///
+/// directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
+///
///
+/// The @provides directive is a router optimization hint specifying field set that
+/// can be resolved locally at the given subgraph through this particular query path. This
+/// allows you to expose only a subset of fields from the underlying entity type to be selectable
+/// from the federated schema without the need to call other subgraphs. Provided fields specified
+/// in the directive field set should correspond to a valid field on the underlying GraphQL
+/// interface/object type. @provides directive can only be used on fields returning entities.
///
-/// type Review @key(fields: "id") {
-/// product: Product @provides(fields: "name")
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// # implies name field can be resolved locally
+/// bar: Bar @provides(fields: "name")
+/// # name fields are external
+/// # so will be fetched from other subgraphs
+/// bars: [Bar]
/// }
///
-/// extend type Product @key(fields: "upc") {
-/// upc: String @external
-/// name: String @external
+/// type Bar @key(fields: "id") {
+/// id: ID!
+/// name: String @external
/// }
///
///
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDescriptorExtensions.cs
new file mode 100644
index 00000000000..a832d12e48b
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDescriptorExtensions.cs
@@ -0,0 +1,52 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ProvidesDescriptorExtensions
+{
+ ///
+ /// Applies the @provides directive which is a router optimization hint specifying field set that
+ /// can be resolved locally at the given subgraph through this particular query path. This
+ /// allows you to expose only a subset of fields from the underlying entity type to be selectable
+ /// from the federated schema without the need to call other subgraphs. Provided fields specified
+ /// in the directive field set should correspond to a valid field on the underlying GraphQL
+ /// interface/object type. @provides directive can only be used on fields returning entities.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// # implies name field can be resolved locally
+ /// bar: Bar @provides(fields: "name")
+ /// # name fields are external
+ /// # so will be fetched from other subgraphs
+ /// bars: [Bar]
+ /// }
+ ///
+ /// type Bar @key(fields: "id") {
+ /// id: ID!
+ /// name: String @external
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// The fields that are guaranteed to be selectable by the gateway.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IObjectFieldDescriptor Provides(
+ this IObjectFieldDescriptor descriptor,
+ string fieldSet)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(fieldSet);
+ return descriptor.Directive(new ProvidesDirective(fieldSet));
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDirective.cs
new file mode 100644
index 00000000000..99a6af13178
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesDirective.cs
@@ -0,0 +1,55 @@
+using HotChocolate.Language;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+using DirectiveLocation = HotChocolate.Types.DirectiveLocation;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
+///
+///
+/// The @provides directive is a router optimization hint specifying field set that
+/// can be resolved locally at the given subgraph through this particular query path. This
+/// allows you to expose only a subset of fields from the underlying entity type to be selectable
+/// from the federated schema without the need to call other subgraphs. Provided fields specified
+/// in the directive field set should correspond to a valid field on the underlying GraphQL
+/// interface/object type. @provides directive can only be used on fields returning entities.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// # implies name field can be resolved locally
+/// bar: Bar @provides(fields: "name")
+/// # name fields are external
+/// # so will be fetched from other subgraphs
+/// bars: [Bar]
+/// }
+///
+/// type Bar @key(fields: "id") {
+/// id: ID!
+/// name: String @external
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(ProvidesDirective_Name, DirectiveLocation.FieldDefinition)]
+[GraphQLDescription(ProvidesDirective_Description)]
+public sealed class ProvidesDirective
+{
+ public ProvidesDirective(string fields)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(fields);
+ Fields = FieldSetType.ParseSelectionSet(fields);
+ }
+
+ public ProvidesDirective(SelectionSetNode fields)
+ {
+ ArgumentNullException.ThrowIfNull(fields);
+ Fields = fields;
+ }
+
+ [FieldSet]
+ public SelectionSetNode Fields { get; }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs
similarity index 59%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresAttribute.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs
index 16549b3a1bb..d8022448c82 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/RequiresAttribute.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs
@@ -2,20 +2,29 @@
using HotChocolate.Types.Descriptors;
using static HotChocolate.ApolloFederation.ThrowHelper;
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Types;
///
-/// The @requires directive is used to annotate the required input fieldset
-/// from a base type for a resolver. It is used to develop a query plan
-/// where the required fields may not be needed by the client, but the
-/// service may need additional information from other services.
+///
+/// # federation v1 definition
+/// directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
///
+/// # federation v2 definition
+/// directive @requires(fields: FieldSet!) on FIELD_DEFINITON
+///
+///
+/// The @requires directive is used to specify external (provided by other subgraphs)
+/// entity fields that are needed to resolve target field. It is used to develop a query plan where
+/// the required fields may not be needed by the client, but the service may need additional
+/// information from other subgraphs. Required fields specified in the directive field set should
+/// correspond to a valid field on the underlying GraphQL interface/object and should be instrumented
+/// with @external directive.
///
-/// # extended from the Users service
-/// extend type User @key(fields: "id") {
-/// id: ID! @external
-/// email: String @external
-/// reviews: [Review] @requires(fields: "email")
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// # this field will be resolved from other subgraph
+/// remote: String @external
+/// local: String @requires(fields: "remote")
/// }
///
///
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDescriptorExtensions.cs
new file mode 100644
index 00000000000..acc54ef695d
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDescriptorExtensions.cs
@@ -0,0 +1,48 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class RequiresDescriptorExtensions
+{
+ ///
+ /// Applies the @requires directive which is used to specify external (provided by other subgraphs)
+ /// entity fields that are needed to resolve target field. It is used to develop a query plan where
+ /// the required fields may not be needed by the client, but the service may need additional
+ /// information from other subgraphs. Required fields specified in the directive field set should
+ /// correspond to a valid field on the underlying GraphQL interface/object and should be instrumented
+ /// with @external directive.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID!
+ /// # this field will be resolved from other subgraph
+ /// remote: String @external
+ /// local: String @requires(fields: "remote")
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// The describes which fields may
+ /// not be needed by the client, but are required by
+ /// this service as additional information from other services.
+ /// Grammatically, a field set is a selection set minus the braces.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ ///
+ /// is null or .
+ ///
+ public static IObjectFieldDescriptor Requires(
+ this IObjectFieldDescriptor descriptor,
+ string fieldSet)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+ ArgumentException.ThrowIfNullOrEmpty(fieldSet);
+
+ return descriptor.Directive(new RequiresDirective(fieldSet));
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDirective.cs
new file mode 100644
index 00000000000..c6b66e1bfc2
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresDirective.cs
@@ -0,0 +1,48 @@
+using HotChocolate.Language;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+using DirectiveLocation = HotChocolate.Types.DirectiveLocation;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @requires(fields: _FieldSet!) on FIELD_DEFINITON
+///
+///
+/// The @requires directive is used to specify external (provided by other subgraphs)
+/// entity fields that are needed to resolve target field. It is used to develop a query plan where
+/// the required fields may not be needed by the client, but the service may need additional
+/// information from other subgraphs. Required fields specified in the directive field set should
+/// correspond to a valid field on the underlying GraphQL interface/object and should be instrumented
+/// with @external directive.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID!
+/// # this field will be resolved from other subgraph
+/// remote: String @external
+/// local: String @requires(fields: "remote")
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(RequiresDirective_Name, DirectiveLocation.FieldDefinition)]
+[GraphQLDescription(RequiresDirective_Description)]
+public sealed class RequiresDirective
+{
+ public RequiresDirective(string fields)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(fields);
+ Fields = FieldSetType.ParseSelectionSet(fields);
+ }
+
+ public RequiresDirective(SelectionSetNode fields)
+ {
+ ArgumentNullException.ThrowIfNull(fields);
+ Fields = fields;
+ }
+
+ [FieldSet]
+ public SelectionSetNode Fields { get; }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs
new file mode 100644
index 00000000000..0b89917a912
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs
@@ -0,0 +1,80 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @requiresScopes(scopes: [[Scope!]!]!) on
+/// ENUM
+/// | FIELD_DEFINITION
+/// | INTERFACE
+/// | OBJECT
+/// | SCALAR
+///
+///
+/// Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes.
+/// Refer to the Apollo Router article for additional details.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID
+/// description: String @requiresScopes(scopes: [["scope1"]])
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Enum
+ | AttributeTargets.Interface
+ | AttributeTargets.Method
+ | AttributeTargets.Property
+ | AttributeTargets.Struct,
+ AllowMultiple = true
+)]
+public sealed class RequiresScopesAttribute : DescriptorAttribute
+{
+ ///
+ /// Initializes new instance of
+ ///
+ ///
+ /// Array of required JWT scopes.
+ ///
+ public RequiresScopesAttribute(string[] scopes)
+ {
+ Scopes = scopes;
+ }
+
+ ///
+ /// Retrieves array of required JWT scopes.
+ ///
+ public string[] Scopes { get; }
+
+ protected internal override void TryConfigure(
+ IDescriptorContext context,
+ IDescriptor descriptor,
+ ICustomAttributeProvider element)
+ {
+ switch (descriptor)
+ {
+ case IEnumTypeDescriptor desc:
+ desc.RequiresScopes(Scopes);
+ break;
+
+ case IObjectTypeDescriptor desc:
+ desc.RequiresScopes(Scopes);
+ break;
+
+ case IObjectFieldDescriptor desc:
+ desc.RequiresScopes(Scopes);
+ break;
+
+ case IInterfaceTypeDescriptor desc:
+ desc.RequiresScopes(Scopes);
+ break;
+
+ case IInterfaceFieldDescriptor desc:
+ desc.RequiresScopes(Scopes);
+ break;
+ }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDescriptorExtensions.cs
new file mode 100644
index 00000000000..74c41624f28
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDescriptorExtensions.cs
@@ -0,0 +1,230 @@
+using System.Collections.Generic;
+using System.Linq;
+using HotChocolate.Types.Descriptors;
+using HotChocolate.Types.Descriptors.Definitions;
+using HotChocolate.Types.Helpers;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// Provides extensions for applying @requiresScopes directive on type system descriptors.
+///
+public static class RequiresScopesDescriptorExtensions
+{
+ ///
+ /// Applies @requiresScopes directive to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID
+ /// description: String @requiresScopes(scopes: [["scope1"]])
+ /// }
+ ///
+ ///
+ ///
+ /// The type descriptor on which this directive shall be annotated.
+ ///
+ /// Required JWT scopes
+ ///
+ /// Returns the type descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IEnumTypeDescriptor RequiresScopes(
+ this IEnumTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes, def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ /// Applies @requiresScopes directive to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID
+ /// description: String @requiresScopes(scopes: [["scope1"]])
+ /// }
+ ///
+ ///
+ ///
+ /// The type descriptor on which this directive shall be annotated.
+ ///
+ /// Required JWT scopes
+ ///
+ /// Returns the type descriptor.
+ ///
+ ///
+ /// The is null .
+ ///
+ public static IEnumTypeDescriptor RequiresScopes(
+ this IEnumTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes.Select(s => new Scope(s)).ToArray(), def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IInterfaceFieldDescriptor RequiresScopes(
+ this IInterfaceFieldDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes, def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IInterfaceFieldDescriptor RequiresScopes(
+ this IInterfaceFieldDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes.Select(s => new Scope(s)).ToArray(), def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IInterfaceTypeDescriptor RequiresScopes(
+ this IInterfaceTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes, def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IInterfaceTypeDescriptor RequiresScopes(
+ this IInterfaceTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes.Select(s => new Scope(s)).ToArray(), def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IObjectFieldDescriptor RequiresScopes(
+ this IObjectFieldDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes, def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IObjectFieldDescriptor RequiresScopes(
+ this IObjectFieldDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes.Select(s => new Scope(s)).ToArray(), def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IObjectTypeDescriptor RequiresScopes(
+ this IObjectTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes, def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ ///
+ public static IObjectTypeDescriptor RequiresScopes(
+ this IObjectTypeDescriptor descriptor,
+ IReadOnlyList scopes)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ descriptor.Extend().OnBeforeCreate(
+ (ctx, def) =>
+ {
+ AddScopes(scopes.Select(s => new Scope(s)).ToArray(), def, ctx.TypeInspector);
+ });
+
+ return descriptor;
+ }
+
+ private static void AddScopes(
+ IReadOnlyList scopes,
+ IHasDirectiveDefinition definition,
+ ITypeInspector typeInspector)
+ {
+ var directive = definition
+ .Directives
+ .Select(t => t.Value)
+ .OfType()
+ .FirstOrDefault();
+
+ if (directive is null)
+ {
+ directive = new RequiresScopesDirective([]);
+ definition.AddDirective(directive, typeInspector);
+ }
+
+ var newScopes = scopes.ToHashSet();
+ directive.Scopes.Add(newScopes);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs
new file mode 100644
index 00000000000..d4ccb6614f5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using HotChocolate.ApolloFederation.Properties;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @@requiresScopes(scopes: [[Scope!]!]!) on
+/// ENUM
+/// | FIELD_DEFINITION
+/// | INTERFACE
+/// | OBJECT
+/// | SCALAR
+///
+///
+/// Directive that is used to indicate that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes.
+/// Refer to the Apollo Router article for additional details.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID
+/// description: String @requiresScopes(scopes: [["scope1"]])
+/// }
+///
+///
+///
+/// List of a list of required JWT scopes.
+///
+[Package(FederationVersionUrls.Federation24)]
+[DirectiveType(
+ RequiresScopesDirective_Name,
+ DirectiveLocation.Enum |
+ DirectiveLocation.FieldDefinition |
+ DirectiveLocation.Interface |
+ DirectiveLocation.Object |
+ DirectiveLocation.Scalar)]
+[GraphQLDescription(FederationResources.RequiresScopesDirective_Description)]
+public sealed class RequiresScopesDirective(List> scopes)
+{
+ ///
+ /// Retrieves list of a list of required JWT scopes.
+ ///
+ public List> Scopes { get; } = scopes;
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableAttribute.cs
new file mode 100644
index 00000000000..939a9d60f2a
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableAttribute.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @shareable repeatable on FIELD_DEFINITION | OBJECT
+///
+///
+/// The @shareable directive indicates that given object and/or field can be resolved by multiple subgraphs.
+/// If an object is marked as @shareable then all its fields are automatically shareable without the need
+/// for explicitly marking them with @shareable directive. All fields referenced from @key directive are
+/// automatically shareable as well.
+///
+///
+/// type Foo @key(fields: "id") {
+/// id: ID! # shareable because id is a key field
+/// name: String # non-shareable
+/// description: String @shareable # shareable
+/// }
+///
+/// type Bar @shareable {
+/// description: String # shareable because User is marked shareable
+/// name: String # shareable because User is marked shareable
+/// }
+///
+///
+[AttributeUsage(
+ AttributeTargets.Class
+ | AttributeTargets.Struct
+ | AttributeTargets.Method
+ | AttributeTargets.Property)]
+public sealed class ShareableAttribute : DescriptorAttribute
+{
+ protected internal override void TryConfigure(
+ IDescriptorContext context,
+ IDescriptor descriptor,
+ ICustomAttributeProvider element)
+ {
+ switch (descriptor)
+ {
+ case IObjectTypeDescriptor objectTypeDescriptor:
+ {
+ objectTypeDescriptor.Shareable();
+ break;
+ }
+ case IObjectFieldDescriptor objectFieldDescriptor:
+ {
+ objectFieldDescriptor.Shareable();
+ break;
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDescriptorExtensions.cs
new file mode 100644
index 00000000000..13cfb93a305
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDescriptorExtensions.cs
@@ -0,0 +1,107 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ShareableDescriptorExtensions
+{
+ ///
+ /// Applies @shareable directive which indicates that given object and/or field can be resolved by multiple
+ /// subgraphs.
+ /// If an object is marked as @shareable then all its fields are automatically shareable without the need
+ /// for explicitly marking them with @shareable directive. All fields referenced from @key directive are
+ /// automatically shareable as well.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID! # shareable because id is a key field
+ /// name: String # non-shareable
+ /// description: String @shareable # shareable
+ /// }
+ ///
+ /// type Bar @shareable {
+ /// description: String # shareable because User is marked shareable
+ /// name: String # shareable because User is marked shareable
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ public static IObjectTypeDescriptor Shareable(this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(ShareableDirective.Default);
+ }
+
+ ///
+ /// Applies @shareable directive which indicates that given object and/or field can be resolved by multiple subgraphs.
+ /// If an object is marked as @shareable then all its fields are automatically shareable without the need
+ /// for explicitly marking them with @shareable directive. All fields referenced from @key directive are
+ /// automatically shareable as well.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID! # shareable because id is a key field
+ /// name: String # non-shareable
+ /// description: String @shareable # shareable
+ /// }
+ ///
+ /// type Bar @shareable {
+ /// description: String # shareable because User is marked shareable
+ /// name: String # shareable because User is marked shareable
+ /// }
+ ///
+ ///
+ ///
+ /// The object type descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object type descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ public static IObjectTypeDescriptor Shareable(this IObjectTypeDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(ShareableDirective.Default);
+ }
+
+ ///
+ /// Applies @shareable directive which indicates that given object and/or field can be resolved by multiple subgraphs.
+ /// If an object is marked as @shareable then all its fields are automatically shareable without the need
+ /// for explicitly marking them with @shareable directive. All fields referenced from @key directive are
+ /// automatically shareable as well.
+ ///
+ /// type Foo @key(fields: "id") {
+ /// id: ID! # shareable because id is a key field
+ /// name: String # non-shareable
+ /// description: String @shareable # shareable
+ /// }
+ ///
+ /// type Bar @shareable {
+ /// description: String # shareable because User is marked shareable
+ /// name: String # shareable because User is marked shareable
+ /// }
+ ///
+ ///
+ ///
+ /// The object field descriptor on which this directive shall be annotated.
+ ///
+ ///
+ /// Returns the object field descriptor.
+ ///
+ ///
+ /// is null .
+ ///
+ public static IObjectFieldDescriptor Shareable(this IObjectFieldDescriptor descriptor)
+ {
+ ArgumentNullException.ThrowIfNull(descriptor);
+
+ return descriptor.Directive(ShareableDirective.Default);
+ }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDirective.cs
new file mode 100644
index 00000000000..099b6b6295c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ShareableDirective.cs
@@ -0,0 +1,39 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.FederationVersionUrls;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+///
+/// directive @shareable repeatable on FIELD_DEFINITION | OBJECT
+///
+///
+/// The @shareable directive indicates that given object and/or field can be resolved by multiple subgraphs.
+/// If an object is marked as @shareable then all its fields are automatically shareable without the need
+/// for explicitly marking them with @shareable directive. All fields referenced from @key directive are
+/// automatically shareable as well.
+///
+/// type Foo @key(fields: "id") {
+/// id: ID! # shareable because id is a key field
+/// name: String # non-shareable
+/// description: String @shareable # shareable
+/// }
+///
+/// type Bar @shareable {
+/// description: String # shareable because User is marked shareable
+/// name: String # shareable because User is marked shareable
+/// }
+///
+///
+[Package(Federation20)]
+[DirectiveType(
+ ShareableDirective_Name,
+ DirectiveLocation.FieldDefinition |
+ DirectiveLocation.Object,
+ IsRepeatable = true)]
+[GraphQLDescription(ShareableDirective_Description)]
+public sealed class ShareableDirective
+{
+ public static ShareableDirective Default { get; } = new();
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetAttribute.cs
new file mode 100644
index 00000000000..9dc61909067
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetAttribute.cs
@@ -0,0 +1,13 @@
+using System.Reflection;
+using HotChocolate.Types.Descriptors;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+internal sealed class FieldSetAttribute : DirectiveArgumentDescriptorAttribute
+{
+ protected override void OnConfigure(
+ IDescriptorContext context,
+ IDirectiveArgumentDescriptor descriptor,
+ PropertyInfo property)
+ => descriptor.Type>();
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FieldSetType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetType.cs
similarity index 91%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/FieldSetType.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetType.cs
index b437c896633..13dc6a7153c 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FieldSetType.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/FieldSetType.cs
@@ -1,9 +1,8 @@
-using HotChocolate.ApolloFederation.Constants;
using HotChocolate.ApolloFederation.Properties;
using HotChocolate.Language;
using static HotChocolate.ApolloFederation.ThrowHelper;
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Types;
///
/// A scalar called _FieldSet is a custom scalar type that is used to
@@ -14,12 +13,13 @@ namespace HotChocolate.ApolloFederation;
/// This means it can represent a single field "upc", multiple fields "id countryCode",
/// and even nested selection sets "id organization { id }".
///
+[Package(FederationVersionUrls.Federation20)]
public sealed class FieldSetType : ScalarType
{
///
/// Initializes a new instance of .
///
- public FieldSetType() : this(WellKnownTypeNames.FieldSet)
+ public FieldSetType() : this(FederationTypeNames.FieldSetType_Name)
{
}
@@ -53,7 +53,7 @@ protected override SelectionSetNode ParseLiteral(StringValueNode valueSyntax)
///
protected override StringValueNode ParseValue(SelectionSetNode runtimeValue)
- => new StringValueNode(SerializeSelectionSet(runtimeValue));
+ => new(SerializeSelectionSet(runtimeValue));
///
public override IValueNode ParseResult(object? resultValue)
@@ -128,12 +128,12 @@ public override bool TryDeserialize(object? resultValue, out object? runtimeValu
return false;
}
- private static SelectionSetNode ParseSelectionSet(string s)
+ internal static SelectionSetNode ParseSelectionSet(string s)
=> Utf8GraphQLParser.Syntax.ParseSelectionSet($"{{{s}}}");
private static string SerializeSelectionSet(SelectionSetNode selectionSet)
{
var s = selectionSet.ToString(false);
- return s.Substring(1, s.Length - 2).Trim();
+ return s.AsSpan()[1 .. ^1].Trim().ToString();
}
}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Scope.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Scope.cs
new file mode 100644
index 00000000000..fa07cdde81c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Scope.cs
@@ -0,0 +1,23 @@
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// Scalar Scope
representation.
+///
+public readonly record struct Scope
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ /// Scope value
+ ///
+ public Scope(string value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Retrieve scope value
+ ///
+ public string Value { get; }
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ScopeType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ScopeType.cs
new file mode 100644
index 00000000000..637c9ab0f98
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ScopeType.cs
@@ -0,0 +1,41 @@
+using HotChocolate.ApolloFederation.Properties;
+using HotChocolate.Language;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// The Scope
scalar representing a JWT scope. Serializes as a string.
+///
+public sealed class ScopeType : ScalarType
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public ScopeType() : this(FederationTypeNames.ScopeType_Name)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ /// Scalar name
+ ///
+ ///
+ /// Defines if this scalar shall bind implicitly to .
+ ///
+ public ScopeType(string name, BindingBehavior bind = BindingBehavior.Explicit)
+ : base(name, bind)
+ {
+ Description = FederationResources.ScopeType_Description;
+ }
+
+ protected override Scope ParseLiteral(StringValueNode valueSyntax)
+ => new(valueSyntax.Value);
+
+ public override IValueNode ParseResult(object? resultValue)
+ => ParseValue(resultValue);
+
+ protected override StringValueNode ParseValue(Scope runtimeValue)
+ => new(runtimeValue.Value);
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs
new file mode 100644
index 00000000000..acb2f95b86c
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/ServerFields.cs
@@ -0,0 +1,45 @@
+#nullable enable
+
+using System.Collections.Generic;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.Resolvers;
+using HotChocolate.Types.Descriptors;
+using HotChocolate.Types.Descriptors.Definitions;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+public static class ServerFields
+{
+ private static readonly _Service _service = new();
+
+ internal static ObjectFieldDefinition CreateServiceField(IDescriptorContext context)
+ {
+ var descriptor = ObjectFieldDescriptor.New(context, WellKnownFieldNames.Service);
+ descriptor.Type>>().Resolve(_service);
+ descriptor.Definition.PureResolver = Resolve;
+
+ static _Service Resolve(IPureResolverContext ctx)
+ => _service;
+
+ return descriptor.CreateDefinition();
+ }
+
+ internal static ObjectFieldDefinition CreateEntitiesField(IDescriptorContext context)
+ {
+ var descriptor = ObjectFieldDescriptor.New(context, WellKnownFieldNames.Entities);
+
+ descriptor
+ .Type>>()
+ .Argument(
+ WellKnownArgumentNames.Representations,
+ d => d.Type>>>())
+ .Resolve(
+ c => EntitiesResolver.ResolveAsync(
+ c.Schema,
+ c.ArgumentValue>(
+ WellKnownArgumentNames.Representations),
+ c));
+
+ return descriptor.CreateDefinition();
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/AnyType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_AnyType.cs
similarity index 86%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/AnyType.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_AnyType.cs
index 127236a62bf..c971db3dd35 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/AnyType.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_AnyType.cs
@@ -1,30 +1,31 @@
using System.Linq;
-using HotChocolate.ApolloFederation.Constants;
using HotChocolate.ApolloFederation.Properties;
using HotChocolate.Language;
using HotChocolate.Utilities;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
using static HotChocolate.ApolloFederation.ThrowHelper;
-namespace HotChocolate.ApolloFederation;
+namespace HotChocolate.ApolloFederation.Types;
///
/// The _Any scalar is used to pass representations of entities
/// from external services into the root _entities field for execution.
///
-public sealed class AnyType : ScalarType
+// ReSharper disable once InconsistentNaming
+public sealed class _AnyType : ScalarType
{
public const string TypeNameField = "__typename";
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
- public AnyType()
- : this(WellKnownTypeNames.Any)
+ public _AnyType()
+ : this(AnyType_Name)
{
}
///
- /// Initializes a new instance of .
+ /// Initializes a new instance of .
///
///
/// The name the scalar shall have.
@@ -32,7 +33,7 @@ public AnyType()
///
/// Defines if this scalar shall bind implicitly to .
///
- public AnyType(string name, BindingBehavior bind = BindingBehavior.Explicit)
+ public _AnyType(string name, BindingBehavior bind = BindingBehavior.Explicit)
: base(name, bind)
{
Description = FederationResources.Any_Description;
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_EntityType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_EntityType.cs
new file mode 100644
index 00000000000..cb5b16967ec
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_EntityType.cs
@@ -0,0 +1,19 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// A union called _Entity which is a union of all types that use the @key directive,
+/// including both types native to the schema and extended types.
+///
+// ReSharper disable once InconsistentNaming
+public sealed class _EntityType : UnionType
+{
+ protected override void Configure(IUnionTypeDescriptor descriptor)
+ {
+ descriptor
+ .Name(EntityType_Name)
+ .Description(EntityType_Description);
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_Service.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_Service.cs
new file mode 100644
index 00000000000..9a4d2e6e9a0
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/_Service.cs
@@ -0,0 +1,22 @@
+using static HotChocolate.ApolloFederation.FederationTypeNames;
+using static HotChocolate.ApolloFederation.Properties.FederationResources;
+
+namespace HotChocolate.ApolloFederation.Types;
+
+///
+/// A new object type called _Service must be created.
+/// This type must have an sdl: String! field 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.
+///
+[ObjectType(ServiceType_Name)]
+[GraphQLDescription(ServiceType_Description)]
+// ReSharper disable once InconsistentNaming
+public sealed class _Service
+{
+ [GraphQLName(WellKnownFieldNames.Sdl)]
+ public string GetSdl(ISchema schema)
+ => SchemaPrinter.Print(schema);
+}
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownArgumentNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownArgumentNames.cs
new file mode 100644
index 00000000000..a5b16c05e67
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownArgumentNames.cs
@@ -0,0 +1,13 @@
+namespace HotChocolate.ApolloFederation;
+
+internal static class WellKnownArgumentNames
+{
+ public const string Fields = "fields";
+ public const string From = "from";
+ public const string Name = "name";
+ public const string Resolvable = "resolvable";
+ public const string Representations = "representations";
+ public const string Scopes = "scopes";
+ public const string Url = "url";
+ public const string Policies = "policies";
+}
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownFieldNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownFieldNames.cs
similarity index 77%
rename from src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownFieldNames.cs
rename to src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownFieldNames.cs
index cad442c664c..39ec1ebdfd1 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Constants/WellKnownFieldNames.cs
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/WellKnownFieldNames.cs
@@ -1,4 +1,4 @@
-namespace HotChocolate.ApolloFederation.Constants;
+namespace HotChocolate.ApolloFederation;
internal static class WellKnownFieldNames
{
diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/packages.lock.json b/src/HotChocolate/ApolloFederation/src/ApolloFederation/packages.lock.json
index 99dd6af021e..29eb4b6d081 100644
--- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/packages.lock.json
+++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/packages.lock.json
@@ -1,273 +1,6 @@
{
"version": 1,
"dependencies": {
- "net6.0": {
- "Microsoft.SourceLink.GitHub": {
- "type": "Direct",
- "requested": "[1.1.1, )",
- "resolved": "1.1.1",
- "contentHash": "IaJGnOv/M7UQjRJks7B6p7pbPnOwisYGOIzqCz5ilGFTApZ3ktOR+6zJ12ZRPInulBmdAf1SrGdDG2MU8g6XTw==",
- "dependencies": {
- "Microsoft.Build.Tasks.Git": "1.1.1",
- "Microsoft.SourceLink.Common": "1.1.1"
- }
- },
- "Microsoft.Bcl.AsyncInterfaces": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
- },
- "Microsoft.Build.Tasks.Git": {
- "type": "Transitive",
- "resolved": "1.1.1",
- "contentHash": "AT3HlgTjsqHnWpBHSNeR0KxbLZD7bztlZVj7I8vgeYG9SYqbeFGh0TM/KVtC6fg53nrWHl3VfZFvb5BiQFcY6Q=="
- },
- "Microsoft.Extensions.DependencyInjection": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "Microsoft.Extensions.DependencyInjection.Abstractions": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg=="
- },
- "Microsoft.Extensions.ObjectPool": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "7hR9FU0JJHOCLmn/Ary31pLLAhlzoMptBKs5CJmNUzD87dXjl+/NqVkyCTl6cT2JAfTK0G39HpvCOv1fhsX3BQ=="
- },
- "Microsoft.Extensions.Options": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0",
- "Microsoft.Extensions.Primitives": "6.0.0"
- }
- },
- "Microsoft.Extensions.Primitives": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "Microsoft.SourceLink.Common": {
- "type": "Transitive",
- "resolved": "1.1.1",
- "contentHash": "WMcGpWKrmJmzrNeuaEb23bEMnbtR/vLmvZtkAP5qWu7vQsY59GqfRJd65sFpBszbd2k/bQ8cs8eWawQKAabkVg=="
- },
- "System.Collections.Immutable": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "System.ComponentModel.Annotations": {
- "type": "Transitive",
- "resolved": "5.0.0",
- "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
- },
- "System.Diagnostics.DiagnosticSource": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "System.Runtime.CompilerServices.Unsafe": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
- },
- "System.Text.Encodings.Web": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0"
- }
- },
- "System.Text.Json": {
- "type": "Transitive",
- "resolved": "6.0.7",
- "contentHash": "/Tf/9XjprpHolbcDOrxsKVYy/mUG/FS7aGd9YUgBVEiHeQH4kAE0T1sMbde7q6B5xcrNUsJ5iW7D1RvHudQNqA==",
- "dependencies": {
- "System.Runtime.CompilerServices.Unsafe": "6.0.0",
- "System.Text.Encodings.Web": "6.0.0"
- }
- },
- "System.Threading.Channels": {
- "type": "Transitive",
- "resolved": "6.0.0",
- "contentHash": "TY8/9+tI0mNaUMgntOxxaq2ndTkdXqLSxvPmas7XEqOlv9lQtB7wLjYGd756lOaO7Dvb5r/WXhluM+0Xe87v5Q=="
- },
- "System.Threading.Tasks.Extensions": {
- "type": "Transitive",
- "resolved": "4.5.0",
- "contentHash": "csAJe24tWCOTO/rXoJAuBGuOq7ZdHY60XtC6b/hNMHT9tuX+2J9HK7nciLEtNvnrRLMxBACLXO3R4y5+kCduMA=="
- },
- "greendonut": {
- "type": "Project",
- "dependencies": {
- "Microsoft.Extensions.ObjectPool": "[6.0.0, )",
- "System.Diagnostics.DiagnosticSource": "[6.0.0, )",
- "System.Threading.Tasks.Extensions": "[4.5.0, )"
- }
- },
- "HotChocolate": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Authorization": "[0.0.0, )",
- "HotChocolate.Execution": "[0.0.0, )",
- "HotChocolate.Fetching": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )",
- "HotChocolate.Types.CursorPagination": "[0.0.0, )",
- "HotChocolate.Types.Mutations": "[0.0.0, )",
- "HotChocolate.Types.OffsetPagination": "[0.0.0, )",
- "HotChocolate.Validation": "[0.0.0, )"
- }
- },
- "hotchocolate.abstractions": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language": "[0.0.0, )",
- "Microsoft.Bcl.AsyncInterfaces": "[6.0.0, )",
- "System.Collections.Immutable": "[6.0.0, )"
- }
- },
- "hotchocolate.authorization": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Execution": "[0.0.0, )"
- }
- },
- "hotchocolate.execution": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Abstractions": "[0.0.0, )",
- "HotChocolate.Execution.Abstractions": "[0.0.0, )",
- "HotChocolate.Fetching": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )",
- "HotChocolate.Utilities.DependencyInjection": "[0.0.0, )",
- "HotChocolate.Validation": "[0.0.0, )",
- "Microsoft.Extensions.DependencyInjection": "[6.0.0, )",
- "System.Threading.Channels": "[6.0.0, )"
- }
- },
- "hotchocolate.execution.abstractions": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Abstractions": "[0.0.0, )",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "[6.0.0, )"
- }
- },
- "hotchocolate.fetching": {
- "type": "Project",
- "dependencies": {
- "GreenDonut": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )"
- }
- },
- "hotchocolate.language": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language.SyntaxTree": "[0.0.0, )",
- "HotChocolate.Language.Utf8": "[0.0.0, )",
- "HotChocolate.Language.Visitors": "[0.0.0, )",
- "HotChocolate.Language.Web": "[0.0.0, )"
- }
- },
- "hotchocolate.language.syntaxtree": {
- "type": "Project",
- "dependencies": {
- "Microsoft.Extensions.ObjectPool": "[6.0.0, )"
- }
- },
- "hotchocolate.language.utf8": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language.SyntaxTree": "[0.0.0, )"
- }
- },
- "hotchocolate.language.visitors": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language.SyntaxTree": "[0.0.0, )"
- }
- },
- "hotchocolate.language.web": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language.Utf8": "[0.0.0, )"
- }
- },
- "hotchocolate.types": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Abstractions": "[0.0.0, )",
- "HotChocolate.Types.Shared": "[0.0.0, )",
- "HotChocolate.Utilities": "[0.0.0, )",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "[6.0.0, )",
- "Microsoft.Extensions.ObjectPool": "[6.0.0, )",
- "System.ComponentModel.Annotations": "[5.0.0, )",
- "System.Text.Json": "[6.0.7, )"
- }
- },
- "hotchocolate.types.cursorpagination": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Execution": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )"
- }
- },
- "hotchocolate.types.mutations": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Execution": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )"
- }
- },
- "hotchocolate.types.offsetpagination": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Execution": "[0.0.0, )",
- "HotChocolate.Types": "[0.0.0, )"
- }
- },
- "hotchocolate.types.shared": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Language.SyntaxTree": "[0.0.0, )"
- }
- },
- "hotchocolate.utilities": {
- "type": "Project"
- },
- "hotchocolate.utilities.dependencyinjection": {
- "type": "Project",
- "dependencies": {
- "Microsoft.Extensions.DependencyInjection": "[6.0.0, )"
- }
- },
- "hotchocolate.validation": {
- "type": "Project",
- "dependencies": {
- "HotChocolate.Types": "[0.0.0, )",
- "Microsoft.Extensions.DependencyInjection.Abstractions": "[6.0.0, )",
- "Microsoft.Extensions.Options": "[6.0.0, )"
- }
- }
- },
"net7.0": {
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.txt
similarity index 87%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.txt
index 3f0248c569c..5d2b0601ec2 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/AnyTypeTests.txt
@@ -1,7 +1,7 @@
using HotChocolate.ApolloFederation.Constants;
using HotChocolate.Language;
using HotChocolate.Types;
-using Xunit;
+using AnyType = HotChocolate.ApolloFederation.Types.AnyType;
namespace HotChocolate.ApolloFederation;
@@ -33,43 +33,41 @@ public void Deserialize()
var representationObject = type.Deserialize(serialized);
// assert
- Assert.IsType(representationObject);
- if (representationObject is Representation representation)
- {
- Assert.Equal("test", representation.TypeName);
- Assert.Collection(representation.Data.Fields,
- node =>
- {
- Assert.Equal(
- AnyType.TypeNameField,
- node.Name.Value);
-
- Assert.Equal(
- "test",
- node.Value.Value);
- },
- node =>
- {
- Assert.Equal(
- "faa",
- node.Name.Value);
-
- Assert.Equal(
- "foo",
- node.Value.Value);
- },
- node =>
- {
- Assert.Equal(
- "foo",
- node.Name.Value);
-
- Assert.Equal(
- "bar",
- node.Value.Value);
- }
- );
- }
+ var representation = Assert.IsType(representationObject);
+
+ Assert.Equal("test", representation.TypeName);
+ Assert.Collection(representation.Data.Fields,
+ node =>
+ {
+ Assert.Equal(
+ AnyType.TypeNameField,
+ node.Name.Value);
+
+ Assert.Equal(
+ "test",
+ node.Value.Value);
+ },
+ node =>
+ {
+ Assert.Equal(
+ "faa",
+ node.Name.Value);
+
+ Assert.Equal(
+ "foo",
+ node.Value.Value);
+ },
+ node =>
+ {
+ Assert.Equal(
+ "foo",
+ node.Name.Value);
+
+ Assert.Equal(
+ "bar",
+ node.Value.Value);
+ }
+ );
}
[Fact]
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs
index 210f81a7841..a4e785174f3 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/CertificationTests.cs
@@ -4,7 +4,6 @@
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using Snapshooter.Xunit;
-using Xunit;
namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased;
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Data.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Data.cs
index 4bba84cad81..e1b33e9acc5 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Data.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Data.cs
@@ -4,9 +4,9 @@ namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Type
public class Data
{
- public List Products { get; } = new()
- {
+ public List Products { get; } =
+ [
new("apollo-federation", "federation", "@apollo/federation", "OSS"),
- new("apollo-studio", "studio", string.Empty, "platform"),
- };
+ new("apollo-studio", "studio", string.Empty, "platform")
+ ];
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Product.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Product.cs
index 8c7dbfbcbc8..66e1f754a8e 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Product.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Product.cs
@@ -1,4 +1,6 @@
using System.Linq;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types.Relay;
namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Types;
@@ -6,29 +8,21 @@ namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Type
[Key("id")]
[Key("sku package")]
[Key("sku variation { id }")]
-public class Product
+public class Product(string id, string sku, string package, string variation)
{
- public Product(string id, string sku, string package, string variation)
- {
- Id = id;
- Sku = sku;
- Package = package;
- Variation = new(variation);
- }
-
[ID]
- public string Id { get; }
+ public string Id { get; } = id;
- public string? Sku { get; }
+ public string? Sku { get; } = sku;
- public string? Package { get; }
+ public string? Package { get; } = package;
- public ProductVariation? Variation { get; }
+ public ProductVariation? Variation { get; } = new(variation);
public ProductDimension? Dimensions { get; } = new("1", 1);
[Provides("totalProductsCreated")]
- public User? CreatedBy { get; } = new("support@apollographql.com", 1337);
+ public User? CreatedBy { get; } = new("contact@chillicream.com", 1337);
[ReferenceResolver]
public static Product? GetProductById(
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/ProductDimension.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/ProductDimension.cs
index a1018fb718a..8b37a3fed80 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/ProductDimension.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/ProductDimension.cs
@@ -1,14 +1,8 @@
namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Types;
-public class ProductDimension
+public class ProductDimension(string size, double weight)
{
- public ProductDimension(string size, double weight)
- {
- Size = size;
- Weight = weight;
- }
+ public string? Size { get; } = size;
- public string? Size { get; }
-
- public double? Weight { get; }
+ public double? Weight { get; } = weight;
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Query.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Query.cs
index f18c954753b..c8a84a73b8f 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Query.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/Query.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types.Relay;
namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Types;
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/User.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/User.cs
index c1f282684f4..f2e2ebbfabe 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/User.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/Types/User.cs
@@ -1,3 +1,4 @@
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types.Relay;
namespace HotChocolate.ApolloFederation.CertificationSchema.AnnotationBased.Types;
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Provides.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Provides.snap
index c3dffea86c6..9e20d556c67 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Provides.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Provides.snap
@@ -2,7 +2,7 @@
"data": {
"product": {
"createdBy": {
- "email": "support@apollographql.com",
+ "email": "contact@chillicream.com",
"totalProductsCreated": 1337
}
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Schema_Snapshot.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Schema_Snapshot.snap
index 14c42f4b2c0..17df87c164e 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Schema_Snapshot.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Schema_Snapshot.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "FieldSet", "@external" ]) {
query: Query
}
@@ -43,13 +43,16 @@ union _Entity = Product | User
directive @external on FIELD_DEFINITION
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+"Object representation of @link directive."
+directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." import: [String!]) repeatable on SCHEMA
"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
-directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
-
-"Scalar representing a set of fields."
-scalar _FieldSet
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Subgraph_SDL.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Subgraph_SDL.snap
index 0a6311806fc..17df87c164e 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Subgraph_SDL.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/AnnotationBased/__snapshots__/CertificationTests.Subgraph_SDL.snap
@@ -1,4 +1,8 @@
-type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "FieldSet", "@external" ]) {
+ query: Query
+}
+
+type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
id: ID!
sku: String
package: String
@@ -16,11 +20,39 @@ type ProductVariation {
id: ID!
}
-extend type Query {
+type Query {
product(id: ID!): Product
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
}
-extend type User @key(fields: "email") {
+type User @key(fields: "email") {
email: ID! @external
totalProductsCreated: Int @external
}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Product | User
+
+"Directive to indicate that a field is owned by another service, for example via Apollo federation."
+directive @external on FIELD_DEFINITION
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+"Object representation of @link directive."
+directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." import: [String!]) repeatable on SCHEMA
+
+"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs
index 5d17999f79f..261e70ff7c9 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/CertificationTests.cs
@@ -4,7 +4,6 @@
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using Snapshooter.Xunit;
-using Xunit;
namespace HotChocolate.ApolloFederation.CertificationSchema.CodeFirst;
@@ -25,19 +24,19 @@ public async Task Subgraph_SDL()
// act
var result = await executor.ExecuteAsync(
- @"{
+ """
+ {
_service {
sdl
}
- }");
+ }
+ """);
// assert
- Assert.IsType(
- Assert.IsType(
- Assert.IsType(result).Data)
- .GetValueOrDefault("_service"))
- .GetValueOrDefault("sdl")
- .MatchSnapshot();
+ var queryResult = Assert.IsType(result);
+ var data = Assert.IsType(queryResult.Data);
+ var service = Assert.IsType(data.GetValueOrDefault("_service"));
+ service.GetValueOrDefault("sdl").MatchSnapshot();
}
[Fact]
@@ -48,13 +47,15 @@ public async Task Product_By_Id()
// act
var result = await executor.ExecuteAsync(
- @"query ($representations: [_Any!]!) {
+ """
+ query ($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Product {
sku
}
}
- }",
+ }
+ """,
new Dictionary
{
["representations"] = new List
@@ -77,13 +78,15 @@ public async Task Product_By_Package()
// act
var result = await executor.ExecuteAsync(
- @"query ($representations: [_Any!]!) {
+ """
+ query ($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Product {
sku
}
}
- }",
+ }
+ """,
new Dictionary
{
["representations"] = new List
@@ -107,13 +110,15 @@ public async Task Product_By_Variation()
// act
var result = await executor.ExecuteAsync(
- @"query ($representations: [_Any!]!) {
+ """
+ query ($representations: [_Any!]!) {
_entities(representations: $representations) {
... on Product {
sku
}
}
- }",
+ }
+ """,
new Dictionary
{
["representations"] = new List
@@ -139,11 +144,13 @@ public async Task Provides()
// act
var result = await executor.ExecuteAsync(
- @"query ($id: ID!) {
+ """
+ query ($id: ID!) {
product(id: $id) {
createdBy { email totalProductsCreated }
}
- }",
+ }
+ """,
new Dictionary
{
["id"] = "apollo-federation",
@@ -161,11 +168,13 @@ public async Task Requires()
// act
var result = await executor.ExecuteAsync(
- @"query ($id: ID!) {
+ """
+ query ($id: ID!) {
product(id: $id) {
dimensions { size weight }
}
- }",
+ }
+ """,
new Dictionary
{
["id"] = "apollo-federation",
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/ProductType.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/ProductType.cs
index ae4a992d896..a24d080ac51 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/ProductType.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/ProductType.cs
@@ -1,4 +1,6 @@
using System.Linq;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
namespace HotChocolate.ApolloFederation.CertificationSchema.CodeFirst.Types;
@@ -23,9 +25,9 @@ protected override void Configure(IObjectTypeDescriptor descriptor)
.Key("sku variation { id }")
.ResolveReferenceWith(t => GetProductByVariation(default!, default!, default!));
- descriptor
- .Field(t => t.CreatedBy)
- .Provides("totalProductsCreated")
+ ProvidesDescriptorExtensions.Provides(
+ descriptor
+ .Field(t => t.CreatedBy), "totalProductsCreated")
.Type>();
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/QueryType.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/QueryType.cs
index 54a6b05f908..f601da8a6b5 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/QueryType.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/QueryType.cs
@@ -1,4 +1,5 @@
using System.Linq;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
namespace HotChocolate.ApolloFederation.CertificationSchema.CodeFirst.Types;
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/User.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/User.cs
index 1ad79c9f611..e15fb009f53 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/User.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/User.cs
@@ -1,5 +1,3 @@
-using HotChocolate.Types.Relay;
-
namespace HotChocolate.ApolloFederation.CertificationSchema.CodeFirst.Types;
public class User
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/UserType.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/UserType.cs
index 7b78308f699..844ea58c245 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/UserType.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/Types/UserType.cs
@@ -1,3 +1,4 @@
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
namespace HotChocolate.ApolloFederation.CertificationSchema.CodeFirst.Types;
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Schema_Snapshot.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Schema_Snapshot.snap
index d645304fedc..f8412cdddab 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Schema_Snapshot.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Schema_Snapshot.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "@external", "FieldSet" ]) {
query: Query
}
@@ -43,13 +43,16 @@ union _Entity = Product | User
directive @external on FIELD_DEFINITION
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+"Object representation of @link directive."
+directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." import: [String!]) repeatable on SCHEMA
"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
-directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
-
-"Scalar representing a set of fields."
-scalar _FieldSet
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Subgraph_SDL.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Subgraph_SDL.snap
index bf23f5f8774..f8412cdddab 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Subgraph_SDL.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/CertificationSchema/CodeFirst/__snapshots__/CertificationTests.Subgraph_SDL.snap
@@ -1,4 +1,8 @@
-type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "@external", "FieldSet" ]) {
+ query: Query
+}
+
+type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
id: ID!
createdBy: User! @provides(fields: "totalProductsCreated")
sku: String
@@ -16,11 +20,39 @@ type ProductVariation {
id: ID!
}
-extend type Query {
+type Query {
product(id: ID!): Product
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
}
-extend type User @key(fields: "email") {
+type User @key(fields: "email") {
email: ID! @external
totalProductsCreated: Int @external
}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Product | User
+
+"Directive to indicate that a field is owned by another service, for example via Apollo federation."
+directive @external on FIELD_DEFINITION
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+"Object representation of @link directive."
+directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." import: [String!]) repeatable on SCHEMA
+
+"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ComposeDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ComposeDirectiveTests.cs
new file mode 100644
index 00000000000..d3c9bb6d070
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ComposeDirectiveTests.cs
@@ -0,0 +1,50 @@
+using System.Threading.Tasks;
+using CookieCrumble;
+using HotChocolate.ApolloFederation.Types;
+using HotChocolate.Execution;
+using HotChocolate.Language;
+using HotChocolate.Types;
+using Microsoft.Extensions.DependencyInjection;
+using DirectiveLocation = HotChocolate.Types.DirectiveLocation;
+
+namespace HotChocolate.ApolloFederation;
+
+public class ComposeDirectiveTests
+{
+ [Fact]
+ public async Task TestServiceTypeEmptyQueryTypePureCodeFirst()
+ {
+ // arrange
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
+ .AddApolloFederation()
+ .AddQueryType()
+ .AddType()
+ .ExportDirective()
+ .BuildSchemaAsync();
+
+ var entityType = schema.GetType(FederationTypeNames.ServiceType_Name);
+ var sdlResolver = entityType.Fields[WellKnownFieldNames.Sdl].Resolver!;
+
+ // act
+ var value = await sdlResolver(TestHelper.CreateResolverContext(schema));
+
+ Utf8GraphQLParser
+ .Parse((string)value!)
+ .MatchSnapshot();
+ }
+
+ [Key("field")]
+ public class Address
+ {
+ [CustomDirective]
+ public string Field => "abc";
+ }
+
+ [Package("https://specs.custom.dev/custom/v1.0")]
+ [DirectiveType(DirectiveLocation.FieldDefinition)]
+ public sealed class Custom;
+
+ public sealed class CustomDirectiveAttribute()
+ : DirectiveAttribute(new Custom());
+}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.txt
similarity index 93%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.txt
index 86348b7123a..32811c90923 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ExternalDirectiveTests.txt
@@ -1,5 +1,6 @@
using System.Linq;
using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Snapshooter.Xunit;
@@ -63,13 +64,12 @@ public void AnnotateExternalToTypeFieldSchemaFirst()
var schema = SchemaBuilder.New()
.AddDocumentFromString(
- @"
- type Query {
- field(a: Int): String
- @external
- }
- "
- )
+ """
+ type Query {
+ field(a: Int): String
+ @external
+ }
+ """)
.AddDirectiveType()
.Use(_ => _ => default)
.Create();
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.txt
similarity index 96%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.txt
index 88f685d70b9..910022afd8a 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/KeyDirectiveTests.txt
@@ -1,5 +1,6 @@
using System.Linq;
using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Snapshooter.Xunit;
@@ -26,8 +27,8 @@ public void AddKeyDirective_EnsureAvailableInSchema()
Assert.NotNull(directive);
Assert.IsType(directive);
Assert.Equal(WellKnownTypeNames.Key, directive!.Name);
- Assert.Single(directive.Arguments);
- AssertDirectiveHasFieldsArgument(directive);
+ Assert.Equal(2, directive.Arguments.Count);
+ AssertDirectiveHasFieldsArgument(directive.Arguments.Take(1));
Assert.True(directive.Locations.HasFlag(DirectiveLocation.Object));
Assert.True(directive.Locations.HasFlag(DirectiveLocation.Interface));
}
@@ -194,6 +195,7 @@ public void AnnotateKeyToClassAttributesPureCodeFirst()
public class Query
{
+ // ReSharper disable once InconsistentNaming
public T someField(int id) => default!;
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/PolicyDirectiveTests.txt b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/PolicyDirectiveTests.txt
new file mode 100644
index 00000000000..c476ec7df01
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/PolicyDirectiveTests.txt
@@ -0,0 +1,170 @@
+using System.Linq;
+using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
+using HotChocolate.Types;
+using Snapshooter.Xunit;
+
+namespace HotChocolate.ApolloFederation.Directives;
+
+public class PolicyDirectiveTests : FederationTypesTestBase
+{
+ [Fact]
+ public void PolicyDirectives_GetParsedCorrectly_SchemaFirst()
+ {
+ // arrange
+ Snapshot.FullName();
+
+ var schema = SchemaBuilder.New()
+ .AddDocumentFromString(
+ """
+ type Review @policy(policies: "p3") {
+ id: Int!
+ }
+
+ type Query {
+ someField(a: Int): Review @policy(policies: [["p1", "p1_1"], ["p2"]])
+ }
+ """)
+ .AddDirectiveType()
+ .Use(_ => _ => default)
+ .Create();
+
+ CheckReviewType(schema);
+ CheckQueryType(schema);
+
+ schema.ToString().MatchSnapshot();
+ }
+
+ [Fact]
+ public void PolicyDirectives_GetAddedCorrectly_Annotations()
+ {
+ // arrange
+ Snapshot.FullName();
+
+ var schema = SchemaBuilder.New()
+ .AddApolloFederation()
+ .AddQueryType()
+ .Create();
+
+ CheckReviewType(schema);
+ CheckQueryType(schema);
+
+ schema.ToString().MatchSnapshot();
+ }
+
+ [Fact]
+ public void PolicyDirectives_GetAddedCorrectly_CodeFirst()
+ {
+ // arrange
+ Snapshot.FullName();
+
+ var reviewType = new ObjectType(d =>
+ {
+ d.Name(nameof(Review));
+ d.Key("id");
+ {
+ var id = d.Field("id");
+ id.Type>();
+ id.Resolve(_ => default);
+ }
+ });
+ var queryType = new ObjectType(d =>
+ {
+ d.Name(nameof(Query));
+ d.Field("someField")
+ .Type(new NonNullType(reviewType))
+ .Policy([["p1", "p1_1"], ["p2"]])
+ .Resolve(_ => default);
+ });
+
+ var schema = SchemaBuilder.New()
+ .AddApolloFederation()
+ .AddType(reviewType)
+ .AddQueryType(queryType)
+ .Create();
+
+ CheckReviewType(schema);
+ CheckQueryType(schema);
+
+ schema.ToString().MatchSnapshot();
+ }
+
+ private static string[][] GetSinglePoliciesArgument(IDirectiveCollection directives)
+ {
+ foreach (var directive in directives)
+ {
+ if (directive.Type.Name != WellKnownTypeNames.PolicyDirective)
+ {
+ continue;
+ }
+
+ var argument = directive.AsSyntaxNode().Arguments.Single();
+ return PolicyParsingHelper.ParseNode(argument.Value);
+ }
+
+ Assert.True(false, "No policy directive found.");
+ return null!;
+ }
+
+ private static void CheckQueryType(ISchema schema)
+ {
+ var testType = schema.GetType(nameof(Query));
+ var directives = testType
+ .Fields
+ .Single(f => f.Name == "someField")
+ .Directives;
+ var policyCollection = GetSinglePoliciesArgument(directives);
+ Assert.Collection(policyCollection,
+ t1 =>
+ {
+ Assert.Equal("p1", t1[0]);
+ Assert.Equal("p1_1", t1[1]);
+ },
+ t2 =>
+ {
+ Assert.Equal("p2", t2[0]);
+ });
+ }
+
+ private static void CheckReviewType(ISchema schema)
+ {
+ var testType = schema.GetType(nameof(Review));
+ var directives = testType.Directives;
+ var policyCollection = GetSinglePoliciesArgument(directives);
+ Assert.Collection(policyCollection,
+ t1 =>
+ {
+ Assert.Equal("p3", t1[0]);
+ });
+ }
+
+ [Fact]
+ public void PolicyDirective_GetsAddedCorrectly_Annotations()
+ {
+ // arrange
+ Snapshot.FullName();
+
+ var schema = SchemaBuilder.New()
+ .AddApolloFederation()
+ .AddQueryType()
+ .Create();
+
+ // act
+ CheckQueryType(schema);
+
+ schema.ToString().MatchSnapshot();
+ }
+
+ public class Query
+ {
+ [Policy("p1,p1_1", "p2")]
+ public Review SomeField(int id) => default!;
+ }
+
+ [Key("id")]
+ [Policy("p3")]
+ public class Review
+ {
+ public int Id { get; set; }
+ }
+}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.txt
similarity index 96%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.txt
index 5e38f963f45..68706ad24b5 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/ProvidesDirectiveTests.txt
@@ -1,5 +1,6 @@
using System.Linq;
using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Snapshooter.Xunit;
@@ -24,7 +25,7 @@ public void AddProvidesDirective_EnsureAvailableInSchema()
Assert.IsType(directive);
Assert.Equal(WellKnownTypeNames.Provides, directive!.Name);
Assert.Single(directive.Arguments);
- AssertDirectiveHasFieldsArgument(directive);
+ AssertDirectiveHasFieldsArgument(directive.Arguments);
Assert.Equal(DirectiveLocation.FieldDefinition, directive.Locations);
}
@@ -89,7 +90,7 @@ public void AnnotateProvidesToFieldCodeFirst()
{
o.Name("Review").Key("id");
o.Field("id").Type();
- o.Field("product").Provides("name").Type("Product");
+ ProvidesDescriptorExtensions.Provides(o.Field("product"), "name").Type("Product");
}))
.AddQueryType(
new ObjectType(o =>
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.txt
similarity index 98%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.txt
index 33614a89332..41753873fc8 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/RequiresDirectiveTests.txt
@@ -1,5 +1,6 @@
using System.Linq;
using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
using HotChocolate.Utilities;
using Snapshooter.Xunit;
@@ -24,7 +25,7 @@ public void AddRequiresDirective_EnsureAvailableInSchema()
Assert.IsType(directive);
Assert.Equal(WellKnownTypeNames.Requires, directive!.Name);
Assert.Single(directive.Arguments);
- AssertDirectiveHasFieldsArgument(directive);
+ AssertDirectiveHasFieldsArgument(directive.Arguments);
Assert.Equal(DirectiveLocation.FieldDefinition, directive.Locations);
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ComposeDirectiveTests.TestServiceTypeEmptyQueryTypePureCodeFirst.graphql b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ComposeDirectiveTests.TestServiceTypeEmptyQueryTypePureCodeFirst.graphql
new file mode 100644
index 00000000000..aaac69bb9ad
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ComposeDirectiveTests.TestServiceTypeEmptyQueryTypePureCodeFirst.graphql
@@ -0,0 +1,37 @@
+schema @composeDirective(name: "custom") @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@composeDirective", "@key", "FieldSet" ]) @link(url: "https:\/\/specs.custom.dev\/custom\/v1.0", import: [ "@custom" ]) {
+ query: Query
+}
+
+type Address @key(fields: "field") {
+ field: String! @custom
+}
+
+type Query {
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
+}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Address
+
+"Marks underlying custom directive to be included in the Supergraph schema."
+directive @composeDirective(name: String!) repeatable on SCHEMA
+
+directive @custom on FIELD_DEFINITION
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+"Object representation of @link directive."
+directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." import: [String!]) repeatable on SCHEMA
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
\ No newline at end of file
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap
index b0cc7d832bd..9d7f99068a8 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ExternalDirectiveTests.AnnotateExternalToTypeFieldPureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: Query
}
@@ -21,11 +21,31 @@ type _Service {
"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
union _Entity = User
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users."
+directive @authenticated on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Marks underlying custom directive to be included in the Supergraph schema."
+directive @composeDirective(name: String!) on SCHEMA
+
+"Directive to indicate that marks target object as extending part of the federated schema."
+directive @extends on OBJECT | INTERFACE
+
"Directive to indicate that a field is owned by another service, for example via Apollo federation."
directive @external on FIELD_DEFINITION
+"Marks location within schema as inaccessible from the GraphQL Gateway"
+directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Provides meta information to the router that this entity type is an interface in the supergraph."
+directive @interfaceObject on OBJECT
+
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap
index b46237c9d8c..5651bda1fd7 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributePureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: QueryOfTestTypePropertyDirective
}
@@ -21,7 +21,12 @@ type _Service {
union _Entity = TestTypePropertyDirective
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap
index 7a186e234c3..0a3328af805 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToClassAttributesPureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: QueryOfTestTypePropertyDirectives
}
@@ -22,7 +22,12 @@ type _Service {
union _Entity = TestTypePropertyDirectives
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap
index 27bbff10a27..9263b11e475 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeCodeFirst.snap
@@ -12,7 +12,7 @@ type TestType @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Scalar representing a set of fields."
scalar _FieldSet
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap
index a4e0c8f88fb..a9cb5e239eb 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypePureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: QueryOfTestTypeClassDirective
}
@@ -21,7 +21,12 @@ type _Service {
union _Entity = TestTypeClassDirective
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
scalar _Any
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap
index f21731e2c65..ffc85e34d4d 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/KeyDirectiveTests.AnnotateKeyToObjectTypeSchemaFirst.snap
@@ -16,7 +16,7 @@ type TestType @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Scalar representing a set of fields."
scalar _FieldSet
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirective_GetsAddedCorrectly_Annotations.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirective_GetsAddedCorrectly_Annotations.snap
new file mode 100644
index 00000000000..a6badde20c5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirective_GetsAddedCorrectly_Annotations.snap
@@ -0,0 +1,76 @@
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy", "@policy" ]) {
+ query: Query
+}
+
+type Query {
+ someField(id: Int!): Review! @policy(policies: [ [ "p1", "p1_1" ], [ "p2" ] ])
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
+}
+
+type Review @key(fields: "id") @policy(policies: [ [ "p3" ] ]) {
+ id: Int!
+}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Review
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users."
+directive @authenticated on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Marks underlying custom directive to be included in the Supergraph schema."
+directive @composeDirective(name: String!) on SCHEMA
+
+"Directive to indicate that marks target object as extending part of the federated schema."
+directive @extends on OBJECT | INTERFACE
+
+"Directive to indicate that a field is owned by another service, for example via Apollo federation."
+directive @external on FIELD_DEFINITION
+
+"Marks location within schema as inaccessible from the GraphQL Gateway"
+directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Provides meta information to the router that this entity type is an interface in the supergraph."
+directive @interfaceObject on OBJECT
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor."
+directive @policy(policies: policyCollection!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Used to annotate the required input fieldset from a base type for a resolver."
+directive @requires(fields: FieldSet!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes."
+directive @requiresScopes(scopes: [[Scope!]!]!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Indicates that given object and\/or field can be resolved by multiple subgraphs."
+directive @shareable repeatable on OBJECT | FIELD_DEFINITION
+
+"Allows users to annotate fields and types with additional metadata information."
+directive @tag(name: String!) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"Scalar representing a JWT scope"
+scalar Scope
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
+
+scalar policyCollection
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_Annotations.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_Annotations.snap
new file mode 100644
index 00000000000..a6badde20c5
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_Annotations.snap
@@ -0,0 +1,76 @@
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy", "@policy" ]) {
+ query: Query
+}
+
+type Query {
+ someField(id: Int!): Review! @policy(policies: [ [ "p1", "p1_1" ], [ "p2" ] ])
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
+}
+
+type Review @key(fields: "id") @policy(policies: [ [ "p3" ] ]) {
+ id: Int!
+}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Review
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users."
+directive @authenticated on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Marks underlying custom directive to be included in the Supergraph schema."
+directive @composeDirective(name: String!) on SCHEMA
+
+"Directive to indicate that marks target object as extending part of the federated schema."
+directive @extends on OBJECT | INTERFACE
+
+"Directive to indicate that a field is owned by another service, for example via Apollo federation."
+directive @external on FIELD_DEFINITION
+
+"Marks location within schema as inaccessible from the GraphQL Gateway"
+directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Provides meta information to the router that this entity type is an interface in the supergraph."
+directive @interfaceObject on OBJECT
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor."
+directive @policy(policies: policyCollection!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Used to annotate the required input fieldset from a base type for a resolver."
+directive @requires(fields: FieldSet!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes."
+directive @requiresScopes(scopes: [[Scope!]!]!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Indicates that given object and\/or field can be resolved by multiple subgraphs."
+directive @shareable repeatable on OBJECT | FIELD_DEFINITION
+
+"Allows users to annotate fields and types with additional metadata information."
+directive @tag(name: String!) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"Scalar representing a JWT scope"
+scalar Scope
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
+
+scalar policyCollection
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_CodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_CodeFirst.snap
new file mode 100644
index 00000000000..fcf6b479077
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetAddedCorrectly_CodeFirst.snap
@@ -0,0 +1,76 @@
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy", "@policy" ]) {
+ query: Query
+}
+
+type Query {
+ someField: Review! @policy(policies: [ [ "p1", "p1_1" ], [ "p2" ] ])
+ _service: _Service!
+ _entities(representations: [_Any!]!): [_Entity]!
+}
+
+type Review @key(fields: "id") @key(fields: "id") @policy(policies: [ [ "p3" ] ]) {
+ id: Int!
+}
+
+"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."
+type _Service {
+ sdl: String!
+}
+
+"Union of all types that key directive applied. This information is needed by the Apollo federation gateway."
+union _Entity = Review
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users."
+directive @authenticated on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Marks underlying custom directive to be included in the Supergraph schema."
+directive @composeDirective(name: String!) on SCHEMA
+
+"Directive to indicate that marks target object as extending part of the federated schema."
+directive @extends on OBJECT | INTERFACE
+
+"Directive to indicate that a field is owned by another service, for example via Apollo federation."
+directive @external on FIELD_DEFINITION
+
+"Marks location within schema as inaccessible from the GraphQL Gateway"
+directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Provides meta information to the router that this entity type is an interface in the supergraph."
+directive @interfaceObject on OBJECT
+
+"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
+directive @key(fields: FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor."
+directive @policy(policies: policyCollection!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
+directive @provides(fields: FieldSet!) on FIELD_DEFINITION
+
+"Used to annotate the required input fieldset from a base type for a resolver."
+directive @requires(fields: FieldSet!) on FIELD_DEFINITION
+
+"Indicates to composition that the target element is accessible only to the authenticated supergraph users with the appropriate JWT scopes."
+directive @requiresScopes(scopes: [[Scope!]!]!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+"Indicates that given object and\/or field can be resolved by multiple subgraphs."
+directive @shareable repeatable on OBJECT | FIELD_DEFINITION
+
+"Allows users to annotate fields and types with additional metadata information."
+directive @tag(name: String!) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+"Scalar representing a set of fields."
+scalar FieldSet
+
+"Scalar representing a JWT scope"
+scalar Scope
+
+"The _Any scalar is used to pass representations of entities from external services into the root _entities field for execution. Validation of the _Any scalar is done by matching the __typename and @external fields defined in the schema."
+scalar _Any
+
+scalar policyCollection
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetParsedCorrectly_SchemaFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetParsedCorrectly_SchemaFirst.snap
new file mode 100644
index 00000000000..6e06413779f
--- /dev/null
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/PolicyDirectiveTests.PolicyDirectives_GetParsedCorrectly_SchemaFirst.snap
@@ -0,0 +1,18 @@
+schema {
+ query: Query
+}
+
+type Query {
+ someField(a: Int): Review @policy(policies: [ [ "p1", "p1_1" ], [ "p2" ] ])
+}
+
+type Review @policy(policies: "p3") {
+ id: Int!
+}
+
+"Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor."
+directive @policy(policies: policyCollection!) on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | ENUM
+
+directive @tag(name: String!) repeatable on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
+
+scalar policyCollection
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
index 773a0d8fb62..5e5100945b1 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: Query
}
@@ -26,7 +26,12 @@ type _Service {
union _Entity = Review
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
index 4d70d0f5530..be6c26570ec 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
@@ -16,7 +16,7 @@ type Review @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
index 3bf8791f661..c2f1b02f591 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ProvidesDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
@@ -16,7 +16,7 @@ type Review @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Used to annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the federation gateway."
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
index e3d45c77604..9a76dd08493 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToClassAttributePureCodeFirst.snap
@@ -1,4 +1,4 @@
-schema {
+schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@extends", "@external", "@key", "@inaccessible", "@override", "@provides", "@requires", "@shareable", "@tag", "FieldSet", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresPolicy" ]) {
query: Query
}
@@ -26,7 +26,12 @@ type _Service {
union _Entity = Review
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
+
+directive @link(url: String! import: [String!]) repeatable on SCHEMA
+
+"Overrides fields resolution logic from other subgraph. Used for migrating fields from one subgraph to another."
+directive @override(from: String!) on FIELD_DEFINITION
"Used to annotate the required input fieldset from a base type for a resolver."
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
index f7b25e9fc4d..30265246dab 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldCodeFirst.snap
@@ -16,7 +16,7 @@ type Review @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Used to annotate the required input fieldset from a base type for a resolver."
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
index 3801171d118..7220e19a1ac 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/RequiresDirectiveTests.AnnotateProvidesToFieldSchemaFirst.snap
@@ -16,7 +16,7 @@ type Review @key(fields: "id") {
}
"Used to indicate a combination of fields that can be used to uniquely identify and fetch an object or interface."
-directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
+directive @key(fields: _FieldSet! resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
"Used to annotate the required input fieldset from a base type for a resolver."
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.txt
similarity index 90%
rename from src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.cs
rename to src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.txt
index 586de6eb967..f59fa4e6080 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntitiesResolverTests.txt
@@ -3,9 +3,9 @@
using System.Threading;
using System.Threading.Tasks;
using GreenDonut;
-using HotChocolate.ApolloFederation.Helpers;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Language;
-using Xunit;
using static HotChocolate.ApolloFederation.TestHelper;
namespace HotChocolate.ApolloFederation;
@@ -13,7 +13,7 @@ namespace HotChocolate.ApolloFederation;
public class EntitiesResolverTests
{
[Fact]
- public async void TestResolveViaForeignServiceType()
+ public async Task TestResolveViaForeignServiceType()
{
// arrange
var schema = SchemaBuilder.New()
@@ -24,17 +24,17 @@ public async void TestResolveViaForeignServiceType()
var context = CreateResolverContext(schema);
// act
- var representations = new List
- {
- new("ForeignType",
- new ObjectValueNode(
- new ObjectFieldNode("id", "1"),
- new ObjectFieldNode("someExternalField", "someExternalField"))),
- };
-
- // assert
+ var representations = RepresentationsOf(
+ nameof(ForeignType),
+ new
+ {
+ id = "1",
+ someExternalField = "someExternalField",
+ });
var result =
await EntitiesResolver.ResolveAsync(schema, representations, context);
+
+ // assert
var obj = Assert.IsType(result[0]);
Assert.Equal("1", obj.Id);
Assert.Equal("someExternalField", obj.SomeExternalField);
@@ -113,12 +113,11 @@ public async void TestResolveViaEntityResolver_WithDataLoader()
mock.Setup(c => c.Service()).Returns(dataLoader);
});
- var representations = new List
- {
- new("FederatedType", new ObjectValueNode(new ObjectFieldNode("Id", "1"))),
- new("FederatedType", new ObjectValueNode(new ObjectFieldNode("Id", "2"))),
- new("FederatedType", new ObjectValueNode(new ObjectFieldNode("Id", "3"))),
- };
+ var representations = RepresentationsOf(
+ nameof(FederatedType),
+ new { Id = "1" },
+ new { Id = "2" },
+ new { Id = "3" });
// act
var resultTask = EntitiesResolver.ResolveAsync(schema, representations, context);
@@ -219,7 +218,10 @@ public async Task TestDetailFieldResolver_Optional()
new ObjectValueNode(new[]
{
new ObjectFieldNode("detail",
- new ObjectValueNode(new[] { new ObjectFieldNode("id", "testId") })),
+ new ObjectValueNode(new[]
+ {
+ new ObjectFieldNode("id", "testId"),
+ })),
})),
};
@@ -359,7 +361,15 @@ public class FederatedTypeWithRequiredDetail
public FederatedTypeDetail Detail { get; set; } = default!;
[ReferenceResolver]
- public static FederatedTypeWithRequiredDetail ReferenceResolver([Map("detail.id")] string detailId) => new() { Id = detailId, Detail = new FederatedTypeDetail { Id = detailId } };
+ public static FederatedTypeWithRequiredDetail ReferenceResolver([Map("detail.id")] string detailId)
+ => new()
+ {
+ Id = detailId,
+ Detail = new FederatedTypeDetail
+ {
+ Id = detailId,
+ },
+ };
}
public class FederatedTypeWithOptionalDetail
@@ -369,8 +379,15 @@ public class FederatedTypeWithOptionalDetail
public FederatedTypeDetail? Detail { get; set; } = default!;
[ReferenceResolver]
- public static FederatedTypeWithOptionalDetail ReferenceResolver([Map("detail.id")] string detailId) => new() { Id = detailId, Detail = new FederatedTypeDetail { Id = detailId } };
-
+ public static FederatedTypeWithOptionalDetail ReferenceResolver([Map("detail.id")] string detailId)
+ => new()
+ {
+ Id = detailId,
+ Detail = new FederatedTypeDetail
+ {
+ Id = detailId,
+ },
+ };
}
public class FederatedTypeDetail
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntityTypeTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntityTypeTests.cs
index 9e198cd1b67..728001e4d04 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntityTypeTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/EntityTypeTests.cs
@@ -1,4 +1,7 @@
-using Xunit;
+using System.Threading.Tasks;
+using HotChocolate.ApolloFederation.Types;
+using HotChocolate.Execution;
+using Microsoft.Extensions.DependencyInjection;
using static HotChocolate.ApolloFederation.Properties.FederationResources;
namespace HotChocolate.ApolloFederation;
@@ -6,143 +9,51 @@ namespace HotChocolate.ApolloFederation;
public class EntityTypeTests
{
[Fact]
- public void TestEntityTypeSchemaFirstSingleKey()
+ public async Task TestEntityTypeCodeFirstNoEntities_ShouldThrow()
{
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(
- @"
- type Query {
- user(a: Int!): User
- }
-
- type Review @key(fields: ""id"") {
- id: Int!
- author: User
- }
-
- type User @key(fields: ""id"") {
- id: Int!
- idCode: String!
- reviews: [Review!]!
- }
- "
- )
- .Use(_ => _ => default)
- .Create();
-
- // act
- var entityType = schema.GetType("_Entity");
-
- // assert
- Assert.Collection(entityType.Types.Values,
- t => Assert.Equal("Review", t.Name),
- t => Assert.Equal("User", t.Name));
- }
-
- [Fact]
- public void TestEntityTypeSchemaFirstMultiKey()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(
- @"
- type Query {
- user(a: Int!): User
- }
-
- type User @key(fields: ""id idCode"") {
- id: Int!
- idCode: String!
- }
- "
- )
- .Use(_ => _ => default)
- .Create();
-
- // act
- var entityType = schema.GetType("_Entity");
-
- // assert
- Assert.Collection(entityType.Types.Values, t => Assert.Equal("User", t.Name));
- }
-
- [Fact]
- public void TestEntityTypeSchemaFirstNestedKey()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(
- @"
- type Query {
- user(a: Int!): User
- }
-
- type User @key(fields: ""id address { matchCode }"") {
- id: Int!
- address: Address
- }
-
- type Address {
- matchCode: String!
- }
- ")
- .Use(_ => _ => default)
- .Create();
-
- // act
- var entityType = schema.GetType("_Entity");
-
- // assert
- Assert.Collection(entityType.Types.Values, t => Assert.Equal("User", t.Name));
- }
-
- [Fact]
- public void TestEntityTypeCodeFirstNoEntities_ShouldThrow()
- {
- void CreateSchema()
+ async Task CreateSchema()
{
// arrange
- SchemaBuilder.New()
+ await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType>()
- .Create();
+ .BuildSchemaAsync();
}
- var exception = Assert.Throws(CreateSchema);
+ var exception = await Assert.ThrowsAsync(CreateSchema);
Assert.Contains(ThrowHelper_EntityType_NoEntities, exception.Message);
}
[Fact]
- public void TestEntityTypeCodeFirstClassKeyAttributeSingleKey()
+ public async Task TestEntityTypeCodeFirstClassKeyAttributeSingleKey()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType>()
- .Create();
+ .BuildSchemaAsync();
// act
- var entityType = schema.GetType("_Entity");
+ var entityType = schema.GetType<_EntityType>("_Entity");
// assert
Assert.Collection(entityType.Types.Values, t => Assert.Equal("Review", t.Name));
}
[Fact]
- public void TestEntityTypeCodeFirstClassKeyAttributeMultiKey()
+ public async Task TestEntityTypeCodeFirstClassKeyAttributeMultiKey()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType>()
- .Create();
+ .BuildSchemaAsync();
// act
- var entityType = schema.GetType("_Entity");
+ var entityType = schema.GetType<_EntityType>("_Entity");
// assert
Assert.Collection(
@@ -152,16 +63,17 @@ public void TestEntityTypeCodeFirstClassKeyAttributeMultiKey()
}
[Fact]
- public void TestEntityTypeCodeFirstPropertyKeyAttributes()
+ public async Task TestEntityTypeCodeFirstPropertyKeyAttributes()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType>()
- .Create();
+ .BuildSchemaAsync();
// act
- var entityType = schema.GetType("_Entity");
+ var entityType = schema.GetType<_EntityType>("_Entity");
// assert
Assert.Collection(
@@ -170,58 +82,59 @@ public void TestEntityTypeCodeFirstPropertyKeyAttributes()
}
[Fact]
- public void TestEntityTypeCodeFirstClassKeyAttributeNestedKey()
+ public async Task TestEntityTypeCodeFirstClassKeyAttributeNestedKey()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType>()
- .Create();
+ .BuildSchemaAsync();
// act
- var entityType = schema.GetType("_Entity");
+ var entityType = schema.GetType<_EntityType>("_Entity");
// assert
Assert.Collection(entityType.Types.Values,
t => Assert.Equal("UserWithNestedKeyClassAttribute", t.Name));
}
-}
-
-public class Query
-{
- public T GetEntity(int id) => default!;
-}
+
+ public sealed class Query
+ {
+ public T GetEntity(int id) => default!;
+ }
-[Key("id idCode")]
-public class UserWithClassAttribute
-{
- public int Id { get; set; }
- public string IdCode { get; set; } = default!;
- public Review[] Reviews { get; set; } = default!;
-}
+ [Key("id idCode")]
+ public sealed class UserWithClassAttribute
+ {
+ public int Id { get; set; }
+ public string IdCode { get; set; } = default!;
+ public Review[] Reviews { get; set; } = default!;
+ }
-public class UserWithPropertyAttributes
-{
- [Key]
- public int Id { get; set; }
- [Key]
- public string IdCode { get; set; } = default!;
-}
+ public sealed class UserWithPropertyAttributes
+ {
+ [Key]
+ public int Id { get; set; }
+ [Key]
+ public string IdCode { get; set; } = default!;
+ }
-[Key("id address { matchCode }")]
-public class UserWithNestedKeyClassAttribute
-{
- public int Id { get; set; }
- public Address Address { get; set; } = default!;
-}
+ [Key("id address { matchCode }")]
+ public sealed class UserWithNestedKeyClassAttribute
+ {
+ public int Id { get; set; }
+ public Address Address { get; set; } = default!;
+ }
-public class Address
-{
- public string MatchCode { get; set; } = default!;
-}
+ public sealed class Address
+ {
+ public string MatchCode { get; set; } = default!;
+ }
-[Key("id")]
-public class Review
-{
- public int Id { get; set; }
+ [Key("id")]
+ public sealed class Review
+ {
+ public int Id { get; set; }
+ }
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationSchemaPrinterTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationSchemaPrinterTests.cs
deleted file mode 100644
index 6db0f1a47e2..00000000000
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationSchemaPrinterTests.cs
+++ /dev/null
@@ -1,323 +0,0 @@
-using System;
-using System.Reflection;
-using HotChocolate.Types;
-using HotChocolate.Types.Descriptors;
-using Snapshooter.Xunit;
-using Xunit;
-
-namespace HotChocolate.ApolloFederation;
-
-public class FederationSchemaPrinterTests
-{
- [Fact]
- public void TestFederationPrinter_ShouldThrow()
- {
- // arrange
- ISchema? schema = null;
- void Action() => FederationSchemaPrinter.Print(schema!);
-
- // act
- // assert
- Assert.Throws(Action);
- }
-
- [Fact]
- public void TestFederationPrinterApolloDirectivesSchemaFirst()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(
- @"type TestType @key(fields: ""id"") {
- id: Int!
- name: String!
- }
-
- type Query {
- someField(a: Int): TestType
- }")
- .Use(_ => _ => default)
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void TestFederationPrinterSchemaFirst()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(@"
- type TestType @key(fields: ""id"") {
- id: Int!
- name: String!
- enum: SomeEnum
- }
-
- type TestTypeTwo {
- id: Int!
- }
-
- interface iTestType @key(fields: ""id"") {
- id: Int!
- external: String! @external
- }
-
- union TestTypes = TestType | TestTypeTwo
-
- enum SomeEnum {
- FOO
- BAR
- }
-
- input SomeInput {
- name: String!
- description: String
- someEnum: SomeEnum
- }
-
- type Mutation {
- doSomething(input: SomeInput): Boolean
- }
-
- type Query implements iQuery {
- someField(a: Int): TestType
- }
-
- interface iQuery {
- someField(a: Int): TestType
- }
- ")
- .Use(_ => _ => default)
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void TestFederationPrinterSchemaFirst_With_DateTime()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddDocumentFromString(@"
- type TestType @key(fields: ""id"") {
- id: Int!
- name: String!
- enum: SomeEnum
- }
-
- type TestTypeTwo {
- id: Int!
- }
-
- interface iTestType @key(fields: ""id"") {
- id: Int!
- external: String! @external
- }
-
- union TestTypes = TestType | TestTypeTwo
-
- enum SomeEnum {
- FOO
- BAR
- }
-
- input SomeInput {
- name: String!
- description: String
- someEnum: SomeEnum
- time: DateTime
- }
-
- type Mutation {
- doSomething(input: SomeInput): Boolean
- }
-
- type Query implements iQuery {
- someField(a: Int): TestType
- }
-
- interface iQuery {
- someField(a: Int): TestType
- }
-
- scalar DateTime
- ")
- .Use(_ => _ => default)
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void TestFederationPrinterApolloDirectivesPureCodeFirst()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddQueryType>()
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void TestFederationPrinterTypeExtensionPureCodeFirst()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddApolloFederation()
- .AddQueryType>()
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void CustomDirective_IsPublic()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddQueryType()
- .AddDirectiveType(new CustomDirectiveType(true))
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- [Fact]
- public void CustomDirective_IsInternal()
- {
- // arrange
- var schema = SchemaBuilder.New()
- .AddQueryType()
- .AddDirectiveType(new CustomDirectiveType(false))
- .Create();
-
- // act
- // assert
- FederationSchemaPrinter.Print(schema).MatchSnapshot();
- }
-
- public class QueryRoot
- {
- public T GetEntity(int id) => default!;
- }
-
- public class User
- {
- [Key]
- public int Id { get; set; }
- [External]
- public string IdCode { get; set; } = default!;
- [Requires("idCode")]
- public string IdCodeShort { get; set; } = default!;
- [Provides("zipcode")]
- public Address Address { get; set; } = default!;
- }
-
- public class Address
- {
- [External]
- public string Zipcode { get; set; } = default!;
- }
-
- [ExtendServiceType]
- public class Product
- {
- [Key]
- public string Upc { get; set; } = default!;
- }
-
- public class QueryWithDirective : ObjectType
- {
- protected override void Configure(IObjectTypeDescriptor descriptor)
- {
- descriptor
- .Name("Query")
- .Field("foo")
- .Resolve("bar")
- .Directive("custom");
-
- descriptor
- .Field("deprecated1")
- .Resolve("abc")
- .Deprecated("deprecated")
- .Type>();
-
- descriptor
- .Field("deprecated2")
- .Resolve("abc")
- .Deprecated("deprecated")
- .Directive("custom")
- .Type>();
- }
- }
-
- public class CustomDirectiveType : DirectiveType
- {
- private readonly bool _isPublic;
-
- public CustomDirectiveType(bool isPublic)
- {
- _isPublic = isPublic;
- }
-
- protected override void Configure(IDirectiveTypeDescriptor descriptor)
- {
- descriptor
- .Name("custom")
- .Location(DirectiveLocation.FieldDefinition)
- .Location(DirectiveLocation.EnumValue);
-
- if (_isPublic)
- {
- descriptor.Public();
- }
- else
- {
- descriptor.Internal();
- }
- }
- }
-
- public enum EnumWithDeprecatedValue
- {
- [Obsolete]
- Deprecated1,
-
- [CustomDirective]
- [Obsolete]
- Deprecated2,
-
- Active,
- }
-
- public class CustomDirectiveAttribute : DescriptorAttribute
- {
- protected override void TryConfigure(
- IDescriptorContext context,
- IDescriptor descriptor,
- ICustomAttributeProvider element)
- {
- if (descriptor is EnumValueDescriptor enumValue)
- {
- enumValue.Directive("custom");
- }
- }
- }
-}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationTypesTestBase.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationTypesTestBase.cs
index 83091f8501f..b8ad67b2c82 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationTypesTestBase.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FederationTypesTestBase.cs
@@ -1,10 +1,11 @@
using System;
+using System.Collections.Generic;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Types;
-using Xunit;
namespace HotChocolate.ApolloFederation;
-public class FederationTypesTestBase
+public abstract class FederationTypesTestBase
{
protected ISchema CreateSchema(Action configure)
{
@@ -23,10 +24,10 @@ protected ISchema CreateSchema(Action configure)
return builder.Create();
}
- protected void AssertDirectiveHasFieldsArgument(DirectiveType directive)
+ protected void AssertDirectiveHasFieldsArgument(IEnumerable directiveArguments)
{
Assert.Collection(
- directive.Arguments,
+ directiveArguments,
t =>
{
Assert.Equal("fields", t.Name);
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FieldSetTypeTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FieldSetTypeTests.cs
index e980004e98e..9e4130a489a 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FieldSetTypeTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/FieldSetTypeTests.cs
@@ -1,7 +1,7 @@
-using HotChocolate.ApolloFederation.Constants;
+using HotChocolate.ApolloFederation.Types;
using HotChocolate.Language;
using HotChocolate.Types;
-using Xunit;
+using static HotChocolate.ApolloFederation.FederationTypeNames;
using static HotChocolate.Language.Utf8GraphQLParser;
namespace HotChocolate.ApolloFederation;
@@ -16,7 +16,7 @@ public void Ensure_Type_Name_Is_Correct()
var type = new FieldSetType();
// assert
- Assert.Equal(WellKnownTypeNames.FieldSet, type.Name);
+ Assert.Equal(FieldSetType_Name, type.Name);
}
[Fact]
@@ -111,13 +111,14 @@ public void Serialize()
{
// arrange
var type = new FieldSetType();
- var selectionSet = Syntax.ParseSelectionSet("{ a b c d e(d: $b) }");
+ const string selection = "a b c d e(d: $b)";
+ var selectionSet = Syntax.ParseSelectionSet(Braces(selection));
// act
var serialized = type.Serialize(selectionSet);
// assert
- Assert.Equal("a b c d e(d: $b)", serialized);
+ Assert.Equal(selection, serialized);
}
[Fact]
@@ -138,14 +139,15 @@ public void TrySerialize()
{
// arrange
var type = new FieldSetType();
- var selectionSet = Syntax.ParseSelectionSet("{ a b c d e(d: $b) }");
+ const string selection = "a b c d e(d: $b)";
+ var selectionSet = Syntax.ParseSelectionSet(Braces(selection));
// act
var success = type.TrySerialize(selectionSet, out var serialized);
// assert
Assert.True(success);
- Assert.Equal("a b c d e(d: $b)", serialized);
+ Assert.Equal(selection, serialized);
}
[Fact]
@@ -162,19 +164,22 @@ public void TrySerialize_Invalid_Format()
Assert.Null(serialized);
}
+ private static string Braces(string s) => $"{{ {s} }}";
+
[Fact]
public void ParseValue()
{
// arrange
var type = new FieldSetType();
- var selectionSet = Syntax.ParseSelectionSet("{ a b c d e(d: $b) }");
+ const string selection = "a b c d e(d: $b)";
+ var selectionSet = Syntax.ParseSelectionSet(Braces(selection));
// act
var valueSyntax = type.ParseValue(selectionSet);
// assert
Assert.Equal(
- "a b c d e(d: $b)",
+ selection,
Assert.IsType(valueSyntax).Value);
}
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj
index 3ea8e790156..3f5951db112 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/HotChocolate.ApolloFederation.Tests.csproj
@@ -6,8 +6,13 @@
+
+
+
+
+
diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ReferenceResolverAttributeTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ReferenceResolverAttributeTests.cs
index 154f6932f4c..bc099cc2552 100644
--- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ReferenceResolverAttributeTests.cs
+++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ReferenceResolverAttributeTests.cs
@@ -1,10 +1,13 @@
using System;
using System.Threading.Tasks;
+using HotChocolate.ApolloFederation.Resolvers;
+using HotChocolate.ApolloFederation.Types;
+using HotChocolate.Execution;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
-using Xunit;
-using static HotChocolate.ApolloFederation.Constants.WellKnownContextData;
+using Microsoft.Extensions.DependencyInjection;
+using static HotChocolate.ApolloFederation.FederationContextData;
using static HotChocolate.ApolloFederation.TestHelper;
namespace HotChocolate.ApolloFederation;
@@ -15,10 +18,11 @@ public class ReferenceResolverAttributeTests
public async void InClassRefResolver_PureCodeFirst()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
// act
var type = schema.GetType(nameof(InClassRefResolver));
@@ -34,10 +38,11 @@ public async void InClassRefResolver_PureCodeFirst()
public async void ExternalRefResolver_PureCodeFirst()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
// act
var type = schema.GetType(nameof(ExternalRefResolver));
@@ -54,11 +59,12 @@ public async void ExternalRefResolver_PureCodeFirst()
public async void SingleKey_CompiledResolver()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
-
+ .BuildSchemaAsync();
+
// act
var type = schema.GetType(nameof(ExternalSingleKeyResolver));
@@ -72,10 +78,11 @@ public async void SingleKey_CompiledResolver()
public async void ExternalFields_Set()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
// act
var type = schema.GetType(nameof(ExternalFields));
@@ -93,10 +100,11 @@ public async void ExternalFields_Set()
public async void ExternalFields_Not_Set()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
// act
var type = schema.GetType(nameof(ExternalFields));
@@ -112,10 +120,11 @@ public async void ExternalFields_Not_Set()
public async void MultiKey_CompiledResolver()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
var type = schema.GetType(nameof(ExternalMultiKeyResolver));
@@ -132,11 +141,12 @@ public async void MultiKey_CompiledResolver()
public async void ExternalRefResolver_RenamedMethod_PureCodeFirst()
{
// arrange
- var schema = SchemaBuilder.New()
+ var schema = await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
-
+ .BuildSchemaAsync();
+
// act
var type = schema.GetType(nameof(ExternalRefResolverRenamedMethod));
@@ -151,34 +161,36 @@ public async void ExternalRefResolver_RenamedMethod_PureCodeFirst()
public void InClassRefResolver_RenamedMethod_InvalidName_PureCodeFirst()
{
// arrange
- void SchemaCreation()
+ async Task SchemaCreation()
{
- SchemaBuilder.New()
+ await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
}
// act
// assert
- Assert.Throws((Action)SchemaCreation);
+ Assert.ThrowsAsync(SchemaCreation);
}
[Fact]
public void ExternalRefResolver_RenamedMethod_InvalidName_PureCodeFirst()
{
// arrange
- void SchemaCreation()
+ async Task SchemaCreation()
{
- SchemaBuilder.New()
+ await new ServiceCollection()
+ .AddGraphQL()
.AddApolloFederation()
.AddQueryType()
- .Create();
+ .BuildSchemaAsync();
}
// act
// assert
- Assert.Throws((Action)SchemaCreation);
+ Assert.ThrowsAsync(SchemaCreation);
}
private ValueTask ResolveRef(ISchema schema, ObjectType type)
@@ -210,18 +222,18 @@ void SchemaCreation()
return entity;
}
- public class Query_InClass_Invalid
+ public sealed class Query_InClass_Invalid
{
public InvalidInClassRefResolver InvalidInClassRefResolver { get; set; } = default!;
}
- public class Query_ExternalClass_Invalid
+ public sealed class Query_ExternalClass_Invalid
{
public ExternalRefResolver_Invalid ExternalRefResolver_Invalid { get; set; } = default!;
}
[ReferenceResolver(EntityResolver = "non-existing-method")]
- public class InvalidInClassRefResolver
+ public sealed class InvalidInClassRefResolver
{
[Key]
public string? Id { get; set; }
@@ -230,19 +242,19 @@ public class InvalidInClassRefResolver
[ReferenceResolver(
EntityResolverType = typeof(InvalidExternalRefResolver),
EntityResolver = "non-existing-method")]
- public class ExternalRefResolver_Invalid
+ public sealed class ExternalRefResolver_Invalid
{
[Key]
public string? Id { get; set; }
}
- public class InvalidExternalRefResolver
+ public sealed class InvalidExternalRefResolver
{
[Key]
public string? Id { get; set; }
}
- public class Query
+ public sealed class Query
{
public InClassRefResolver InClassRefResolver { get; set; } = default!;
public ExternalRefResolver ExternalRefResolver { get; set; } = default!;
@@ -250,23 +262,23 @@ public class Query
default!;
}
- public class QueryWithSingleKeyResolver
+ public sealed class QueryWithSingleKeyResolver
{
public ExternalSingleKeyResolver ExternalRefResolver { get; set; } = default!;
}
- public class QueryWithMultiKeyResolver
+ public sealed class QueryWithMultiKeyResolver
{
public ExternalMultiKeyResolver ExternalRefResolver { get; set; } = default!;
}
- public class QueryWithExternalField
+ public sealed class QueryWithExternalField
{
public ExternalFields ExternalRefResolver { get; set; } = default!;
}
[ReferenceResolver(EntityResolver = nameof(GetAsync))]
- public class InClassRefResolver
+ public sealed class InClassRefResolver
{
[Key]
public string? Id { get; set; }
@@ -274,7 +286,7 @@ public class InClassRefResolver
public Task GetAsync([LocalState] ObjectValueNode data)
{
return Task.FromResult(
- new InClassRefResolver()
+ new InClassRefResolver
{
Id = nameof(InClassRefResolver),
});
@@ -282,14 +294,14 @@ public Task GetAsync([LocalState] ObjectValueNode data)
}
[ReferenceResolver(EntityResolverType = typeof(ExternalReferenceResolver))]
- public class ExternalRefResolver
+ public sealed class ExternalRefResolver
{
[Key]
public string Id { get; set; } = default!;
}
[ReferenceResolver(EntityResolver = nameof(GetAsync))]
- public class ExternalSingleKeyResolver
+ public sealed class ExternalSingleKeyResolver
{
[Key]
public string Id { get; set; } = default!;
@@ -299,7 +311,7 @@ public static Task GetAsync(string id)
}
[ReferenceResolver(EntityResolver = nameof(GetAsync))]
- public class ExternalFields
+ public sealed class ExternalFields
{
[Key]
public string Id { get; set; } = default!;
@@ -313,8 +325,7 @@ public static Task GetAsync(string id)
[Key("id")]
[Key("sku")]
-
- public class ExternalMultiKeyResolver
+ public sealed class ExternalMultiKeyResolver
{
public string Id { get; set; } = default!;
@@ -332,7 +343,7 @@ public static Task