Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ rearchitected
reencode
refetched
relayjs
reparsed
repaste
replit
reprojected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
namespace HotChocolate;

/// <summary>
/// Denotes a deprecated field on a GraphQL type or a
/// deprecated value on a GraphQL enum.
/// Denotes a deprecated field on a GraphQL type, a deprecated value on a
/// GraphQL enum, or a deprecated GraphQL directive definition.
/// </summary>
[AttributeUsage(AttributeTargets.Field // Required for enum values
[AttributeUsage(AttributeTargets.Class // Required for directive definitions
| AttributeTargets.Field // Required for enum values
| AttributeTargets.Property
| AttributeTargets.Parameter
| AttributeTargets.Method)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,32 @@ type.Description is null
type.Directives.Select(Format).ToArray());

public static DirectiveDefinitionNode Format(IDirectiveDefinition directiveDefinition)
=> new DirectiveDefinitionNode(
{
var directives = directiveDefinition.Directives.Select(Format).ToList();

if (directiveDefinition.IsDeprecated)
{
var deprecatedDirective = new DirectiveNode(
DirectiveNames.Deprecated.Name,
new ArgumentNode(
DirectiveNames.Deprecated.Arguments.Reason,
directiveDefinition.DeprecationReason
?? DirectiveNames.Deprecated.Arguments.DefaultReason));

directives.Insert(0, deprecatedDirective);
}

return new DirectiveDefinitionNode(
null,
new NameNode(directiveDefinition.Name),
directiveDefinition.Description is null
? null
: new StringValueNode(directiveDefinition.Description),
directiveDefinition.IsRepeatable,
directiveDefinition.Arguments.Select(Format).ToArray(),
directives,
DirectiveLocationUtils.AsEnumerable(directiveDefinition.Locations).Select(Format).ToArray());
}

public static FieldDefinitionNode Format(IOutputFieldDefinition field)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,17 +485,31 @@ public override void VisitDirectiveDefinition(
IDirectiveDefinition mutableDirective,
VisitorContext context)
{
VisitInputFields(mutableDirective.Arguments, context);
var arguments = (List<InputValueDefinitionNode>)context.Result!;
VisitDirectives(mutableDirective.Directives, context);
var directives = (List<DirectiveNode>)context.Result!;

directives = ApplyDeprecatedDirective(mutableDirective, directives);

context.Result =
new DirectiveDefinitionNode(
if (IsTypeExtension(mutableDirective))
{
context.Result = new DirectiveExtensionNode(
null,
new NameNode(mutableDirective.Name),
CreateDescription(mutableDirective.Description),
mutableDirective.IsRepeatable,
arguments,
mutableDirective.Locations.ToNameNodes());
directives);
return;
}

VisitInputFields(mutableDirective.Arguments, context);
var arguments = (List<InputValueDefinitionNode>)context.Result!;

context.Result = new DirectiveDefinitionNode(
null,
new NameNode(mutableDirective.Name),
CreateDescription(mutableDirective.Description),
mutableDirective.IsRepeatable,
arguments,
directives,
mutableDirective.Locations.ToNameNodes());
}

public override void VisitOutputFields(
Expand Down Expand Up @@ -595,7 +609,8 @@ private static List<DirectiveNode> ApplyDeprecatedDirective(
IDeprecationProvider canBeDeprecated,
List<DirectiveNode> directives)
{
if (canBeDeprecated.IsDeprecated)
if (canBeDeprecated.IsDeprecated
&& !directives.Any(d => d.Name.Value == DirectiveNames.Deprecated.Name))
{
var deprecateDirective = CreateDeprecatedDirective(canBeDeprecated.DeprecationReason);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ private static DirectiveDefinitionNode CreateSemanticNonNullDirectiveDefinition(
description: null,
isRepeatable: false,
arguments: new[] { argument },
directives: [],
locations: new[] { new NameNode("FIELD_DEFINITION") });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace HotChocolate.Types;
public interface IDirectiveDefinition
: INameProvider
, IDescriptionProvider
, IDeprecationProvider
, IDirectivesProvider
, IFeatureProvider
, ISyntaxNodeProvider<DirectiveDefinitionNode>
, ISchemaCoordinateProvider
Expand Down Expand Up @@ -47,6 +49,13 @@ public interface IDirectiveDefinition
/// </summary>
DirectiveLocation Locations { get; }

IReadOnlyDirectiveCollection IDirectivesProvider.Directives
=> EmptyCollections.Directives;

bool IDeprecationProvider.IsDeprecated => false;

string? IDeprecationProvider.DeprecationReason => null;

/// <summary>
/// Creates a <see cref="DirectiveDefinitionNode"/> from the current <see cref="IDirectiveDefinition"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ internal static class LogEntryCodes
public const string UndefinedInputFieldDefaultEnumValue = "HCV0024";
public const string UndefinedArgumentAssignedEnumValue = "HCV0025";
public const string UndefinedDirective = "HCV0026";
public const string DirectiveDefinitionSelfApplication = "HCV0027";
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public static LogEntry DirectiveDefinitionMissingLocation(IDirectiveDefinition d
.Build();
}

public static LogEntry DirectiveDefinitionSelfApplication(IDirectiveDefinition directiveDefinition)
{
return LogEntryBuilder.New()
.SetMessage(LogEntryHelper_DirectiveDefinitionSelfApplication, directiveDefinition.Name)
.SetCode(LogEntryCodes.DirectiveDefinitionSelfApplication)
.SetSeverity(LogSeverity.Error)
.SetTypeSystemMember(directiveDefinition)
.SetSpecifiedBy(TypeKind.Directive)
.Build();
}

public static LogEntry EmptyEnumType(IEnumTypeDefinition enumType)
{
return LogEntryBuilder.New()
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
<data name="LogEntryHelper_DirectiveDefinitionMissingLocation" xml:space="preserve">
<value>The Directive definition '@{0}' must include at least one DirectiveLocation.</value>
</data>
<data name="LogEntryHelper_DirectiveDefinitionSelfApplication" xml:space="preserve">
<value>The directive definition '@{0}' must not reference itself.</value>
</data>
<data name="LogEntryHelper_EmptyEnumType" xml:space="preserve">
<value>The Enum type '{0}' must define one or more values.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using HotChocolate.Events;
using HotChocolate.Events.Contracts;
using HotChocolate.Types;
using static HotChocolate.Logging.LogEntryHelper;

namespace HotChocolate.Rules;

/// <summary>
/// A Directive definition must not contain the use of a Directive which references
/// itself directly.
/// </summary>
/// <seealso href="https://spec.graphql.org/September2025/#sec-Type-System.Directives.Type-Validation">
/// Specification
/// </seealso>
public sealed class DirectiveDefinitionNoSelfReferenceRule : IValidationEventHandler<DirectiveDefinitionEvent>
{
/// <summary>
/// Checks that a directive definition is not applied to itself, either on the
/// definition or on any of its arguments.
/// </summary>
public void Handle(DirectiveDefinitionEvent @event, ValidationContext context)
{
var directiveDefinition = @event.DirectiveDefinition;

if (ContainsSelfReference(directiveDefinition.Directives, directiveDefinition))
{
context.Log.Write(DirectiveDefinitionSelfApplication(directiveDefinition));
return;
}

foreach (var argument in directiveDefinition.Arguments)
{
if (ContainsSelfReference(argument.Directives, directiveDefinition))
{
context.Log.Write(DirectiveDefinitionSelfApplication(directiveDefinition));
return;
}
}
Comment thread
glen-84 marked this conversation as resolved.
Dismissed
}

private static bool ContainsSelfReference(
IReadOnlyDirectiveCollection directives,
IDirectiveDefinition directiveDefinition)
{
foreach (var directive in directives)
{
if (string.Equals(
directive.Definition.Name,
directiveDefinition.Name,
StringComparison.Ordinal))
{
return true;
}
}
Comment thread
glen-84 marked this conversation as resolved.
Dismissed

return false;
}
}
3 changes: 3 additions & 0 deletions src/HotChocolate/Core/src/Types.Validation/SchemaValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public SchemaValidator(IEnumerable<object> rules)
public void AddDefaultRules()
{
_rules.Add(new DirectiveDefinitionIncludesLocationRule());
_rules.Add(new DirectiveDefinitionNoSelfReferenceRule());
_rules.Add(new DirectiveIsDefinedRule());
_rules.Add(new EnumValueIsDefinedRule());
_rules.Add(new NoInputObjectCycleRule());
Expand Down Expand Up @@ -162,6 +163,8 @@ private void PublishEvents(ValidationContext context)
PublishEvent(new DirectiveDefinitionEvent(directiveDefinition), context);
PublishEvent(new NamedMemberEvent(directiveDefinition), context);

PublishDirectiveEvents(directiveDefinition, context);

foreach (var argument in directiveDefinition.Arguments)
{
PublishEvent(new ArgumentEvent(argument), context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal sealed class TypeSystemFeature
public List<Action<ISchemaTypeDescriptor>> SchemaTypeOptions { get; } = [];
public List<SchemaDocumentInfo> SchemaDocuments { get; } = [];
#if NET10_0_OR_GREATER
public ImmutableDictionary<string, IReadOnlyList<DirectiveNode>> DirectiveExtensions { get; set; } =
[];
public ImmutableDictionary<string, IReadOnlyList<DirectiveNode>> ScalarDirectives { get; set; } =
[];
public ImmutableDictionary<Type, RuntimeTypeBinding> RuntimeTypeBindings { get; set; } =
Expand All @@ -23,6 +25,8 @@ internal sealed class TypeSystemFeature
public ImmutableDictionary<string, RuntimeTypeNameBinding> NameRuntimeTypeBinding { get; set; } =
[];
#else
public ImmutableDictionary<string, IReadOnlyList<DirectiveNode>> DirectiveExtensions { get; set; } =
ImmutableDictionary<string, IReadOnlyList<DirectiveNode>>.Empty;
public ImmutableDictionary<string, IReadOnlyList<DirectiveNode>> ScalarDirectives { get; set; } =
ImmutableDictionary<string, IReadOnlyList<DirectiveNode>>.Empty;
public ImmutableDictionary<Type, RuntimeTypeBinding> RuntimeTypeBindings { get; set; } =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using HotChocolate.Types;
using HotChocolate.Types.Descriptors;
using HotChocolate.Utilities;
using static HotChocolate.Configuration.Validation.TypeValidationHelper;
using static HotChocolate.Utilities.ErrorHelper;

Expand All @@ -25,6 +26,7 @@ public void Validate(
EnsureDirectiveNameIsValid(directiveDefinition, errors);
EnsureArgumentNamesAreValid(directiveDefinition, errors);
EnsureArgumentDeprecationIsValid(directiveDefinition, errors);
EnsureNoSelfApplication(directiveDefinition, errors);
}
}
}
Expand All @@ -44,4 +46,30 @@ private static void EnsureDirectiveNameIsValid(
}
}
}

private static void EnsureNoSelfApplication(
IDirectiveDefinition directiveDefinition,
ICollection<ISchemaError> errors)
{
foreach (var directive in directiveDefinition.Directives)
{
if (directive.Definition.Name.EqualsOrdinal(directiveDefinition.Name))
{
errors.Add(DirectiveDefinitionSelfApplication(directiveDefinition));
return;
}
}
Comment thread
glen-84 marked this conversation as resolved.
Dismissed

foreach (var argument in directiveDefinition.Arguments)
{
foreach (var directive in argument.Directives)
{
if (directive.Definition.Name.EqualsOrdinal(directiveDefinition.Name))
{
errors.Add(DirectiveDefinitionSelfApplication(directiveDefinition));
return;
}
}
Comment thread
glen-84 marked this conversation as resolved.
Dismissed
}
}
}
18 changes: 18 additions & 0 deletions src/HotChocolate/Core/src/Types/Internal/TypeDependencyHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using HotChocolate.Configuration;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Configurations;
using HotChocolate.Utilities;
using static HotChocolate.Types.Descriptors.Configurations.TypeDependencyFulfilled;

namespace HotChocolate.Internal;
Expand Down Expand Up @@ -139,6 +141,22 @@ public static void CollectDependencies(
}
}

if (definition.HasDirectives)
{
foreach (var directive in definition.Directives)
{
// A directive definition is always available to itself, so a
// self reference must not become a completion dependency.
if (directive.Type is NameDirectiveReference nameReference
&& nameReference.Name.EqualsOrdinal(definition.Name))
{
continue;
}

dependencies.Add(new TypeDependency(directive.Type, Completed));
}
}

if (definition.HasArguments)
{
foreach (var argument in definition.Arguments)
Expand Down
Loading