Skip to content

Commit

Permalink
Added circular reference validation rules in input types (#6821)
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonC9018 committed Jan 16, 2024
1 parent 75b3991 commit a5f0039
Show file tree
Hide file tree
Showing 12 changed files with 635 additions and 404 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using HotChocolate.Language;
using HotChocolate.Types;
using static HotChocolate.Configuration.Validation.TypeValidationHelper;
using static HotChocolate.Utilities.ErrorHelper;
Expand All @@ -15,18 +18,104 @@ internal sealed class InputObjectTypeValidationRule : ISchemaValidationRule
IReadOnlySchemaOptions options,
ICollection<ISchemaError> errors)
{
if (options.StrictValidation)
if (!options.StrictValidation)
{
List<string>? names = null;
return;
}

List<string>? names = null;
CycleValidationContext cycleValidationContext = new()
{
Visited = new(),
CycleStartIndex = new(),
Errors = errors,
FieldPath = new(),
};

foreach (var type in typeSystemObjects)
{
if (type is not InputObjectType inputType)
{
continue;
}

EnsureTypeHasFields(inputType, errors);
EnsureFieldNamesAreValid(inputType, errors);
EnsureOneOfFieldsAreValid(inputType, errors, ref names);
EnsureFieldDeprecationIsValid(inputType, errors);
TryReachCycleRecursively(cycleValidationContext, inputType);

cycleValidationContext.CycleStartIndex.Clear();
}
}

private struct CycleValidationContext
{
public HashSet<InputObjectType> Visited { get; set; }
public Dictionary<InputObjectType, int> CycleStartIndex { get; set; }
public ICollection<ISchemaError> Errors { get; set; }
public List<string> FieldPath { get; set; }
}

// https://github.com/IvanGoncharov/graphql-js/blob/408bcda9c88df85e039f5d072011b1cb465fe830/src/type/validate.js#L535
private static void TryReachCycleRecursively(
in CycleValidationContext context,
InputObjectType type)
{
if (!context.Visited.Add(type))
{
return;
}

context.CycleStartIndex[type] = context.FieldPath.Count;

foreach (var field in type.Fields)
{
var unwrappedType = UnwrapCompletelyIfRequired(field.Type);
if (unwrappedType is not InputObjectType inputObjectType)
{
continue;
}

context.FieldPath.Add(field.Name);
if (context.CycleStartIndex.TryGetValue(inputObjectType, out var cycleIndex))
{
var cyclePath = context.FieldPath.Skip(cycleIndex);
context.Errors.Add(
InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf(type, cyclePath));
}
else
{
TryReachCycleRecursively(context, inputObjectType);
}
context.FieldPath.Pop();
}

context.CycleStartIndex.Remove(type);
}

private static IType? UnwrapCompletelyIfRequired(IType type)
{
while (true)
{
if (type.Kind == TypeKind.NonNull)
{
type = ((NonNullType)type).Type;
}
else
{
return null;
}

foreach (var type in typeSystemObjects)
switch (type.Kind)
{
if (type is InputObjectType inputType)
case TypeKind.List:
{
EnsureTypeHasFields(inputType, errors);
EnsureFieldNamesAreValid(inputType, errors);
EnsureOneOfFieldsAreValid(inputType, errors, ref names);
EnsureFieldDeprecationIsValid(inputType, errors);
return null;
}
default:
{
return type;
}
}
}
Expand All @@ -37,30 +126,33 @@ internal sealed class InputObjectTypeValidationRule : ISchemaValidationRule
ICollection<ISchemaError> errors,
ref List<string>? temp)
{
if (type.Directives.ContainsDirective(WellKnownDirectives.OneOf))
if (!type.Directives.ContainsDirective(WellKnownDirectives.OneOf))
{
temp ??= new List<string>();
return;
}

foreach (var field in type.Fields)
{
if (field.Type.Kind is TypeKind.NonNull || field.DefaultValue is not null)
{
temp.Add(field.Name);
}
}
temp ??= new List<string>();

if (temp.Count > 0)
foreach (var field in type.Fields)
{
if (field.Type.Kind is TypeKind.NonNull || field.DefaultValue is not null)
{
var fieldNames = new string[temp.Count];
temp.Add(field.Name);
}
}

for (var i = 0; i < temp.Count; i++)
{
fieldNames[i] = temp[i];
}
if (temp.Count == 0)
{
return;
}

temp.Clear();
errors.Add(OneofInputObjectMustHaveNullableFieldsWithoutDefaults(type, fieldNames));
}
var fieldNames = new string[temp.Count];
for (var i = 0; i < temp.Count; i++)
{
fieldNames[i] = temp[i];
}

temp.Clear();
errors.Add(OneofInputObjectMustHaveNullableFieldsWithoutDefaults(type, fieldNames));
}
}

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

0 comments on commit a5f0039

Please sign in to comment.