Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static class LogEntryCodes
public const string ProvidesInvalidSyntax = "PROVIDES_INVALID_SYNTAX";
public const string ProvidesOnNonCompositeField = "PROVIDES_ON_NON_COMPOSITE_FIELD";
public const string QueryRootTypeInaccessible = "QUERY_ROOT_TYPE_INACCESSIBLE";
public const string ReferenceToInaccessibleType = "REFERENCE_TO_INACCESSIBLE_TYPE";
public const string RequireInvalidFields = "REQUIRE_INVALID_FIELDS";
public const string RequireInvalidFieldType = "REQUIRE_INVALID_FIELD_TYPE";
public const string RequireInvalidSyntax = "REQUIRE_INVALID_SYNTAX";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,63 @@ public static LogEntry QueryRootTypeInaccessible(
.Build();
}

public static LogEntry ReferenceToInaccessibleTypeFromFieldArgument(
MutableInputFieldDefinition argument,
MutableOutputFieldDefinition field,
string referencedTypeName,
MutableSchemaDefinition schema)
{
return LogEntryBuilder.New()
.SetMessage(
LogEntryHelper_ReferenceToInaccessibleTypeFromFieldArgument,
argument.Name,
field.Coordinate.ToString(),
referencedTypeName)
.SetCode(LogEntryCodes.ReferenceToInaccessibleType)
.SetSeverity(LogSeverity.Error)
.SetTypeSystemMember(argument)
.SetSchema(schema)
.Build();
}

public static LogEntry ReferenceToInaccessibleTypeFromInputField(
MutableInputFieldDefinition inputField,
string typeName,
string referencedTypeName,
MutableSchemaDefinition schema)
{
return LogEntryBuilder.New()
.SetMessage(
LogEntryHelper_ReferenceToInaccessibleTypeFromInputField,
inputField.Name,
typeName,
referencedTypeName)
.SetCode(LogEntryCodes.ReferenceToInaccessibleType)
.SetSeverity(LogSeverity.Error)
.SetTypeSystemMember(inputField)
.SetSchema(schema)
.Build();
}

public static LogEntry ReferenceToInaccessibleTypeFromOutputField(
MutableOutputFieldDefinition outputField,
string typeName,
string referencedTypeName,
MutableSchemaDefinition schema)
{
return LogEntryBuilder.New()
.SetMessage(
LogEntryHelper_ReferenceToInaccessibleTypeFromOutputField,
outputField.Name,
typeName,
referencedTypeName)
.SetCode(LogEntryCodes.ReferenceToInaccessibleType)
.SetSeverity(LogSeverity.Error)
.SetTypeSystemMember(outputField)
.SetSchema(schema)
.Build();
}

public static LogEntry RequireInvalidFields(
Directive requireDirective,
MutableInputFieldDefinition argument,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using HotChocolate.Fusion.Events;
using HotChocolate.Fusion.Events.Contracts;
using HotChocolate.Fusion.Extensions;
using HotChocolate.Types;
using static HotChocolate.Fusion.Logging.LogEntryHelper;

namespace HotChocolate.Fusion.PostMergeValidationRules;

/// <summary>
/// In a composed schema, fields and arguments must only reference types that are exposed. This
/// requirement guarantees that public types do not reference <c>inaccessible</c> structures which
/// are intended for internal use.
/// </summary>
/// <seealso href="https://graphql.github.io/composite-schemas-spec/draft/#sec-Reference-To-Inaccessible-Type">
/// Specification
/// </seealso>
internal sealed class ReferenceToInaccessibleTypeRule
: IEventHandler<InputFieldEvent>
, IEventHandler<OutputFieldEvent>
, IEventHandler<FieldArgumentEvent>
{
public void Handle(InputFieldEvent @event, CompositionContext context)
{
var (inputField, type, schema) = @event;

if (inputField.HasFusionInaccessibleDirective())
{
return;
}

var fieldType = inputField.Type.AsTypeDefinition();

if (fieldType.HasFusionInaccessibleDirective())
{
context.Log.Write(
ReferenceToInaccessibleTypeFromInputField(
inputField,
type.Name,
fieldType.Name,
schema));
}
}

public void Handle(OutputFieldEvent @event, CompositionContext context)
{
var (field, type, schema) = @event;

if (field.HasFusionInaccessibleDirective())
{
return;
}

var fieldType = field.Type.AsTypeDefinition();

if (fieldType.HasFusionInaccessibleDirective())
{
context.Log.Write(
ReferenceToInaccessibleTypeFromOutputField(
field,
type.Name,
fieldType.Name,
schema));
}
}

public void Handle(FieldArgumentEvent @event, CompositionContext context)
{
var (argument, field, _, schema) = @event;

if (argument.HasFusionInaccessibleDirective())
{
return;
}

var argumentType = argument.Type.AsTypeDefinition();

if (argumentType.HasFusionInaccessibleDirective())
{
context.Log.Write(
ReferenceToInaccessibleTypeFromFieldArgument(
argument,
field,
argumentType.Name,
schema));
}
}
}

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 @@ -342,6 +342,15 @@
<data name="LogEntryHelper_QueryRootTypeInaccessible" xml:space="preserve">
<value>The root query type in schema '{0}' must be accessible.</value>
</data>
<data name="LogEntryHelper_ReferenceToInaccessibleTypeFromFieldArgument" xml:space="preserve">
<value>The merged field argument '{0}' on field '{1}' cannot reference the inaccessible type '{2}'.</value>
</data>
<data name="LogEntryHelper_ReferenceToInaccessibleTypeFromInputField" xml:space="preserve">
<value>The merged input field '{0}' in type '{1}' cannot reference the inaccessible type '{2}'.</value>
</data>
<data name="LogEntryHelper_ReferenceToInaccessibleTypeFromOutputField" xml:space="preserve">
<value>The merged output field '{0}' in type '{1}' cannot reference the inaccessible type '{2}'.</value>
</data>
<data name="LogEntryHelper_RequireInvalidFields" xml:space="preserve">
<value>The @require directive on argument '{0}' in schema '{1}' specifies an invalid field selection against the composed schema.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public CompositionResult<MutableSchemaDefinition> Compose()
new KeyInvalidFieldsRule(),
new NonNullInputFieldIsInaccessibleRule(),
new NoQueriesRule(),
new ReferenceToInaccessibleTypeRule(),
new RequireInvalidFieldsRule()
];
}
Loading
Loading