diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs index 511735be58d..35008d2dae3 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/ApolloFederationRequestExecutorBuilderExtensions.cs @@ -27,7 +27,7 @@ public static class ApolloFederationRequestExecutorBuilderExtensions /// public static IRequestExecutorBuilder AddApolloFederation( this IRequestExecutorBuilder builder, - FederationVersion version = FederationVersion.Latest) + FederationVersion version = FederationVersion.Default) { ArgumentNullException.ThrowIfNull(builder); builder.SetContextData(FederationContextData.FederationVersion, version); diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs index 537a1a53209..16c67819ea7 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Extensions/FederationVersionExtensions.cs @@ -14,8 +14,10 @@ [new Uri(FederationVersionUrls.Federation22)] = FederationVersion.Federation22, [new Uri(FederationVersionUrls.Federation23)] = FederationVersion.Federation23, [new Uri(FederationVersionUrls.Federation24)] = FederationVersion.Federation24, [new Uri(FederationVersionUrls.Federation25)] = FederationVersion.Federation25, + [new Uri(FederationVersionUrls.Federation26)] = FederationVersion.Federation26, + [new Uri(FederationVersionUrls.Federation27)] = FederationVersion.Federation27, }; - + private static readonly Dictionary _versionToUri = new() { [FederationVersion.Federation20] = new(FederationVersionUrls.Federation20), @@ -24,8 +26,10 @@ [new Uri(FederationVersionUrls.Federation25)] = FederationVersion.Federation25, [FederationVersion.Federation23] = new(FederationVersionUrls.Federation23), [FederationVersion.Federation24] = new(FederationVersionUrls.Federation24), [FederationVersion.Federation25] = new(FederationVersionUrls.Federation25), + [FederationVersion.Federation26] = new(FederationVersionUrls.Federation26), + [FederationVersion.Federation27] = new(FederationVersionUrls.Federation27), }; - + public static FederationVersion GetFederationVersion( this IDescriptor descriptor) where T : DefinitionBase @@ -40,7 +44,7 @@ public static FederationVersion GetFederationVersion( // TODO : resources throw new InvalidOperationException("The configuration state is invalid."); } - + public static FederationVersion GetFederationVersion( this IDescriptorContext context) { @@ -56,26 +60,26 @@ public static FederationVersion GetFederationVersion( public static Uri ToUrl(this FederationVersion version) { - if(_versionToUri.TryGetValue(version, out var url)) + 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)) + 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/FederationTypeNames.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs index 6b9a29c7efb..8a18a645bc3 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationTypeNames.cs @@ -12,13 +12,15 @@ internal static class FederationTypeNames public const string KeyDirective_Name = "key"; public const string LinkDirective_Name = "link"; public const string OverrideDirective_Name = "override"; + public const string PolicyDirective_Name = "policy"; 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 PolicyType_Name = "Policy"; 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 index f3626a7c84c..1cbb17544f1 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersion.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersion.cs @@ -13,6 +13,9 @@ public enum FederationVersion Federation23 = 2_3, Federation24 = 2_4, Federation25 = 2_5, - // Federation26 = 2_6, - Latest = Federation25, -} \ No newline at end of file + Federation26 = 2_6, + Federation27 = 2_7, + // default to latest-1 + Default = Federation26, + Latest = Federation27 +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs index 054b96ce3cb..68f4b606113 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/FederationVersionUrls.cs @@ -8,4 +8,6 @@ internal static class FederationVersionUrls 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 + public const string Federation26 = "https://specs.apollo.dev/federation/v2.6"; + public const string Federation27 = "https://specs.apollo.dev/federation/v2.7"; +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs index 64465d4a006..1040e29e37d 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.Designer.cs @@ -7,196 +7,257 @@ // //------------------------------------------------------------------------------ -namespace HotChocolate.ApolloFederation.Properties { +namespace HotChocolate.ApolloFederation.Properties +{ using System; - - + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal partial class FederationResources { - + internal partial class FederationResources + { + private static System.Resources.ResourceManager resourceMan; - + private static System.Globalization.CultureInfo resourceCulture; - + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal FederationResources() { + internal FederationResources() + { } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { + internal static System.Resources.ResourceManager ResourceManager + { + get + { + if (object.Equals(null, resourceMan)) + { System.Resources.ResourceManager temp = new System.Resources.ResourceManager("HotChocolate.ApolloFederation.Properties.FederationResources", typeof(FederationResources).Assembly); resourceMan = temp; } return resourceMan; } } - + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { + internal static System.Globalization.CultureInfo Culture + { + get + { return resourceCulture; } - set { + set + { resourceCulture = value; } } - - internal static string FieldsetType_Description { - get { + + internal static string FieldsetType_Description + { + get + { return ResourceManager.GetString("FieldsetType_Description", resourceCulture); } } - - internal static string ScopeType_Description { - get { + + internal static string ScopeType_Description + { + get + { return ResourceManager.GetString("ScopeType_Description", resourceCulture); } } - - internal static string TagDirective_Description { - get { - return ResourceManager.GetString("TagDirective_Description", resourceCulture); + + internal static string PolicyType_Description + { + get + { + return ResourceManager.GetString("PolicyType_Description", resourceCulture); } } - - internal static string EntityType_Description { - get { - return ResourceManager.GetString("EntityType_Description", resourceCulture); + + internal static string TagDirective_Description + { + get + { + return ResourceManager.GetString("TagDirective_Description", resourceCulture); } } - - internal static string PolicyDirective_Description { - get { - return ResourceManager.GetString("PolicyDirective_Description", resourceCulture); + + internal static string EntityType_Description + { + get + { + return ResourceManager.GetString("EntityType_Description", resourceCulture); } } - - internal static string ThrowHelper_FieldSet_HasInvalidFormat { - get { + + internal static string ThrowHelper_FieldSet_HasInvalidFormat + { + get + { return ResourceManager.GetString("ThrowHelper_FieldSet_HasInvalidFormat", resourceCulture); } } - - internal static string ThrowHelper_Scalar_CannotParseValue { - get { + + internal static string ThrowHelper_Scalar_CannotParseValue + { + get + { return ResourceManager.GetString("ThrowHelper_Scalar_CannotParseValue", resourceCulture); } } - - internal static string ThrowHelper_Key_FieldSet_CannotBeEmpty { - get { + + internal static string ThrowHelper_Key_FieldSet_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_Key_FieldSet_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_Provides_FieldSet_CannotBeEmpty { - get { + + internal static string ThrowHelper_Provides_FieldSet_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_Provides_FieldSet_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_Requires_FieldSet_CannotBeEmpty { - get { + + internal static string ThrowHelper_Requires_FieldSet_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_Requires_FieldSet_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_ComposeDirective_Name_CannotBeEmpty { - get { + + internal static string ThrowHelper_ComposeDirective_Name_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_ComposeDirective_Name_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_Link_Url_CannotBeEmpty { - get { + + internal static string ThrowHelper_Link_Url_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_Link_Url_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_Contact_Name_CannotBeEmpty { - get { + + internal static string ThrowHelper_Contact_Name_CannotBeEmpty + { + get + { return ResourceManager.GetString("ThrowHelper_Contact_Name_CannotBeEmpty", resourceCulture); } } - - internal static string ThrowHelper_FederationVersion_Unknown { - get { + + internal static string ThrowHelper_FederationVersion_Unknown + { + get + { return ResourceManager.GetString("ThrowHelper_FederationVersion_Unknown", resourceCulture); } } - - internal static string FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty { - get { + + internal static string FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty + { + get + { return ResourceManager.GetString("FieldDescriptorExtensions_Key_FieldSet_CannotBeNullOrEmpty", resourceCulture); } } - - internal static string FieldDescriptorExtensions_Requires_FieldSet_CannotBeNullOrEmpty { - get { + + internal static string FieldDescriptorExtensions_Requires_FieldSet_CannotBeNullOrEmpty + { + get + { return ResourceManager.GetString("FieldDescriptorExtensions_Requires_FieldSet_CannotBeNullOrEmpty", resourceCulture); } } - - internal static string FieldDescriptorExtensions_Provides_FieldSet_CannotBeNullOrEmpty { - get { + + internal static string FieldDescriptorExtensions_Provides_FieldSet_CannotBeNullOrEmpty + { + get + { return ResourceManager.GetString("FieldDescriptorExtensions_Provides_FieldSet_CannotBeNullOrEmpty", resourceCulture); } } - - internal static string FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty { - get { + + internal static string FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty + { + get + { return ResourceManager.GetString("FieldDescriptorExtensions_Override_From_CannotBeNullOrEmpty", resourceCulture); } } - - internal static string ThrowHelper_EntityType_NoEntities { - get { + + internal static string ThrowHelper_EntityType_NoEntities + { + get + { return ResourceManager.GetString("ThrowHelper_EntityType_NoEntities", resourceCulture); } } - - internal static string ThrowHelper_EntityResolver_NoEntityResolverFound { - get { + + internal static string ThrowHelper_EntityResolver_NoEntityResolverFound + { + get + { return ResourceManager.GetString("ThrowHelper_EntityResolver_NoEntityResolverFound", resourceCulture); } } - - internal static string EntityResolver_MustBeMethod { - get { + + internal static string EntityResolver_MustBeMethod + { + get + { return ResourceManager.GetString("EntityResolver_MustBeMethod", resourceCulture); } } - - internal static string ThrowHelper_Any_HasInvalidFormat { - get { + + internal static string ThrowHelper_Any_HasInvalidFormat + { + get + { return ResourceManager.GetString("ThrowHelper_Any_HasInvalidFormat", resourceCulture); } } - - internal static string ExpressionHelper_GetScopedStateWithDefault_NoDefaultValue { - get { + + internal static string ExpressionHelper_GetScopedStateWithDefault_NoDefaultValue + { + get + { return ResourceManager.GetString("ExpressionHelper_GetScopedStateWithDefault_NoDefaultValue", resourceCulture); } } - - internal static string Any_Description { - get { + + internal static string Any_Description + { + get + { return ResourceManager.GetString("Any_Description", resourceCulture); } } - - internal static string ResolveReference_MustBeMethod { - get { + + internal static string ResolveReference_MustBeMethod + { + get + { return ResourceManager.GetString("ResolveReference_MustBeMethod", resourceCulture); } } - - internal static string PolicyCollectionType_ParseValue_ExpectedStringArray { - get { + + 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 index f37f2257f5c..9585618b9bc 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.cs @@ -16,17 +16,17 @@ internal partial class FederationResources 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."; @@ -38,7 +38,7 @@ internal partial class FederationResources 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 " + @@ -48,8 +48,21 @@ internal partial class FederationResources 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 + + public const string PolicyDirective_Description = + "Indicates to composition that the target element is restricted based " + + "on authorization policies."; + + public const string LinkDirective_Description = + "Links definitions within the document to external schemas."; + + public const string LinkDirective_Url_Description = + "Gets imported specification url."; + + public const string LinkDirective_Import_Description = + "Gets optional list of imported element names."; +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx index 73302292ad2..4e648628953 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Properties/FederationResources.resx @@ -121,7 +121,10 @@ Scalar representing a set of fields. - Scalar representing a JWT scope + Scalar representing a JWT scope. + + + Scalar representing an authorization policy. Allows users to annotate fields and types with additional metadata information. @@ -129,9 +132,6 @@ 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. diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs index d5ee8b5ee29..0d54ef5bbfc 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/AuthenticatedDirective.cs @@ -25,9 +25,9 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// -[Package(Federation24)] +[Package(Federation25)] [DirectiveType( - AuthenticatedDirective_Name, + AuthenticatedDirective_Name, DirectiveLocation.Enum | DirectiveLocation.FieldDefinition | DirectiveLocation.Interface | diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs index e76aa35145a..9e7a8f19ef3 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ComposeDirective.cs @@ -14,7 +14,7 @@ namespace HotChocolate.ApolloFederation.Types; /// /// /// extend schema @composeDirective(name: "@custom") -/// @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@composeDirective"]) +/// @link(url: "https://specs.apollo.dev/federation/v2.1", import: ["@composeDirective"]) /// @link(url: "https://myspecs.dev/custom/v1.0", import: ["@custom"]) /// /// directive @custom on FIELD_DEFINITION @@ -24,10 +24,10 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// -[Package(Federation25)] +[Package(Federation21)] [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/ExternalDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs index 3118084014e..f2fe5395e42 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalDirective.cs @@ -6,7 +6,11 @@ namespace HotChocolate.ApolloFederation.Types; /// /// +/// # federation v1 definition /// directive @external on FIELD_DEFINITION +/// +/// # federation v2 definition (applying on object = shorthand for applying on all fields) +/// directive @external on OBJECT | FIELD_DEFINITION /// /// /// The @external directive is used to mark a field as owned by another service. @@ -27,8 +31,9 @@ namespace HotChocolate.ApolloFederation.Types; /// /// [Package(Federation20)] -[DirectiveType(ExternalDirective_Name, DirectiveLocation.FieldDefinition)] +[DirectiveType(ExternalDirective_Name, DirectiveLocation.FieldDefinition | DirectiveLocation.Object)] [GraphQLDescription(ExternalDirective_Description)] +[ExternalLegacySupport] public sealed class ExternalDirective { public static ExternalDirective Default { get; } = new(); diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalLegacySupportAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalLegacySupportAttribute.cs new file mode 100644 index 00000000000..b567f2d5b56 --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ExternalLegacySupportAttribute.cs @@ -0,0 +1,19 @@ +using HotChocolate.Types.Descriptors; + +namespace HotChocolate.ApolloFederation.Types; + +internal sealed class ExternalLegacySupportAttribute : DirectiveTypeDescriptorAttribute +{ + protected override void OnConfigure( + IDescriptorContext context, + IDirectiveTypeDescriptor descriptor, + Type type) + { + // federation v1 only supported @external on fields + if (descriptor.GetFederationVersion() == FederationVersion.Federation10) + { + var desc = (IDirectiveTypeDescriptor)descriptor; + desc.Location(DirectiveLocation.Field); + } + } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs index 0d76dfc1daa..e00f9e31949 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/InterfaceObjectDirective.cs @@ -19,10 +19,10 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// -[Package(Federation22)] +[Package(Federation23)] [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/KeyLegacySupportAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs index 8aa10f9d292..ce6cab4e623 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/KeyLegacySupportAttribute.cs @@ -5,10 +5,11 @@ namespace HotChocolate.ApolloFederation.Types; internal sealed class KeyLegacySupportAttribute : DirectiveTypeDescriptorAttribute { protected override void OnConfigure( - IDescriptorContext context, - IDirectiveTypeDescriptor descriptor, + IDescriptorContext context, + IDirectiveTypeDescriptor descriptor, Type type) { + // federation v1 @key only specified "fields" parameter if (descriptor.GetFederationVersion() == FederationVersion.Federation10) { var desc = (IDirectiveTypeDescriptor)descriptor; @@ -16,4 +17,4 @@ protected override void OnConfigure( desc.Argument(t => t.Fields); } } -} \ 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 index 88df3bd476a..3e221a37862 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/LinkDirective.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using HotChocolate.ApolloFederation.Properties; using static HotChocolate.ApolloFederation.FederationTypeNames; namespace HotChocolate.ApolloFederation.Types; @@ -7,6 +8,7 @@ namespace HotChocolate.ApolloFederation.Types; /// Object representation of @link directive. /// [DirectiveType(LinkDirective_Name, DirectiveLocation.Schema, IsRepeatable = true)] +[GraphQLDescription(FederationResources.LinkDirective_Description)] public sealed class LinkDirective { /// @@ -28,10 +30,12 @@ public LinkDirective(Uri url, IReadOnlySet? import) /// Gets imported specification url. /// [GraphQLType>] + [GraphQLDescription(FederationResources.LinkDirective_Url_Description)] public Uri Url { get; } /// /// Gets optional list of imported element names. /// + [GraphQLDescription(FederationResources.LinkDirective_Import_Description)] 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 index 020f4243acc..f65faa2acca 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideAttribute.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideAttribute.cs @@ -19,31 +19,48 @@ namespace HotChocolate.ApolloFederation.Types; /// description: String @override(from: "BarSubgraph") /// } /// +/// +/// The progressive @override feature enables the gradual, progressive deployment of a subgraph +/// with an @override field. As a subgraph developer, you can customize the percentage of traffic +/// that the overriding and overridden subgraphs each resolve for a field. You apply a label to +/// an @override field to set the percentage of traffic for the field that should be resolved by +/// the overriding subgraph, with the remaining percentage resolved by the overridden subgraph. +/// See Apollo documentation +/// for additional details. +/// +/// type Foo @key(fields: "id") { +/// id: ID! +/// description: String @override(from: "BarSubgraph", label: "percent(1)") +/// } +/// /// -public sealed class OverrideAttribute : ObjectFieldDescriptorAttribute +/// +/// Initializes new instance of +/// +/// +/// Name of the subgraph to be overridden +/// +/// +/// Optional label that will be evaulated at runtime to determine whether field should be overriden +/// +public sealed class OverrideAttribute(string from, string? label = null) : ObjectFieldDescriptorAttribute { /// - /// Initializes new instance of + /// Get name of the subgraph to be overridden. /// - /// - /// Name of the subgraph to be overridden - /// - public OverrideAttribute(string from) - { - From = from; - } + public string From { get; } = from; /// - /// Get name of the subgraph to be overridden. + /// Get optional label that will be evaulated at runtime to determine whether field should be overriden. /// - public string From { get; } + public string? Label { get; } = label; protected override void OnConfigure( IDescriptorContext context, IObjectFieldDescriptor descriptor, MemberInfo member) { - descriptor.Override(From); + descriptor.Override(From, Label); } } diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs index f50c150e48a..097ac464030 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDescriptorExtensions.cs @@ -13,6 +13,19 @@ public static class OverrideDescriptorExtensions /// description: String @override(from: "BarSubgraph") /// } /// + /// The progressive @override feature enables the gradual, progressive deployment of a subgraph + /// with an @override field. As a subgraph developer, you can customize the percentage of traffic + /// that the overriding and overridden subgraphs each resolve for a field. You apply a label to + /// an @override field to set the percentage of traffic for the field that should be resolved by + /// the overriding subgraph, with the remaining percentage resolved by the overridden subgraph. + /// See Apollo documentation + /// for additional details. + /// + /// type Foo @key(fields: "id") { + /// id: ID! + /// description: String @override(from: "BarSubgraph", label: "percent(1)") + /// } + /// /// /// /// The object field descriptor on which this directive shall be annotated. @@ -20,6 +33,9 @@ public static class OverrideDescriptorExtensions /// /// Name of the subgraph to be overridden. /// + /// + /// Optional label that will be used at runtime to evaluate whether to override the field or not. + /// /// /// Returns the object field descriptor. /// @@ -31,11 +47,12 @@ public static class OverrideDescriptorExtensions /// public static IObjectFieldDescriptor Override( this IObjectFieldDescriptor descriptor, - string from) + string from, + string? label = null) { ArgumentNullException.ThrowIfNull(descriptor); ArgumentException.ThrowIfNullOrEmpty(from); - return descriptor.Directive(new OverrideDirective(from)); + return descriptor.Directive(new OverrideDirective(from, label)); } -} \ 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 index 5c5a1943cb4..445a0c49198 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideDirective.cs @@ -6,7 +6,11 @@ namespace HotChocolate.ApolloFederation.Types; /// /// +/// # federation v2.0 definition /// directive @override(from: String!) on FIELD_DEFINITION +/// +/// # federation v2.7 definition +/// directive @override(from: String!, label: String) on FIELD_DEFINITION /// /// /// The @override directive is used to indicate that the current subgraph is taking @@ -19,11 +23,46 @@ namespace HotChocolate.ApolloFederation.Types; /// description: String @override(from: "BarSubgraph") /// } /// +/// +/// The progressive @override feature enables the gradual, progressive deployment of a subgraph +/// with an @override field. As a subgraph developer, you can customize the percentage of traffic +/// that the overriding and overridden subgraphs each resolve for a field. You apply a label to +/// an @override field to set the percentage of traffic for the field that should be resolved by +/// the overriding subgraph, with the remaining percentage resolved by the overridden subgraph. +/// See Apollo documentation +/// for additional details. +/// +/// type Foo @key(fields: "id") { +/// id: ID! +/// description: String @override(from: "BarSubgraph", label: "percent(1)") +/// } +/// /// +/// +/// Name of the subgraph to be overridden +/// [Package(Federation20)] [DirectiveType(OverrideDirective_Name, DirectiveLocation.FieldDefinition)] [GraphQLDescription(OverrideDirective_Description)] +[OverrideLegacySupport] public sealed class OverrideDirective(string from) { + + /// + /// Creates new instance of @override directive. + /// + /// + /// Name of the subgraph to be overridden + /// + /// + /// Optional label that will be evaulated at runtime to determine whether field should be overriden + /// + public OverrideDirective(string from, string? label = null) : this(from) + { + Label = label; + } + public string From { get; } = from; -} \ No newline at end of file + + public string? Label { get; } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideLegacySupportAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideLegacySupportAttribute.cs new file mode 100644 index 00000000000..d53a58c3c8b --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/OverrideLegacySupportAttribute.cs @@ -0,0 +1,20 @@ +using HotChocolate.Types.Descriptors; + +namespace HotChocolate.ApolloFederation.Types; + +internal sealed class OverrideLegacySupportAttribute : DirectiveTypeDescriptorAttribute +{ + protected override void OnConfigure( + IDescriptorContext context, + IDirectiveTypeDescriptor descriptor, + Type type) + { + // prior to version 2.7 @override only specified "from" parameter + if (descriptor.GetFederationVersion() < FederationVersion.Federation27) + { + var desc = (IDirectiveTypeDescriptor)descriptor; + desc.BindArgumentsExplicitly(); + desc.Argument(t => t.From); + } + } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyAttribute.cs new file mode 100644 index 00000000000..fb99bc267ae --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyAttribute.cs @@ -0,0 +1,76 @@ +using System.Reflection; +using HotChocolate.Types.Descriptors; + +namespace HotChocolate.ApolloFederation.Types; + +/// +/// +/// directive @policy(policies: [[Policy!]!]!) on +/// ENUM +/// | FIELD_DEFINITION +/// | INTERFACE +/// | OBJECT +/// | SCALAR +/// +/// +/// Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. +/// Refer to the Apollo Router article for additional details. +/// +/// type Foo @key(fields: "id") { +/// id: ID +/// description: String @policy(policies: [["policy1"]]) +/// } +/// +/// +/// +/// Initializes new instance of +/// +/// +/// Array of required authentication policies. +/// +[AttributeUsage( + AttributeTargets.Class + | AttributeTargets.Enum + | AttributeTargets.Interface + | AttributeTargets.Method + | AttributeTargets.Property + | AttributeTargets.Struct, + AllowMultiple = true +)] +public sealed class PolicyAttribute(string[] policies) : DescriptorAttribute +{ + + /// + /// Retrieves array of required authentication policies. + /// + public string[] Policies { get; } = policies; + + protected internal override void TryConfigure( + IDescriptorContext context, + IDescriptor descriptor, + ICustomAttributeProvider element) + { + switch (descriptor) + { + case IEnumTypeDescriptor desc: + desc.Policy(Policies); + break; + + case IObjectTypeDescriptor desc: + desc.Policy(Policies); + break; + + case IObjectFieldDescriptor desc: + desc.Policy(Policies); + break; + + case IInterfaceTypeDescriptor desc: + desc.Policy(Policies); + break; + + case IInterfaceFieldDescriptor desc: + desc.Policy(Policies); + break; + } + } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDescriptorExtensions.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDescriptorExtensions.cs new file mode 100644 index 00000000000..6cb65c656cf --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDescriptorExtensions.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 @policy directive on type system descriptors. +/// +public static class PolicyDescriptorExtensions +{ + /// + /// Applies @policy directive to indicate that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. + /// + /// type Foo @key(fields: "id") { + /// id: ID + /// description: String @policy(policies: [["policy1"]]) + /// } + /// + /// + /// + /// The type descriptor on which this directive shall be annotated. + /// + /// Required authorization policies + /// + /// Returns the type descriptor. + /// + /// + /// The is null. + /// + public static IEnumTypeDescriptor Policy( + this IEnumTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies, def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + /// Applies @policy directive to indicate that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. + /// + /// type Foo @key(fields: "id") { + /// id: ID + /// description: String @policy(policies: [["policy1"]]) + /// } + /// + /// + /// + /// The type descriptor on which this directive shall be annotated. + /// + /// Required authrorization policies + /// + /// Returns the type descriptor. + /// + /// + /// The is null. + /// + public static IEnumTypeDescriptor Policy( + this IEnumTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies.Select(p => new Policy(p)).ToArray(), def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IInterfaceFieldDescriptor Policy( + this IInterfaceFieldDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies, def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IInterfaceFieldDescriptor Policy( + this IInterfaceFieldDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies.Select(p => new Policy(p)).ToArray(), def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IInterfaceTypeDescriptor Policy( + this IInterfaceTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies, def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IInterfaceTypeDescriptor Policy( + this IInterfaceTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies.Select(p => new Policy(p)).ToArray(), def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IObjectFieldDescriptor Policy( + this IObjectFieldDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies, def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IObjectFieldDescriptor Policy( + this IObjectFieldDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies.Select(p => new Policy(p)).ToArray(), def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IObjectTypeDescriptor Policy( + this IObjectTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies, def, ctx.TypeInspector); + }); + + return descriptor; + } + + /// + public static IObjectTypeDescriptor Policy( + this IObjectTypeDescriptor descriptor, + IReadOnlyList policies) + { + ArgumentNullException.ThrowIfNull(descriptor); + + descriptor.Extend().OnBeforeCreate( + (ctx, def) => + { + AddPolicies(policies.Select(p => new Policy(p)).ToArray(), def, ctx.TypeInspector); + }); + + return descriptor; + } + + private static void AddPolicies( + IReadOnlyList policies, + IHasDirectiveDefinition definition, + ITypeInspector typeInspector) + { + var directive = definition + .Directives + .Select(t => t.Value) + .OfType() + .FirstOrDefault(); + + if (directive is null) + { + directive = new PolicyDirective([]); + definition.AddDirective(directive, typeInspector); + } + + var newPolicies = policies.ToHashSet(); + directive.Policies.Add(newPolicies); + } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDirective.cs new file mode 100644 index 00000000000..db0d3932055 --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/PolicyDirective.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using HotChocolate.ApolloFederation.Properties; +using static HotChocolate.ApolloFederation.FederationTypeNames; + +namespace HotChocolate.ApolloFederation.Types; + +/// +/// +/// directive @policy(policies: [[Policy!]!]!) on +/// ENUM +/// | FIELD_DEFINITION +/// | INTERFACE +/// | OBJECT +/// | SCALAR +/// +/// +/// Indicates to composition that the target element is restricted based on authorization policies that are evaluated in a Rhai script or coprocessor. +/// Refer to the Apollo Router article for additional details. +/// +/// type Foo @key(fields: "id") { +/// id: ID +/// description: String @policy(policies: [["policy1"]]) +/// } +/// +/// +/// +/// List of a list of of authorization policies to evaluate. +/// +[Package(FederationVersionUrls.Federation26)] +[DirectiveType( + PolicyDirective_Name, + DirectiveLocation.Enum | + DirectiveLocation.FieldDefinition | + DirectiveLocation.Interface | + DirectiveLocation.Object | + DirectiveLocation.Scalar)] +[GraphQLDescription(FederationResources.PolicyDirective_Description)] +public sealed class PolicyDirective(List> policies) +{ + /// + /// Retrieves list of a list of authorization policies to evaluate. + /// + public List> Policies { get; } = policies; +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs index 606457193e1..4faa2705ef5 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/ProvidesAttribute.cs @@ -31,25 +31,21 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// -public sealed class ProvidesAttribute : ObjectFieldDescriptorAttribute +/// +/// Initializes a new instance of . +/// +/// +/// Gets the fields that is guaranteed to be selectable by the gateway. +/// Grammatically, a field set is a selection set minus the braces. +/// +public sealed class ProvidesAttribute(string fieldSet) : ObjectFieldDescriptorAttribute { - /// - /// Initializes a new instance of . - /// - /// - /// Gets the fields that is guaranteed to be selectable by the gateway. - /// Grammatically, a field set is a selection set minus the braces. - /// - public ProvidesAttribute(string fieldSet) - { - FieldSet = fieldSet; - } /// /// Gets the fields that are guaranteed to be selectable by the gateway. /// Grammatically, a field set is a selection set minus the braces. /// - public string FieldSet { get; } + public string FieldSet { get; } = fieldSet; protected override void OnConfigure( IDescriptorContext context, diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs index d8022448c82..76066788990 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresAttribute.cs @@ -28,28 +28,24 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// -public sealed class RequiresAttribute : ObjectFieldDescriptorAttribute +/// +/// Initializes a new instance of . +/// +/// +/// 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. +/// +public sealed class RequiresAttribute(string fieldSet) : ObjectFieldDescriptorAttribute { - /// - /// Initializes a new instance of . - /// - /// - /// 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. - /// - public RequiresAttribute(string fieldSet) - { - FieldSet = fieldSet; - } /// /// Gets the fieldset which describes fields that 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. /// - public string FieldSet { get; } + public string FieldSet { get; } = fieldSet; protected override void OnConfigure( IDescriptorContext context, diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs index 0b89917a912..7f33fd3a732 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesAttribute.cs @@ -22,6 +22,12 @@ namespace HotChocolate.ApolloFederation.Types; /// } /// /// +/// +/// Initializes new instance of +/// +/// +/// Array of required JWT scopes. +/// [AttributeUsage( AttributeTargets.Class | AttributeTargets.Enum @@ -31,23 +37,13 @@ namespace HotChocolate.ApolloFederation.Types; | AttributeTargets.Struct, AllowMultiple = true )] -public sealed class RequiresScopesAttribute : DescriptorAttribute +public sealed class RequiresScopesAttribute(string[] scopes) : 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; } + public string[] Scopes { get; } = scopes; protected internal override void TryConfigure( IDescriptorContext context, @@ -59,19 +55,19 @@ protected internal override void TryConfigure( 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/RequiresScopesDirective.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs index d4ccb6614f5..6055e4ad074 100644 --- a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Directives/RequiresScopesDirective.cs @@ -26,7 +26,7 @@ namespace HotChocolate.ApolloFederation.Types; /// /// List of a list of required JWT scopes. /// -[Package(FederationVersionUrls.Federation24)] +[Package(FederationVersionUrls.Federation25)] [DirectiveType( RequiresScopesDirective_Name, DirectiveLocation.Enum | diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Policy.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Policy.cs new file mode 100644 index 00000000000..bf952c14e30 --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/Policy.cs @@ -0,0 +1,23 @@ +namespace HotChocolate.ApolloFederation.Types; + +/// +/// Scalar Policy representation. +/// +public readonly record struct Policy +{ + /// + /// Initializes a new instance of . + /// + /// + /// Policy value + /// + public Policy(string value) + { + Value = value; + } + + /// + /// Retrieve policy value + /// + public string Value { get; } +} diff --git a/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/PolicyType.cs b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/PolicyType.cs new file mode 100644 index 00000000000..182e03f3718 --- /dev/null +++ b/src/HotChocolate/ApolloFederation/src/ApolloFederation/Types/PolicyType.cs @@ -0,0 +1,41 @@ +using HotChocolate.ApolloFederation.Properties; +using HotChocolate.Language; + +namespace HotChocolate.ApolloFederation.Types; + +/// +/// The Policy scalar representing an authorization policy. Serializes as a string. +/// +public sealed class PolicyType : ScalarType +{ + /// + /// Initializes a new instance of . + /// + public PolicyType() : this(FederationTypeNames.PolicyType_Name) + { + } + + /// + /// Initializes a new instance of . + /// + /// + /// Scalar name + /// + /// + /// Defines if this scalar shall bind implicitly to . + /// + public PolicyType(string name, BindingBehavior bind = BindingBehavior.Explicit) + : base(name, bind) + { + Description = FederationResources.PolicyType_Description; + } + + protected override Policy ParseLiteral(StringValueNode valueSyntax) + => new(valueSyntax.Value); + + public override IValueNode ParseResult(object? resultValue) + => ParseValue(resultValue); + + protected override StringValueNode ParseValue(Policy runtimeValue) + => new(runtimeValue.Value); +} 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 17df87c164e..3a801cd3b05 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 @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "FieldSet", "@external" ]) { +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@key", "@provides", "FieldSet", "@external" ]) { query: Query } @@ -40,12 +40,12 @@ type _Service { 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 +directive @external on OBJECT | 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." +"Links definitions within the document to external schemas." 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." 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 17df87c164e..3a801cd3b05 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,4 @@ -schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "FieldSet", "@external" ]) { +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@key", "@provides", "FieldSet", "@external" ]) { query: Query } @@ -40,12 +40,12 @@ type _Service { 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 +directive @external on OBJECT | 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." +"Links definitions within the document to external schemas." 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." 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 f8412cdddab..46dd915c0bc 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 @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "@external", "FieldSet" ]) { +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@key", "@provides", "@external", "FieldSet" ]) { query: Query } @@ -40,12 +40,12 @@ type _Service { 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 +directive @external on OBJECT | 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." +"Links definitions within the document to external schemas." 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." 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 f8412cdddab..46dd915c0bc 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,4 @@ -schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "@provides", "@external", "FieldSet" ]) { +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@key", "@provides", "@external", "FieldSet" ]) { query: Query } @@ -40,12 +40,12 @@ type _Service { 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 +directive @external on OBJECT | 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." +"Links definitions within the document to external schemas." 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." diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/OverrideDirectiveTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/OverrideDirectiveTests.cs new file mode 100644 index 00000000000..ade57fa3ffa --- /dev/null +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/OverrideDirectiveTests.cs @@ -0,0 +1,70 @@ +using System.Threading.Tasks; +using HotChocolate.ApolloFederation.Resolvers; +using HotChocolate.ApolloFederation.Types; +using HotChocolate.Execution; +using HotChocolate.Types; +using HotChocolate.Types.Relay; +using Microsoft.Extensions.DependencyInjection; +using Snapshooter.Xunit; + +namespace HotChocolate.ApolloFederation.Directives; + +public class OverrideDirectiveTests +{ + [Fact] + public async Task OverrideDirective_Annotation() + { + // arrange + var schema = await new ServiceCollection() + .AddGraphQL() + .AddApolloFederation(FederationVersion.Federation20) + .AddQueryType() + .AddType() + .BuildSchemaAsync(); + + var entityType = schema.GetType(FederationTypeNames.ServiceType_Name); + var sdlResolver = entityType.Fields[WellKnownFieldNames.Sdl].Resolver!; + + // act + var value = await sdlResolver(TestHelper.CreateResolverContext(schema)); + value!.ToString().MatchSnapshot(); + } + + [Fact] + public async Task OverrideDirective_Progressive_Annotation() + { + // arrange + var schema = await new ServiceCollection() + .AddGraphQL() + .AddApolloFederation(FederationVersion.Federation27) + .AddQueryType() + .AddType() + .BuildSchemaAsync(); + + var entityType = schema.GetType(FederationTypeNames.ServiceType_Name); + var sdlResolver = entityType.Fields[WellKnownFieldNames.Sdl].Resolver!; + + // act + var value = await sdlResolver(TestHelper.CreateResolverContext(schema)); + value!.ToString().MatchSnapshot(); + } + + [Key("id")] + public class Foo(string id) + { + [ID] + public string Id { get; } = id; + + [Override("bar")] + public string Name => "abc"; + + [Override("bar", "percent(1)")] + public string Description => "xyz"; + + [ReferenceResolver] + public static Foo? ResolveReference(string id) + { + return new Foo(id); + } + } +} 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 index aaac69bb9ad..cd4504f123d 100644 --- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ComposeDirectiveTests.TestServiceTypeEmptyQueryTypePureCodeFirst.graphql +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/ComposeDirectiveTests.TestServiceTypeEmptyQueryTypePureCodeFirst.graphql @@ -1,4 +1,4 @@ -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" ]) { +schema @composeDirective(name: "custom") @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@composeDirective", "@key", "FieldSet" ]) @link(url: "https:\/\/specs.custom.dev\/custom\/v1.0", import: [ "@custom" ]) { query: Query } @@ -27,7 +27,7 @@ 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." +"Links definitions within the document to external schemas." 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." diff --git a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/OverrideDirectiveTests.OverrideDirective_Annotation.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/OverrideDirectiveTests.OverrideDirective_Annotation.snap new file mode 100644 index 00000000000..7f14772ab2a --- /dev/null +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/OverrideDirectiveTests.OverrideDirective_Annotation.snap @@ -0,0 +1,37 @@ +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.0", import: [ "@override", "@key", "FieldSet" ]) { + query: Query +} + +type Foo @key(fields: "id") { + id: ID! + name: String! @override(from: "bar") + description: String! @override(from: "bar") +} + +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 = Foo + +"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 + +"Links definitions within the document to external schemas." +directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." 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 + +"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/__snapshots__/OverrideDirectiveTests.OverrideDirective_Progressive_Annotation.snap b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/OverrideDirectiveTests.OverrideDirective_Progressive_Annotation.snap new file mode 100644 index 00000000000..e786fed43a5 --- /dev/null +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/Directives/__snapshots__/OverrideDirectiveTests.OverrideDirective_Progressive_Annotation.snap @@ -0,0 +1,37 @@ +schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.7", import: [ "@override", "@key", "FieldSet" ]) { + query: Query +} + +type Foo @key(fields: "id") { + id: ID! + name: String! @override(from: "bar") + description: String! @override(from: "bar", label: "percent(1)") +} + +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 = Foo + +"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 + +"Links definitions within the document to external schemas." +directive @link("Gets imported specification url." url: String! "Gets optional list of imported element names." 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! label: String) 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/ServiceTypeTests.cs b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ServiceTypeTests.cs index ea1c9474234..73039e9cae7 100644 --- a/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ServiceTypeTests.cs +++ b/src/HotChocolate/ApolloFederation/test/ApolloFederation.Tests/ServiceTypeTests.cs @@ -34,7 +34,7 @@ public async Task TestServiceTypeEmptyQueryTypePureCodeFirst() .Parse((string)value!) .MatchInlineSnapshot( """ - schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.5", import: [ "@key", "FieldSet" ]) { + schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.6", import: [ "@key", "FieldSet" ]) { query: Query } @@ -58,7 +58,7 @@ type _Service { "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." + "Links definitions within the document to external schemas." 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." @@ -93,34 +93,34 @@ public async Task TestServiceTypeTypePureCodeFirst() schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.2", import: [ "@key", "FieldSet" ]) { query: Query } - + type Address @key(fields: "matchCode") { matchCode: String } - + type Query { address(id: Int!): Address! _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 - + "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." + + "Links definitions within the document to external schemas." 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 """); @@ -135,4 +135,4 @@ public class Address { [Key] public string? MatchCode { get; set; } } -} \ No newline at end of file +}